简介
- 官网地址:airbnb.io/lottie/#/an…
- github:github.com/airbnb/lott…
- 动画库+预览动画:lottiefiles.com/account/das…
- 约800种方法
- 111kb未压缩
使用方法
- 布局中添加LottieAnimationView控件
如果json文件在assets子文件夹中,lottie_fileName="lottieani/hello-world.json"
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/animation_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:lottie_fileName="hello-world.json"
app:lottie_loop="true"
app:lottie_autoPlay="true" />
同时也支持
//assets
<attr name="lottie_fileName" format="string" />
//raw
<attr name="lottie_rawRes" format="reference" />
//链接
<attr name="lottie_url" format="string" />
- 获取到LottieAnimationView对象进行动画操作
// 布局中不指定文件可以在此设置,路径设置同布局文件
//assets下的文件
animationView.setAnimation("match_success.json");
//R.raw.xxx
animationView.setAnimation(R.raw.match_success);
//从链接获取Json
animationView.setAnimationFromUrl("https://assets7.lottiefiles.com/private_files/lf3 rBgRS1.json");
//json串
animationView.setAnimationFromJson("json", "cachekey");
// 是否循环播放
animationView.loop(true);
// 设置播放速率,例如:2代表播放速率是不设置时的二倍
animationView.setSpeed(2f);
// 开始播放
animationView.playAnimation();
// 暂停播放
animationView.pauseAnimation();
// 取消播放
animationVIew.cancelAnimation();
// 设置播放进度
animationView.setProgress(0.5f);
// 判断是否正在播放
animationView.isAnimating();
- 添加监听 LottieAnimationView里使用的是ValueAnimator控制进度
//json转化LottieComposition,准备好动画的回调
animationView.addLottieOnCompositionLoadedListener(***)
//动画监听
animationView.addAnimatorUpdateListener(ValueAnimator.AnimatorUpdateListene updateListener);
animationView.addAnimatorListener(Animator.AnimatorListener listener);
//自定义动画的速率和时长
ValueAnimator valueAnimator = ValueAnimator
.ofFloat(0f, 1f)
.setDuration(5000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
animationView.setProgress((Float) animation.getAnimatedValue());
}
});
valueAnimator.start();
- 替换动画元素
//参考PPLottieAnimationView
animationView.setAnimation(R.raw.match_success);
animationView.addLottieOnCompositionLoadedListener(new LottieOnCompositionLoadedListener() {
@Override
public void onCompositionLoaded(LottieComposition composition) {
String imageUrl = "https://ae01.alicdn.com/kf/U75499bbca2264e6badd85f7695dd9ab5H.jpg";
animationView.setDynamicImage("image_0", imageUrl);
animationView.setDynamicImage("image_1", imageUrl);
animationView.playAnimation();
}
});
更多详细的使用可以浏览官网 链接在上方
源码解析
加载流程
- LottieComposition:承载json文件解析好的数据
-
- LottieAnimationView:可以认为是LottieDrawable的代理类
- LottieDrawable:真正干活的类负责绘制
- LottieValueAnimator:通过监听Vsync,一帧一帧更新进度
- LottieCompositionFactory:创建和缓存LottieComposition
- LottieTask:运行异步任务并回调结果
json结构
整个动画是由一层一层图层组成的
{
"v": "4.6.0", //bodymovin的版本,AE上的插件,生成Json文件
"fr": 29.9700012207031, //帧率
"ip": 0, //起始关键帧
"op": 141.000005743048, //结束关键帧
"w": 800, //动画宽度
"h": 800, //动画高度
"ddd": 0,
"assets": [...] //资源信息
"layers": [...] //图层信息
}
setAnimation做了什么
当调用setAnimation的方法时,目的是将Json文件转换成LottieComposition,在将LottieComposition设置进LottieDrawable,然后调用playAnimation()时就开始动画。
以setAniamtion(String assetName)为例,其他也类似
LottieAnimationView,看到fromAsset方法,继续跟下去
public void setAnimation(final String assetName) {
this.animationName = assetName;
animationResId = 0;
//1. 获取LottieTask, LottieTask里封装了线程池和FutureTask,做解析并返回
setCompositionTask(LottieCompositionFactory.fromAsset(getContext(), assetName));
}
LottieCompositionFactory 看见cache方法,在这里处理创建和缓存LottieComposition, 如果没有缓存走2处,有缓存走3处返回
//用于保存LottieTask,
private static final Map<String, LottieTask<LottieComposition>> taskCache = new HashMap<>();
public static LottieTask<LottieComposition> fromAsset(Context context, final String fileName) {
final Context appContext = context.getApplicationContext();
//LottieCompositionFactory生成LottieTask或从缓存中取出
//asset文件会以filename去做缓存的key,raw资源下的会以raw_resId, url会以url
return cache(fileName, new Callable<LottieResult<LottieComposition>>() {
@Override
public LottieResult<LottieComposition> call() {
// 2.没有缓存
return fromAssetSync(appContext, fileName);
}
});
}
private static LottieTask<LottieComposition> cache(
@Nullable final String cacheKey, Callable<LottieResult<LottieComposition>> callable) {
//LottieCompositionCache是LruCache,这里先判断有没有缓存过LottieComposition,有的话就直接返回
final LottieComposition cachedComposition = LottieCompositionCache.getInstance().get(cacheKey);
if (cachedComposition != null) {
return new LottieTask<>(new Callable<LottieResult<LottieComposition>>() {
@Override
public LottieResult<LottieComposition> call() {
// 3.返回缓存
return new LottieResult<>(cachedComposition);
}
});
}
//如果当前已经有执行的task直接返回
if (taskCache.containsKey(cacheKey)) {
return taskCache.get(cacheKey);
}
//callable 在上方2处哪里处理
LottieTask<LottieComposition> task = new LottieTask<>(callable);
task.addListener(new LottieListener<LottieComposition>() {
@Override
public void onResult(LottieComposition result) {
//执行成功保存LottieComposition
if (cacheKey != null) {
LottieCompositionCache.getInstance().put(cacheKey, result);
}
taskCache.remove(cacheKey);
}
});
task.addFailureListener(new LottieListener<Throwable>() {
@Override
public void onResult(Throwable result) {
taskCache.remove(cacheKey);
}
});
//添加task
taskCache.put(cacheKey, task);
return task;
}
当LottieCompositionFactory处理完回到LottieAnimationView,使用获取到LottieTask对象添加监听并等待回调。
//在看回1处 获取到LottieTask
private void setCompositionTask(LottieTask<LottieComposition> compositionTask) {
clearComposition();
cancelLoaderTask();
//添加监听 等待结果回调
this.compositionTask = compositionTask
//4 添加监听
.addListener(loadedListener)
.addFailureListener(failureListener);
}
private final LottieListener<LottieComposition> loadedListener = new LottieListener<LottieComposition>() {
@Override public void onResult(LottieComposition composition) {
//获取到结果
setComposition(composition);
}
};
public void setComposition(@NonNull LottieComposition composition) {
this.composition = composition;
//设置进LottieDrawable
boolean isNewComposition = lottieDrawable.setComposition(composition);
if (getDrawable() == lottieDrawable && !isNewComposition) {
return;
}
setImageDrawable(null);
//注意:这里面会调用bitmap.recycle(),将以前的bitmap回收; 所以注意bitmap重用时会报已回收的异常
setImageDrawable(lottieDrawable);
requestLayout();
//加载成功的回调在这里触发
for (LottieOnCompositionLoadedListener lottieOnCompositionLoadedListener : lottieOnCompositionLoadedListeners) {
lottieOnCompositionLoadedListener.onCompositionLoaded(composition);
}
//至此完成整理加载和缓存的逻辑
}
LottieTask主要负责异步处理任务并返回,主要在startTaskObserverIfNeeded方法里监听task是否完成
//public static修饰的,可换成项目里的
public static Executor EXECUTOR = Executors.newCachedThreadPool();
//有回调的任务
private final FutureTask<LottieResult<T>> task;
//添加监听的方法
public synchronized LottieTask<T> addListener(LottieListener<T> listener) {
if (result != null && result.getValue() != null) {
listener.onResult(result.getValue());
}
successListeners.add(listener);
//里面是开启线程,里面有一个while循环,等待结果回调
startTaskObserverIfNeeded();
return this;
}
播放动画
点进LottieAnimationView的playAnimation方法,发现实际调用的是LottieDrawable的playAnimation
public LottieDrawable() {
private final LottieValueAnimator animator = new LottieValueAnimator();
public LottieDrawable() {
//先记住这个监听
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override public void onAnimationUpdate(ValueAnimator animation) {
if (compositionLayer != null) {
//通过compositionLayer.setProgress通知到每一个图层去变化实现动画效果
compositionLayer.setProgress(animator.getAnimatedValueAbsolute());
}
}
});
}
public void playAnimation() {
if (compositionLayer == null) {
lazyCompositionTasks.add(new LazyCompositionTask() {
@Override public void run(LottieComposition composition) {
playAnimation();
}
});
return;
}
animator.playAnimation();
}
}
现在来看LottieValueAnimator,是通过实现Choreographer.getInstance().postFrameCallback(this)注册下一帧去进行动画和回调监听的,主要处理逻辑看doFrame()
public class LottieValueAnimator extends BaseLottieAnimator implements Choreographer.FrameCallback {
@MainThread
public void playAnimation() {
running = true;
notifyStart(isReversed());
setFrame((int) (isReversed() ? getMaxFrame() : getMinFrame()));
lastFrameTimeNs = System.nanoTime();
repeatCount = 0;
//注册下一帧去开始动画
postFrameCallback();
}
@Override
public void doFrame(long frameTimeNanos) {
//注册下一帧回调
postFrameCallback();
...
//回调在LottieDrawable注册的监听(animator.addUpdateListener)
//就是这里去更新动画进度
notifyUpdate();
if (ended) {
if (getRepeatCount() != INFINITE && repeatCount >= getRepeatCount()) {
frame = getMaxFrame();
removeFrameCallback();
//回调AnimatorListener.onAnimationEnd
notifyEnd(isReversed());
} else {
//回调AnimatorListener.onAnimationRepeat
notifyRepeat();
repeatCount++;
if (getRepeatMode() == REVERSE) {
speedReversedForRepeatMode = !speedReversedForRepeatMode;
reverseAnimationSpeed();
} else {
frame = isReversed() ? getMaxFrame() : getMinFrame();
}
lastFrameTimeNs = now;
}
}
verifyFrame();
}
}
缓存
主要有两处,都是在LottieCompositionFactory里使用
-
LottieCompositionCache类,使用LruCache缓存LottieComposition,最多10MB
-
当调用setAnimationFromUrl时,LottieCompositionFactory实际是NetworkFetcher去做网络访问获取,使用NetworkCache保存到本地
最后
至于Lottie怎么根据进度做绘制和变化这块没有细看,有兴趣的同学可以去研究研究
写得不是很好,完全按照自己的思路写的,如果发现哪里有问题,欢迎指正和讨论
附上简单的使用Demo:LottieDemo