Lottie的使用及原理浅析
来源:互联网 发布:linux mv移动多个文件 编辑:程序博客网 时间:2024/05/21 09:49
lottie
项目地址: https://github.com/xsfelvis/lottie-android
Lottie支持Jellybean (API 16)及以上的系统
什么是lottie?
Airbnb最近开源了一个名叫Lottie的动画库,它能够同时支持iOS,Android与ReactNative的开发,使用流程如下图所示
如图所示,通过安装AE上的bodymovin的插件,能够将AE中的动画工程文件转换为通用的json格式描述文件(bodymovin插件本身是用于网页上呈现各种AE效果的一个开源库),lottie所做的事情就是实现在不同移动端平台上呈现AE动画的方式,从而达到动画文件的一次绘制、一次转换,随处可用的效果,这个跟Java一次编译随处运行效果一样
很酷炫有木有!
使用准备
在使用这么酷炫的项目前,需要做一下准备
以windows为例,使用到的工具在云盘中
http://pan.baidu.com/s/1c19FLdA
- 下载AE 安装(AE2007)
`
安装破解:
Adobe After Effects CC 2017 安装后不要运行,直接使用 adobe.snr.patch.v2.0-painter.exe 选择产品破解即可
中文语言更改:安装后默认是英文界面,找到安装目录(默认是:C:\Program Files\Adobe\Adobe After Effects CC 2017\Support Files\AMT)在 AMT 文件夹内找到 application.xml 文件使用文本编辑器打开并修改底部一行:<Data key=”installedLanguages”>en_GB</Data> 为 <Data key=”installedLanguages”>zh_CN</Data> 保存。
`
- 安装bodymovin插件
该项目地址在https://github.com/bodymovin/bodymovin
安装这个插件有几种方式,采用安装 zxp installer安装bodymovin.zxp(获取这个文件:下载上面的zip文件,在build/extension目录下,这里已经下载好了)方式,
安装云盘中的aescript+aeplugins zxp installer.exe,然后安装bodymovin即可
此时打开AE 在window/extension 文件夹下可以看到bodymovin插件说明就ok了,
- 开启允许脚本写入文件和访问网络
都需要在AE的编辑->首选项->常规中勾选允许脚本写入文件和访问网络(默认不开启)
制作Json
从这里可以找到一些Lottie中演示过的动画的AE源文件,下载到本地后在AE中打开即可(或者去https://material.uplabs.com 选择 Download 选择 -> view all -> 在tools这一栏里面选择 After effects 然后选择一个免费的项目下载下来 用ae打开。 ).这里我们选用EmptyState.aep这个实例工程,稍作修改:
然后使用bodymovin插件导出aep文件对应的数据json(点击Render)
你也可以通过http://svgsprite.com/demo/bm/player.php?render=canvas&bg=fff去浏览你制作的json文件动画
使用Lottie库播放动画
Lottie的引入与使用就如其他库一样,这里以Android平台的使用为例.
在项目的build.gradle文件中加入:
dependencies { compile 'com.airbnb.android:lottie:1.0.1' }
布局文件中添加
Lottie支持Jellybean (API 16)及以上的系统,最简单的使用方式是直接在布局文件中添加:
<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" />
代码中添加
LottieAnimationView animationView = (LottieAnimationView) findViewById(R.id.animation_view); animationView.setAnimation("hello-world.json"); animationView.loop(true);
这方法将在后台线程异步加载数据文件,并在加载完之后开始渲染显示动画
或者从网络上加载jsonObject
LottieAnimationView animationView = (LottieAnimationView) findViewById(R.id.animation_view); ... LottieComposition composition = LottieComposition.fromJson(getResources(), jsonObject, (composition) -> { animationView.setComposition(composition); animationView.playAnimation(); });
你也可以通过API控制动画,并且设置一些监听
animationView.addAnimatorUpdateListener((animation) -> { // Do something. }); animationView.playAnimation(); ... if (animationView.isAnimating()) { // Do something. } ... animationView.setProgress(0.5f); ... // 自定义速度与时长 ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f) .setDuration(500); animator.addUpdateListener(animation -> { animationView.setProgress(animation.getAnimatedValue()); }); animator.start(); ... animationView.cancelAnimation();
在使用遮罩的情况下,LottieAnimationView 使用 LottieDrawable来渲染动画.如果需要的话,你可以直接使用drawable形式:
LottieDrawable drawable = new LottieDrawable(); LottieComposition.fromAssetFileName(getContext(), "hello-world.json", (composition) -> { drawable.setComposition(composition); });
如果你需要频繁使用某一个动画,可以使用LottieAnimationView内置的一个缓存策略:
LottieAnimationView.setAnimation(String, CacheStrategy)
其中CacheStrategy的值可以是Strong,Weak或者None,它们用来决定LottieAnimationView对已经加载并转换好的动画持有怎样形式的引用(强引用/弱引用).
使用小结
关于方法数
使用ClassShark分析官方demo,lottie库其实只占用了783
个方法数,还是比较少的
关于性能
官方说法
- 如果没有mask和mattes,那么性能和内存非常好,没有bitmap创建,大部分操作都是简单的cavas绘制。
- 如果存在mattes,将会创建2~3个bitmap。bitmap在动画加载到window时被创建,被window删除时回收。所以不宜在RecyclerView中使用包涵mattes或者mask的动画,否则会引起bitmap抖动。除了内存抖动,mattes和mask中必要的bitmap.eraseColor()和canvas.drawBitmap()也会降低动画性能。对于简单的动画,在实际使用时性能不太明显。
- 如果在列表中使用动画,推荐使用缓存LottieAnimationView.setAnimation(String, CacheStrategy) 。
原理
Lottie使用json文件来作为动画数据源,json文件是通过Bodymovin插件导出的,查看sample中给出的json文件,其实就是把图片中的元素进行来拆分,并且描述每个元素的动画执行路径和执行时间。Lottie的功能就是读取这些数据,然后绘制到屏幕上。
首先要解析json,建立数据到对象的映射,然后根据数据对象创建合适的Drawable绘制到view上,动画的实现可以通过操作读取到的元素完成。
具体过程如下所示
json文件——>Component——>Drawable——>View
通过如下3个核心类来来完成整个工作流程,因而使用起来比较简单
- LottieComposition(json->数据对象)
Lottie使用LottieComposition
来作为After Effects的数据对象,即把Json文件映射为到LottieComposition
,该类中提供了解析json的静态方法
- LottieDrawable(数据对象->Drawable)
绘制
- LottieAnimationView(绘制)
操作集合,LottieAnimationView 继承自 AppCompatImageView,封装了一些动画的操作,具体的绘制时委托为 LottieDrawable 完成的
LottieComposition(json->数据对象)
定义两个接口
//定义两个通用接口 public interface OnCompositionLoadedListener { void onCompositionLoaded(LottieComposition composition); } interface Cancellable { void cancel(); }
看下这个类的关键函数调用情况
简单介绍下,通过提供
- fromAssetFileName(资源file)
- fromFileSync(异步文件,通常是网络数据)
- fromJson(直接的json)
通过这三个入口接收json文件、json流,然后异步都通过AsynTask来异步处理,最终核心处理都是在fromJsonSync
中进行json数据的解析
主要分为以下层数据
width = json.getInt(“w”);
height = json.getInt(“h”);
将根据这两个得到一块矩形区域
composition.bounds = new Rect(0, 0, scaledWidth, scaledHeight);
composition.startFrame = json.getLong(“ip”);
composition.endFrame = json.getLong(“op”);
composition.frameRate = json.getInt(“fr”);
将根据这几个得到,动画帧持续的时间
long frameDuration = composition.endFrame - composition.startFrame; composition.duration = (long) (frameDuration / (float) composition.frameRate * 1000);
JSONArray jsonLayers = json.getJSONArray(“layers”);
其中Layers是包含了几层的参数,层级非常丰富,具体可以查看Layer类中的json解析(该类中除了解析参数之外还实现了属性动画一些基本类,核心方法是 Layer fromJson)
这些数据解析完后,都被存在如下变量中,用以描述After Effects中的动画
private final LongSparseArray<Layer> layerMap = new LongSparseArray<>(); private final List<Layer> layers = new ArrayList<>(); private Rect bounds; private long startFrame; private long endFrame; private int frameRate; private long duration; private boolean hasMasks; private boolean hasMattes; private float scale;
从数据变量来看,startFrame、endFrame、duration、scale等都是动画中常见的参数,List layers为映射拆分后的图层数据
LottieDrawable(数据对象->Drawable)
先看下继承关系
LottieDrawable
extends AnimatableLayer
extends Drawable
AnimatableLayer
首先看下AnimatableLayer
继承了Drawable
主要重写了draw,在代码中可以看出,借用canvas的save
、restoreToCount
来实现像PS那种图层叠加的效果
@Override public void draw(@NonNull Canvas canvas) { int saveCount = canvas.save(); applyTransformForLayer(canvas, this); int backgroundAlpha = Color.alpha(backgroundColor); if (backgroundAlpha != 0) { int alpha = backgroundAlpha; if (this.alpha != null) { alpha = alpha * this.alpha.getValue() / 255; } solidBackgroundPaint.setAlpha(alpha); if (alpha > 0) { canvas.drawRect(getBounds(), solidBackgroundPaint); } } for (int i = 0; i < layers.size(); i++) { layers.get(i).draw(canvas); } canvas.restoreToCount(saveCount); }
特别是for循环那段,更是体现了将之前json解析的元素图层一层层的画出来,如同PS一般
好了,AnimatableLayer
如其名一样负责将将之前的Layer层动画显示出来,下面再来看LottieDrawable
LottieDrawable
这个类的核心方法
void setComposition(LottieComposition composition) { if (getCallback() == null) { throw new IllegalStateException( "You or your view must set a Drawable.Callback before setting the composition. This " + "gets done automatically when added to an ImageView. " + "Either call ImageView.setImageDrawable() before setComposition() or call " + "setCallback(yourView.getCallback()) first."); } //清除之前的数据 clearComposition(); this.composition = composition; animator.setDuration(composition.getDuration()); setBounds(0, 0, composition.getBounds().width(), composition.getBounds().height()); //核心函数:即根据lottieComposition建立多个layerView,此时已经创建好了多个Drawable,并通过List建立的为以lottieDrawable为根的一个drawable树。 buildLayersForComposition(composition); getCallback().invalidateDrawable(this); }
该方法在LottieAnimationView
中调用,该方法中实际调用的核心函数是
void buildLayersForComposition(LottieComposition composition)
这个函数的重点做的是为AnimatableLayer创建关键信息:
将得到的
bitmap
(mainBitmap, maskBitmap, matteBitmap)+layer
(通过之前setComposition获得的)信息合成LayerView
将LayerView通过super.addLayer(AnimatableLayer#layers),在AnimatableLayer#layers中去draw
详见AnimatableLayer#draw方法
//这里的layer来自于LottieDrawabe调用super.addLayers for (int i = 0; i < layers.size(); i++) { layers.get(i).draw(canvas); }
绘制动画的载体LottieAnimationView
这个view将加载、转换、和显示由AE动画插件bodymovin导出的json文件,并且支持设置动画的进度。,其实LottieAnimationView仅仅是个载体,通过管理
- 数据源(LottieComposition)
- 动画执行者(LottieDrawable实际上是AnimatableLayer!,LottieDrawable继承自AnimatableLayer)
LottieAnimationView
继承了AppCompatImageView
,并且封装了一些动画操作,如入口和进度控制,开启、取消、暂停动画
通过重载 setAnimation函数间接调用之前LottieComposition解析文件的三种方式
- 入口
void setAnimation(final JSONObject json)——>LottieComposition.fromJson(getResources(), json, loadedListener)
void setAnimation(final String animationName, final CacheStrategy cacheStrategy)——LottieComposition.fromAssetFileName(getContext(), animationName,new LottieComposition.OnCompositionLoadedListener()
可以看出动画的设置是通过LottieComposition
来代理的
- 进度控制
setProgress(@FloatRange(from = 0f, to = 1f) float progress)——>lottieDrawable.setProgress(progress)
- 开启动画
playAnimation()——>lottieDrawable.playAnimation();
- 取消动画
cancelAnimation()——>lottieDrawable.cancelAnimation();
- 暂停动画
pauseAnimation()——>lottieDrawable.cancelAnimation(); setProgress(progress);
因此可以看出动画的控制是通过lottieDrawable
来代理进行的。
下面将详细的总结下LottieAnimationView
如何工作的
- 创建
LottieAnimationView lottieAnimationView
、LottieDrawable lottieDrawable
- 提供入口setAnimation,实际中是通过
LottieComposition
中的静态方法解析json文件创建LottieComposition lottieComposition,并且这个过程中已经解析出多个Layer对象。
这里要重点提一下 lottieDrawable.setComposition(lottieComposition)
,因为每个setAnimation都会去调用setComposition(@NonNull LottieComposition composition)
里面会调用前面的方法。前面部分已经详述
- 启动动画、取消动画、暂停动画
直接委托给了lottieDrawable,lottieDrawable中有private final ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
在lottieDrawable中有private final ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);,并且在构造函数中初始化
LottieDrawable() { super(null); animator.setRepeatCount(0); animator.setInterpolator(new LinearInterpolator()); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { if (systemAnimationsAreDisabled) { animator.cancel(); setProgress(1f); } else { setProgress(animation.getAnimatedFraction()); } } }); }
- 进度控制setProgress方法
还是在lottieDrawable中实现,
public void setProgress(@FloatRange(from = 0f, to = 1f) float progress) { this.progress = progress; for (int i = 0; i < animations.size(); i++) { animations.get(i).setProgress(progress); } for (int i = 0; i < layers.size(); i++) { layers.get(i).setProgress(progress); } }
最终还是调用了private final List
void setProgress(@FloatRange(from = 0f, to = 1f) float progress) { if (progress < getStartDelayProgress()) { progress = 0f; } else if (progress > getDurationEndProgress()) { progress = 1f; } else { progress = (progress - getStartDelayProgress()) / getDurationRangeProgress(); } if (progress == this.progress) { return; } this.progress = progress; T value = getValue(); for (int i = 0; i < listeners.size(); i++) { //在onValueChanged时,各个创建好的Drawable会根据需求进行重绘,达到动画的效果。 listeners.get(i).onValueChanged(value); } }
参考
- https://github.com/xsfelvis/lottie-android
- http://www.jianshu.com/p/81be1bf9600c
- Lottie的使用及原理浅析
- Lottie的使用
- Lottie的使用
- Dubbo的使用及原理浅析.
- Dubbo的使用及原理浅析.
- Dubbo的使用及原理浅析.
- Dubbo的使用及原理浅析.
- Android Lottie动画的简单使用
- Lottie动画库的使用 & 源码解析
- 使用lottie遇到的两个问题
- 浅析COM的思想及原理
- 浅析COM的思想及原理
- 浅析COM的思想及原理
- 浅析COM的思想及原理
- 浅析搜索引擎的原理及发展前景
- 浅析COM的思想及原理
- 浅析COM的思想及原理
- 浅析COM的思想及原理
- 丑数
- python对象类型之概述
- Dubbo与Zookeeper、SpringMVC整合和使用(负载均衡、容错)(转)
- Android之Animation动画各属性的参数意思
- 在eclipse上配置有关SVN的忽略
- Lottie的使用及原理浅析
- 170214
- 图的基本存储的基本方式一
- java--09--对象与JSON与Map之间的转换
- 静态广播与动态广播的区别
- js中扩充类型的功能
- shiro的使用2 灵活使用shiro的密码服务模块
- PAT:A1095. Cars on Campus (0/30)
- 伪代码编程过程