一、核心架构与核心流程
Lottie 的核心是将设计师用 Adobe After Effects 制作的动画,通过 Bodymovin 插件导出为轻量级 JSON 文件,然后在移动端解析并渲染为流畅动画。整个流程可分为三大阶段:
-
JSON 解析阶段
- 解析器将 JSON 文件转换为 LottieComposition 对象,包含动画的宽高、帧率、图层结构等基础信息。
- 关键类
LayerParser递归解析 JSON 中的layers字段,根据ty字段创建不同类型的 BaseLayer 子类(如ShapeLayer、ImageLayer),并构建图层树结构。
-
动画控制阶段
LottieDrawable通过 ValueAnimator 和 Choreographer 同步屏幕刷新率(VSYNC 信号),驱动动画进度更新1011。- 核心方法
setProgress()将进度传递给所有图层,触发关键帧动画计算和重绘请求。
-
渲染阶段
- 每个
BaseLayer负责将自身内容绘制到Canvas上,支持图层叠加、变换(缩放 / 旋转 / 位移)等效果17。 - 复杂动画(如遮罩、路径动画)通过 KeyframeAnimation 子类实现插值计算,例如
TransformKeyframeAnimation处理图层变换属性。
- 每个
二、源码级实现细节
1. JSON 解析流程
// LottieCompositionFactory.java
public static void fromAssetFileName(Context context, String fileName,
@NonNull CompositionListener listener) {
// 异步解析 JSON 文件
new AsyncTask<Void, Void, LottieComposition>() {
@Override
protected LottieComposition doInBackground(Void... params) {
try (InputStream is = context.getAssets().open(fileName)) {
return LottieCompositionParser.parse(is);
}
}
@Override
protected void onPostExecute(LottieComposition composition) {
listener.onCompositionLoaded(composition);
}
}.execute();
}
// LottieCompositionParser.java
public static LottieComposition parse(InputStream inputStream) {
JsonReader reader = new JsonReader(new InputStreamReader(inputStream));
reader.beginObject();
while (reader.hasNext()) {
String name = reader.nextName();
if (name.equals("layers")) {
// 解析图层数组
List<Layer> layers = parseLayers(reader);
composition.setLayers(layers);
}
// 解析其他字段(帧率、尺寸等)
}
return composition;
}
// LayerParser.java
public static BaseLayer parse(JsonReader reader, LottieDrawable drawable) {
reader.beginObject();
while (reader.hasNext()) {
String name = reader.nextName();
if (name.equals("ty")) {
int layerType = reader.nextInt();
switch (layerType) {
case 1: // 形状图层
return new ShapeLayer(drawable, layerModel);
case 2: // 图片图层
return new ImageLayer(drawable, layerModel);
// 其他图层类型...
}
}
}
}
关键机制:
- 流式解析:使用
JsonReader避免一次性加载整个 JSON 到内存,降低 OOM 风险。 - 图层树构建:按 JSON 中
layers的顺序倒序创建图层,确保绘制顺序与 AE 一致(后定义的图层覆盖先定义的)。
2. 动画驱动与渲染
// LottieDrawable.java
public void playAnimation() {
if (animator == null) {
animator = ValueAnimator.ofFloat(0f, 1f);
animator.addUpdateListener(animation -> setProgress(animator.getAnimatedFraction()));
}
animator.start();
}
public void setProgress(float progress) {
if (compositionLayer != null) {
// 递归设置所有图层的进度
compositionLayer.setProgress(progress);
invalidateSelf(); // 触发重绘
}
}
// BaseLayer.java
public void setProgress(float progress) {
// 更新变换动画(缩放、旋转等)
transform.setProgress(progress);
// 更新其他关键帧动画
for (KeyframeAnimation<?> animation : animations) {
animation.setProgress(progress);
}
}
// ImageLayer.java
@Override
public void drawLayer(Canvas canvas, Matrix parentMatrix, float alpha) {
// 获取当前帧的 Bitmap
Bitmap bitmap = imageAsset.getBitmap();
// 应用变换矩阵
Matrix matrix = new Matrix(parentMatrix);
transform.getMatrix().postConcat(matrix);
// 绘制 Bitmap
canvas.drawBitmap(bitmap, matrix, paint);
}
核心原理:
- 属性动画驱动:
ValueAnimator计算 0~1 的进度值,通过setProgress()传递给所有图层。 - VSYNC 同步:
Choreographer确保动画帧与屏幕刷新率同步,避免卡顿。 - 增量渲染:仅重绘变化的图层,利用
Canvas.save()/restore()实现图层叠加。
3. 关键帧动画插值计算
// TransformKeyframeAnimation.java
public Matrix getValue() {
Matrix matrix = new Matrix();
// 计算缩放
PointF scale = scaleAnimation.getValue();
matrix.postScale(scale.x, scale.y);
// 计算旋转
float rotation = rotationAnimation.getValue();
matrix.postRotate(rotation);
// 计算位移
PointF position = positionAnimation.getValue();
matrix.postTranslate(position.x, position.y);
return matrix;
}
// LinearKeyframeAnimation.java
@Override
public T getValue() {
float fraction = getCurrentFraction();
// 线性插值:startValue + (endValue - startValue) * fraction
return (T) (startValue + (endValue - startValue) * fraction);
}
// BezierKeyframeAnimation.java
@Override
public T getValue() {
float fraction = getCurrentFraction();
// 贝塞尔曲线插值
float bezierFraction = BezierEvaluator.evaluate(fraction, inTangent, outTangent);
return (T) (startValue + (endValue - startValue) * bezierFraction);
}
技术细节:
- 插值器类型:支持线性插值、贝塞尔曲线插值(用于缓动效果)、路径插值(如
PositionKeyframeAnimation)。 - 浮点精度:动画进度允许浮点值(如 15.25 帧),确保过渡平滑。
三、性能优化策略
-
硬件加速
- 默认启用
Canvas硬件加速,复杂图层通过RenderNode缓存渲染结果。 - 避免使用
saveLayer()(如遮罩),因其会创建离屏缓冲区,导致性能下降。
- 默认启用
-
内存管理
- 图片缓存:
LottieImageAsset缓存加载的 Bitmap,支持CacheStrategy控制内存占用35。 - 对象池:复用
Matrix、Paint等绘图对象,减少 GC 压力。
- 图片缓存:
-
按需渲染
- 仅在动画可见时更新进度,通过
ViewTreeObserver监听可见性。 - 列表中使用时,建议通过
setAnimation(String, CacheStrategy)启用缓存。
- 仅在动画可见时更新进度,通过
四、复杂动画实现原理
-
遮罩(Mask)与蒙版(Matte)
- 遮罩:通过
MaskLayer定义可见区域,使用Canvas.clipPath()实现硬裁剪。 - 蒙版:
MatteLayer支持透明度混合,需多次渲染到离屏缓冲区,性能开销较大。
- 遮罩:通过
-
路径动画
ShapeLayer解析 JSON 中的shapes字段,构建Path对象。PathKeyframeAnimation使用贝塞尔曲线插值计算路径点,实现平滑移动。
-
文本动画
TextLayer解析 JSON 中的字体、颜色、位置等属性,通过StaticTextKeyframeAnimation实现文本内容和样式的动态变化。
五、总结与最佳实践
-
核心优势:
- 跨平台一致性:同一份 JSON 可在 Android、iOS、Web 上渲染出相同效果39。
- 轻量高效:JSON 体积远小于帧动画,内存占用低,支持硬件加速17。
- 动态控制:可实时调整动画进度、速度,添加事件监听811。
-
使用建议:
-
避免过度绘制:减少图层嵌套和复杂遮罩,优先使用矢量图形17。
-
缓存策略:列表中使用
CacheStrategy避免重复解析 JSON 和加载图片35。 -
性能监控:通过
LottieDrawable的addAnimatorListener监听帧率,结合StrictMode检测卡顿711。
-
Lottie 的设计哲学是将动画创作与工程实现解耦,通过声明式 JSON 描述和高效渲染引擎,让开发者专注于业务逻辑,而无需手工实现复杂动效。理解其核心原理后,可更灵活地优化动画性能,并扩展自定义渲染逻辑(如结合 OpenGL 实现 3D 效果)。