vlambda博客
学习文章列表

Lottie使用&源码分析

一、简介

Lottie是Airbnb开源的一个动画渲染库,支持多平台,包括iOS、Android、React Native以及Web。Lottie动画的制作流程如下:设计师通用After Effects 制作动画,然后通过Bodymovin插件导出对应的json文件,给到每个端的研发,然后进行编译展示。

二、简单使用

1、依赖引入

2、使用

在代码里面创建一个LottieAnimationView对象,然后使用setAnimation设置存放在assets目录下的JSON文件,然后运行就可以看到对应的动画效果了。

public class LoadingView extends FrameLayout {

private LottieAnimationView lottieAnimationView;

public LoadingView(@NonNull Context context) {
super(context);
lottieAnimationView = new LottieAnimationView(getContext());
LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1000);
layoutParams.gravity = Gravity.CENTER;
addView(lottieAnimationView, layoutParams);
lottieAnimationView.setAnimation("LottieLogo1.json");
lottieAnimationView.setRepeatCount(ValueAnimator.INFINITE);
}

@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
lottieAnimationView.playAnimation();
}
}

这里有个小细节需要注意一点,如果遇到首次播放不了的情况,可以将playAnimation的调用,放在onAttachedToWindow的下面,具体原因后面解释。

三、进阶用法

多样的数据源

  • a、从raw文件夹获取: src/main/res/raw
  • b、从 src/main/assets加载zip文件;
  • c、从一个指向json文件或者zip文件的url;
  • d、从网络返回的json字符串;
  • e、从 InputStream获取到的json文件或者zip文件;带来的好处就是,根据根据诉求可以动态进行改变。

代码变色

lottie提供了三个改变颜色的API,

addColorFilter();
addColorFilterToLayer();
addColorFilterToContent();
  • addColorFilter 调用LottieAnimationView的 addColorFilter方法,即可修改对应的颜色。
lottieAnimationView.addColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_ATOP));

譬如,官方demo 里面的HamburgerArrow.json文件,原先是紫色的,增加上以上代码之后,整个就变成了红色。Lottie使用&源码分析Lottie使用&源码分析注意,这种修改方式,只能使用在背景透明的动画上,如果lottie动画本身就自带了背景色,那这种方式并不适用。因为addColorFilter的实现原理是为每一层都加上了colorfilter,对应的源码如下:

  @Override public void addColorFilter(@Nullable String layerName, @Nullable String contentName,
@Nullable ColorFilter colorFilter) {
for (int i = 0; i < layers.size(); ++i) {
final BaseLayer layer = layers.get(i);
final String name = layer.getLayerModel().getName();
if (layerName == null) {
layer.addColorFilter(null, null, colorFilter);
} else if (name.equals(layerName)) {
layer.addColorFilter(layerName, contentName, colorFilter);
}
}
}

会连背景色也被替换成为红色。导致看不出动画效果。

  • addColorFilterToLayer 为了解决上面提到的问题,lottie提供了另外一个apiaddColorFilterToLayer,可以在指定的layer上增加对应的colorfilter.

  • addColorFilterToContent 进一步的,通过addColorFilterToContent可以给对应的layer的content,增加对应的色值。

setSpeed

设置播放速度,譬如如果设置了2,就是普通不设置场景下的2倍速度进行播放。

setProgress

设置开始播放的进度。第一次播放的时候,从整体进度的百分之多少开始执行动画,如果是loop的方式,只会影响第一次的进度。

设置动画监听

lottieAnimationView.addAnimatorListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {

}

@Override
public void onAnimationEnd(Animator animation) {

}

@Override
public void onAnimationCancel(Animator animation) {

}

@Override
public void onAnimationRepeat(Animator animation) {

}
});
lottieAnimationView.addAnimatorUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {}});

性能提升

1、Lottie动画如果包含遮罩、阴影或者蒙版,在性能会有所下降;如果必须使用遮罩,务必让其覆盖最小的区域。2.在列表中使用lottie动画,要使用缓存,避免内存抖动 3.开启硬件加速。默认是关闭的。

四、其他效率提升工具

五、源码分析

我们使用Lottie的时候,最关键的类就是LottieAnimationView(继承自ImageView)和LottieDrawable(继承自Drawable),Lottie的描述文件最终会解析成一系列的Layer,然后在绘制的时候,根据不同的进度,绘制Layer的不同帧。

JSON描述文件

在分析源码之前,我们需要先认识一下,加载的json 文件的数据结构是怎样的。比较重要的有三层,assets,这个描述的是,图片资源的位置;layers,这个描述的是,每个图层的相关信息;shapes,这个描述的是,具体图层的动画元素相关信息。Lottie使用&源码分析

assets会解析成LottieImageAsset对象,Layers层在渲染的时候,会被解析成以下几种类型中的一种。Lottie使用&源码分析shapes会被解析成ShapeGroup

动画文件的加载

Lottie提供了各种数据源获取的API,根据数据源的不同选择不同的获取方式。这里我们介绍一下,从assets下面获取解析json文件的流程。

public void setAnimation(final String animationName, final CacheStrategy cacheStrategy) {
this.animationName = animationName;
lottieDrawable.cancelAnimation();
cancelLoaderTask();
compositionLoader = LottieComposition.Factory.fromAssetFileName(getContext(), animationName,
new OnCompositionLoadedListener() {
@Override public void onCompositionLoaded(LottieComposition composition) {
if (cacheStrategy == CacheStrategy.Strong) {
strongRefCache.put(animationName, composition);
} else if (cacheStrategy == CacheStrategy.Weak) {
weakRefCache.put(animationName, new WeakReference<>(composition));
}
setComposition(composition);
}
});
}

如上,先调用fromAssetFileName方法,会直接同步使用AssetManageropen方法,然后将InputStream流提供给FileCompositionLoader(继承自AsyncTask),在里面进行异步解析。最终会返回一个LottieComposition对象。

public class LottieComposition {
//预合成的图层
private final Map<String, List<Layer>> precomps = new HashMap<>();
//图片资源
private final Map<String, LottieImageAsset> images = new HashMap<>();
//所有的Layer,带对应的ID
private final LongSparseArray<Layer> layerMap = new LongSparseArray<>();
//所有的layer,
private final List<Layer> layers = new ArrayList<>();
private final Rect bounds;
private final long startFrame;
private final long endFrame;
private final int frameRate;
private final float dpScale;
}

至此,就已经将json文件解析完成,接着会调用LottieDrawablesetComposition方法。进行一系列的初始化配置,包括速度、原始进度、colorFilter的设置、layer之间的关系等。

public boolean setComposition(LottieComposition composition) {
if (this.composition == composition){return false;}
clearComposition();
this.composition = composition;
//设置速度
setSpeed(speed);
updateBounds();
//构建CompositionLayer,会将所有的Layer转换成为可以绘制的各种BaseLayer
buildCompositionLayer();
//处理colorFilter的设置
applyColorFilters();
//处理进度的设置
setProgress(progress);
//如果需要,怎在以上配置完成,开始执行动画
if(playAnimationWhenCompositionAdded){
playAnimationWhenCompositionAdded = false;
playAnimation();
}
if(reverseAnimationWhenCompositionAdded) {
reverseAnimationWhenCompositionAdded = false;
reverseAnimation();
}
return true;
}

动画文件的渲染

其实就是将上面构建出来的各种BaseLayer进行对应的绘制。当调用animator.start的时候,LottieDrawableonAnimationUpdate就会被回调,根据动画的进度,调用setProgress方法进行更新。

public void onAnimationUpdate(ValueAnimator animation) {
if (systemAnimationsAreDisabled) {
animator.cancel();
setProgress(1f);
} else {
setProgress((float) animation.getAnimatedValue());
}
}
public void setProgress(@FloatRange(from = 0f, to = 1f) float progress) {
this.progress = progress;
if (compositionLayer != null) {
compositionLayer.setProgress(progress);
}
}

而真正实现动画功能的,其实是CompositionLayer里面控制的。在这里会调用到每个BaseLayersetProgress,

public void setProgress(@FloatRange(from = 0f, to = 1f) float progress) {
super.setProgress(progress);
progress -= layerModel.getStartProgress();
for (int i = layers.size() - 1; i >= 0; i--) {
layers.get(i).setProgress(progress);
}
}

而BaseLayer的setProgress会触发LottieDrawableinvalidateSelf方法,进行重新绘制。随着动画的不断执行,就会不断绘制对应进度的样式,形成动画。

    public void onValueChanged() {
this.invalidateSelf();
}
private void invalidateSelf() {
this.lottieDrawable.invalidateSelf();
}

小结

整个流程其实就是:1、通过LottieComposition.Factory获取对应数据源的json文件并解析成LottieComposition。2、调用LottieDrawablesetComposition,将所有的图层解析成对应的Layer,并构建出一个基础的CompositionLayer. 3、接着调用LottieDrawable的动画执行方法,触发BaseLayer的draw()方法不断执行,不断的绘制各个图层从而形成动画。