Android library for rendering Adobe After Effects animations exported as JSON through Bodymovin with native performance and extensive customization options.
—
Runtime animation modification system using keypaths to target specific elements and properties. This powerful system allows you to dynamically change colors, positions, transforms, and other animation properties without modifying the source animation files.
KeyPath system for targeting specific animation elements using hierarchical paths with support for wildcards and globstars.
/**
* Keypath for targeting specific animation elements
*/
public class KeyPath {
// Constructors
public KeyPath(String... keys);
// Static instances
public static final KeyPath COMPOSITION; // Targets root composition
// Path manipulation (internal)
@RestrictTo(RestrictTo.Scope.LIBRARY)
public KeyPath addKey(String key);
@RestrictTo(RestrictTo.Scope.LIBRARY)
public KeyPath resolve(KeyPathElement element);
@RestrictTo(RestrictTo.Scope.LIBRARY)
public KeyPathElement getResolvedElement();
// Path matching (internal)
@RestrictTo(RestrictTo.Scope.LIBRARY)
public boolean matches(String key, int depth);
@RestrictTo(RestrictTo.Scope.LIBRARY)
public int incrementDepthBy(String key, int depth);
@RestrictTo(RestrictTo.Scope.LIBRARY)
public boolean fullyResolvesTo(String key, int depth);
@RestrictTo(RestrictTo.Scope.LIBRARY)
public boolean propagateToChildren(String key, int depth);
// Utility methods
public String keysToString();
@Override
public boolean equals(Object o);
@Override
public int hashCode();
@Override
public String toString();
}KeyPath Patterns:
new KeyPath("Layer Name", "Shape Group", "Fill")*): Matches exactly one element - new KeyPath("*", "Shape Group", "Fill")**): Matches zero or more elements - new KeyPath("**", "Fill")KeyPath.COMPOSITION - targets the entire animationUsage Examples:
// Target specific layer's fill color
KeyPath colorPath = new KeyPath("Character", "Body", "Fill 1");
animationView.addValueCallback(colorPath, LottieProperty.COLOR,
new LottieValueCallback<>(Color.BLUE));
// Target all fills in animation
KeyPath allFills = new KeyPath("**", "Fill");
animationView.addValueCallback(allFills, LottieProperty.COLOR,
new LottieValueCallback<>(Color.GREEN));
// Target specific layer with wildcard
KeyPath layerPath = new KeyPath("Layer *", "Transform");
animationView.addValueCallback(layerPath, LottieProperty.TRANSFORM_OPACITY,
new LottieValueCallback<>(50)); // 50% opacity
// Target root composition transform
animationView.addValueCallback(KeyPath.COMPOSITION, LottieProperty.TRANSFORM_SCALE,
new LottieValueCallback<>(new ScaleXY(2.0f, 2.0f)));
// Resolve keypath to see what elements match
List<KeyPath> resolvedPaths = animationView.resolveKeyPath(new KeyPath("**", "Fill"));
for (KeyPath path : resolvedPaths) {
Log.d("Lottie", "Resolved path: " + path.keysToString());
}Constants defining all animatable properties that can be modified through dynamic property callbacks.
/**
* Constants for animatable properties
*/
public interface LottieProperty {
// Color properties (ColorInt values)
Integer COLOR = 1;
Integer STROKE_COLOR = 2;
Integer DROP_SHADOW_COLOR = 5;
// Opacity properties (0-100 to match After Effects)
Integer TRANSFORM_OPACITY = 3;
Integer OPACITY = 4; // [0,100]
Float DROP_SHADOW_OPACITY = 15f; // [0,100]
Float TRANSFORM_START_OPACITY = 12f; // [0,100]
Float TRANSFORM_END_OPACITY = 12.1f; // [0,100]
// Transform properties
PointF TRANSFORM_ANCHOR_POINT = new PointF(); // In Px
PointF TRANSFORM_POSITION = new PointF(); // In Px
Float TRANSFORM_POSITION_X = 15f; // In Px (when split dimensions enabled)
Float TRANSFORM_POSITION_Y = 16f; // In Px (when split dimensions enabled)
ScaleXY TRANSFORM_SCALE = new ScaleXY();
Float TRANSFORM_ROTATION = 1f; // In degrees
Float TRANSFORM_SKEW = 0f; // 0-85
Float TRANSFORM_SKEW_ANGLE = 0f; // In degrees
// Shape properties
PointF ELLIPSE_SIZE = new PointF(); // In Px
PointF RECTANGLE_SIZE = new PointF(); // In Px
Float CORNER_RADIUS = 0f; // In degrees
PointF POSITION = new PointF(); // In Px
// Stroke properties
Float STROKE_WIDTH = 2f; // In Px
// Polystar properties
Float POLYSTAR_POINTS = 6f;
Float POLYSTAR_ROTATION = 7f; // In degrees
Float POLYSTAR_INNER_RADIUS = 8f; // In Px
Float POLYSTAR_OUTER_RADIUS = 9f; // In Px
Float POLYSTAR_INNER_ROUNDEDNESS = 10f; // [0,100]
Float POLYSTAR_OUTER_ROUNDEDNESS = 11f; // [0,100]
// Repeater properties
Float REPEATER_COPIES = 4f;
Float REPEATER_OFFSET = 5f;
// Text properties
Float TEXT_TRACKING = 3f;
Float TEXT_SIZE = 14f; // In Dp
Typeface TYPEFACE = Typeface.DEFAULT;
CharSequence TEXT = "dynamic_text";
// Time properties
Float TIME_REMAP = 13f; // Time value in seconds
// Effect properties
Float BLUR_RADIUS = 17f; // In Px
ColorFilter COLOR_FILTER = new ColorFilter();
// Drop shadow properties (resolved on drawing content)
Float DROP_SHADOW_DIRECTION = 16f; // Degrees from 12 o'clock
Float DROP_SHADOW_DISTANCE = 17f; // In Px
Float DROP_SHADOW_RADIUS = 18f; // In Px
// Gradient properties
Integer[] GRADIENT_COLOR = new Integer[0]; // Array of ARGB colors
// Image properties
Bitmap IMAGE = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8);
}Usage Examples:
// Change fill color
animationView.addValueCallback(
new KeyPath("Layer", "Shape", "Fill"),
LottieProperty.COLOR,
new LottieValueCallback<>(Color.parseColor("#FF5722"))
);
// Modify transform properties
animationView.addValueCallback(
new KeyPath("MovingObject"),
LottieProperty.TRANSFORM_POSITION,
new LottieValueCallback<>(new PointF(100, 200))
);
// Adjust opacity
animationView.addValueCallback(
new KeyPath("FadingLayer"),
LottieProperty.TRANSFORM_OPACITY,
new LottieValueCallback<>(75) // 75% opacity
);
// Scale animation
animationView.addValueCallback(
KeyPath.COMPOSITION,
LottieProperty.TRANSFORM_SCALE,
new LottieValueCallback<>(new ScaleXY(1.5f, 1.5f))
);
// Change stroke width
animationView.addValueCallback(
new KeyPath("**", "Stroke"),
LottieProperty.STROKE_WIDTH,
new LottieValueCallback<>(8.0f) // 8px stroke
);
// Dynamic text replacement
animationView.addValueCallback(
new KeyPath("TextLayer"),
LottieProperty.TEXT,
new LottieValueCallback<>("New Dynamic Text")
);
// Apply color filter to entire animation
animationView.addValueCallback(
KeyPath.COMPOSITION,
LottieProperty.COLOR_FILTER,
new LottieValueCallback<>(new PorterDuffColorFilter(Color.BLUE, PorterDuff.Mode.SRC_ATOP))
);
// Replace image asset
animationView.addValueCallback(
new KeyPath("ImageLayer"),
LottieProperty.IMAGE,
new LottieValueCallback<>(customBitmap)
);Base callback system for providing dynamic values to animation properties. Supports both static values and frame-by-frame dynamic values.
/**
* Base class for dynamic property callbacks
*/
public class LottieValueCallback<T> {
// Constructors
public LottieValueCallback();
public LottieValueCallback(T staticValue);
// Value provision
public T getValue(LottieFrameInfo<T> frameInfo);
public final void setValue(T value);
// Internal animation binding
@RestrictTo(RestrictTo.Scope.LIBRARY)
public final T getValueInternal(
float startFrame,
float endFrame,
T startValue,
T endValue,
float linearKeyframeProgress,
float interpolatedKeyframeProgress,
float overallProgress);
@RestrictTo(RestrictTo.Scope.LIBRARY)
public final void setAnimation(BaseKeyframeAnimation<?, ?> animation);
}
/**
* Frame information provided to value callbacks
*/
public class LottieFrameInfo<T> {
// Frame timing
public float getStartFrame();
public float getEndFrame();
// Keyframe values
public T getStartValue();
public T getEndValue();
// Progress information
public float getLinearKeyframeProgress(); // Linear progress 0-1
public float getInterpolatedKeyframeProgress(); // Eased progress 0-1
public float getOverallProgress(); // Overall animation progress 0-1
// Internal state management
@RestrictTo(RestrictTo.Scope.LIBRARY)
public LottieFrameInfo<T> set(
float startFrame, float endFrame,
T startValue, T endValue,
float linearKeyframeProgress,
float interpolatedKeyframeProgress,
float overallProgress);
}Usage Examples:
// Static value callback
animationView.addValueCallback(
new KeyPath("Layer"),
LottieProperty.COLOR,
new LottieValueCallback<>(Color.RED) // Always red
);
// Dynamic value callback
animationView.addValueCallback(
new KeyPath("PulsingCircle"),
LottieProperty.TRANSFORM_SCALE,
new LottieValueCallback<ScaleXY>() {
@Override
public ScaleXY getValue(LottieFrameInfo<ScaleXY> frameInfo) {
// Pulse between 0.8x and 1.2x based on progress
float progress = frameInfo.getOverallProgress();
float scale = 1.0f + 0.2f * (float) Math.sin(progress * Math.PI * 4);
return new ScaleXY(scale, scale);
}
}
);
// Color interpolation based on progress
animationView.addValueCallback(
new KeyPath("ColorChangingShape"),
LottieProperty.COLOR,
new LottieValueCallback<Integer>() {
@Override
public Integer getValue(LottieFrameInfo<Integer> frameInfo) {
float progress = frameInfo.getOverallProgress();
// Interpolate from red to blue
int red = (int) (255 * (1 - progress));
int blue = (int) (255 * progress);
return Color.rgb(red, 0, blue);
}
}
);
// Position animation following circular path
animationView.addValueCallback(
new KeyPath("CircularMotion"),
LottieProperty.TRANSFORM_POSITION,
new LottieValueCallback<PointF>() {
@Override
public PointF getValue(LottieFrameInfo<PointF> frameInfo) {
float progress = frameInfo.getOverallProgress();
float angle = progress * 2 * (float) Math.PI;
float radius = 100f;
float centerX = 200f;
float centerY = 200f;
return new PointF(
centerX + radius * (float) Math.cos(angle),
centerY + radius * (float) Math.sin(angle)
);
}
}
);
// Using keyframe interpolation values
animationView.addValueCallback(
new KeyPath("BlendingShape"),
LottieProperty.COLOR,
new LottieValueCallback<Integer>() {
@Override
public Integer getValue(LottieFrameInfo<Integer> frameInfo) {
// Use original keyframe values with custom blending
Integer startColor = frameInfo.getStartValue();
Integer endColor = frameInfo.getEndValue();
float interpolatedProgress = frameInfo.getInterpolatedKeyframeProgress();
if (startColor == null || endColor == null) {
return Color.WHITE; // Fallback
}
// Custom color interpolation
return interpolateColor(startColor, endColor, interpolatedProgress);
}
private Integer interpolateColor(Integer startColor, Integer endColor, float progress) {
int startRed = Color.red(startColor);
int startGreen = Color.green(startColor);
int startBlue = Color.blue(startColor);
int endRed = Color.red(endColor);
int endGreen = Color.green(endColor);
int endBlue = Color.blue(endColor);
int red = (int) (startRed + (endRed - startRed) * progress);
int green = (int) (startGreen + (endGreen - startGreen) * progress);
int blue = (int) (startBlue + (endBlue - startBlue) * progress);
return Color.rgb(red, green, blue);
}
}
);
// Update static value dynamically
LottieValueCallback<Integer> colorCallback = new LottieValueCallback<>(Color.RED);
animationView.addValueCallback(path, LottieProperty.COLOR, colorCallback);
// Later, change the static value
colorCallback.setValue(Color.BLUE); // Animation will update automaticallyPre-built callback implementations for common use cases involving relative values and interpolation.
/**
* Simple static value callback
*/
public class SimpleLottieValueCallback<T> extends LottieValueCallback<T> {
public SimpleLottieValueCallback(T value);
}
/**
* Relative float value callback
*/
public class LottieRelativeFloatValueCallback extends LottieValueCallback<Float> {
public LottieRelativeFloatValueCallback();
public LottieRelativeFloatValueCallback(Float staticValue);
public Float getOffset(LottieFrameInfo<Float> frameInfo);
}
/**
* Relative integer value callback
*/
public class LottieRelativeIntegerValueCallback extends LottieValueCallback<Integer> {
public LottieRelativeIntegerValueCallback();
public LottieRelativeIntegerValueCallback(Integer staticValue);
public Integer getOffset(LottieFrameInfo<Integer> frameInfo);
}
/**
* Relative point value callback
*/
public class LottieRelativePointValueCallback extends LottieValueCallback<PointF> {
public LottieRelativePointValueCallback();
public LottieRelativePointValueCallback(PointF staticValue);
public PointF getOffset(LottieFrameInfo<PointF> frameInfo);
}
/**
* Interpolated float value
*/
public class LottieInterpolatedFloatValue extends LottieValueCallback<Float> {
public LottieInterpolatedFloatValue(Float startValue, Float endValue);
public LottieInterpolatedFloatValue(Float startValue, Float endValue, Interpolator interpolator);
}
/**
* Interpolated integer value
*/
public class LottieInterpolatedIntegerValue extends LottieValueCallback<Integer> {
public LottieInterpolatedIntegerValue(Integer startValue, Integer endValue);
public LottieInterpolatedIntegerValue(Integer startValue, Integer endValue, Interpolator interpolator);
}
/**
* Interpolated point value
*/
public class LottieInterpolatedPointValue extends LottieValueCallback<PointF> {
public LottieInterpolatedPointValue(PointF startValue, PointF endValue);
public LottieInterpolatedPointValue(PointF startValue, PointF endValue, Interpolator interpolator);
}Usage Examples:
// Simple static callback
animationView.addValueCallback(
path,
LottieProperty.COLOR,
new SimpleLottieValueCallback<>(Color.MAGENTA)
);
// Relative offset - adds to original animation values
animationView.addValueCallback(
new KeyPath("MovingObject"),
LottieProperty.TRANSFORM_POSITION,
new LottieRelativePointValueCallback(new PointF(50, 100)) // Offset by 50,100
);
// Interpolated values with custom interpolation
animationView.addValueCallback(
new KeyPath("FadingElement"),
LottieProperty.TRANSFORM_OPACITY,
new LottieInterpolatedFloatValue(
100f, 0f, // From 100% to 0% opacity
new AccelerateDecelerateInterpolator()
)
);
// Dynamic relative positioning
animationView.addValueCallback(
new KeyPath("FollowingElement"),
LottieProperty.TRANSFORM_POSITION,
new LottieRelativePointValueCallback() {
@Override
public PointF getOffset(LottieFrameInfo<PointF> frameInfo) {
// Dynamic offset based on animation progress
float progress = frameInfo.getOverallProgress();
return new PointF(progress * 200f, (float) Math.sin(progress * Math.PI) * 50f);
}
}
);public class ScaleXY {
public ScaleXY();
public ScaleXY(float scaleX, float scaleY);
public float getScaleX();
public float getScaleY();
public void set(float scaleX, float scaleY);
@Override
public String toString();
}
public interface KeyPathElement {
void resolveKeyPath(KeyPath keyPath, int depth, List<KeyPath> accumulator, KeyPath currentPartialKeyPath);
void addValueCallback(T property, LottieValueCallback<T> callback);
}Install with Tessl CLI
npx tessl i tessl/maven-com-airbnb-android--lottie