从json文件到炫酷动画-Lottie实现思路和源码分析

y6314360 7年前
   <p>Lottie是最近Airbnb开源的动画项目,支持Android、iOS、ReactNaitve三个平台,本文分析主要Lottie把json文件转为动画的思路和源码实现。</p>    <p>文章首先介绍Lottie的基本使用,然后分析把json文件映射到动画的实现思路,最后分析Lottie的源码实现,这里分析的是Lottie-Android。</p>    <h2>基本用法</h2>    <p>与使用相关的只有三个类文件: LottieAnimationView、LottieComposition、LottieDrawable ,所以Lottie使用起来特别简单(需要注意Lottie支持API16及以上)。</p>    <p>最简单的使用方式是在xml中增加LottieAnimationView:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/abcbb48c2b3b4ccbb0136ce5f7a600d2.png"></p>    <p>"Logo/LogoSmall.json"是需要加载的动画数据路径,根目录是assets目录。</p>    <p>也可以通过代码设置动画数据json路径:</p>    <p><img src="https://simg.open-open.com/show/d213c423671ad07c1551fe8fa02436b0.png"></p>    <p>然后在代码中控制动画播放或者添加监听事件:</p>    <p><img src="https://simg.open-open.com/show/b2d74c28ce0c054d5d21c8199f44907c.png"></p>    <p>Lottie提供了LottieDrawable可以使用:</p>    <p><img src="https://simg.open-open.com/show/d6fe57920fff517897a6633719322710.png"></p>    <p>可以看到Lottie使用起来非常简单,我们之后就从以上用到的 LottieAnimationView、LottieComposition、LottieDrawable 入手来分析下Lottie动画的实现原理。</p>    <h2>思路分析</h2>    <p>我们先从底层思考下如何在屏幕上绘制动画,最简单的方式是把动画分为多张图片,然后通过周期替换屏幕上绘制的图片来形成动画,这种暴力的方式非常简单,但缺点明显,很耗内存,动画播放中前后两张替换的图片在很多元素并没有变化,重复的内容浪费了空间。</p>    <p>为了提高空间利用率,可以把图片中的元素进行拆分,使用过photoshop的同学知道,其实在处理一张图片时,可以把一张复杂的图片使用多个图层来表示,每个图层上展示一部分内容,图层中的内容也可以拆分为多个元素。拆分元素之后,根据动画需求,可以单独对图层,甚至图层中的元素设置平移、旋转、收缩等动画。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/47e1d2e931a8b46117ccbe18fc8206ee.png"></p>    <p>Lottie使用json文件来作为动画数据源,json文件是通过 <a href="/misc/goto?guid=4958979788551992094" rel="nofollow,noindex">Bodymovin</a> 插件导出的,查看sample中给出的json文件,其实就是把图片中的元素进行来拆分,并且描述每个元素的动画执行路径和执行时间。Lottie的功能就是读取这些数据,然后绘制到屏幕上。</p>    <p>现在思考如果我们拿到一份json格式动画如何展示到屏幕上。首先要解析json,建立数据到对象的映射,然后根据数据对象创建合适的Drawable绘制到View上,动画的实现可以通过操作读取到的元素完成。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/23d5484ba6304b7473e55aebfde43c85.png"></p>    <h2>源码分析</h2>    <h3>1. json文件到对象的映射</h3>    <p>Lottie使用 LottieComposition 来作为After Effects的数据对象,即把json文件映射到 LottieComposition , LottieComposition 中提供了解析json的静态方法:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/50e6b7de62434af061fbbc40df626955.png"></p>    <p>我们看下 LottieComposition 都有哪些成员变量,这些成员变量描述了After Effects中的动画。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/a12186e834675096c1cf7e58132211b5.png"></p>    <p>可以看到startFrame、endFrame、duration、scale等都是动画中常见的。我们看下 List<Layer> ,看名字就是映射拆分后的图层数据:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/d5777a65ba3a99214c7071b57d1dcdad.png"></p>    <p>Layer 中完成layer的json数据解析:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/74420097bafe1aaacaeede68cd2ab0a4.png"></p>    <h3>2. 数据对象到Drawable的映射</h3>    <p>AnimatableLayer 继承自 Drawable ,我们看下它的子类:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/13f9301d9f2059926af1f2ec6a4d04f5.png"></p>    <p>其中 LayerView 对应着 Layer 数据, Layer 中有</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/90ab68d8086e8ec68859f776a256ddd2.png"></p>    <p>对应的 LayerView 中有</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/45b53c0605cbd8726abec449577ff59e.png"></p>    <p>可以简单地理解为ViewGroup中可以包含ViewGroup或者View,但其实整个Lottie实现的动画都是绘制在一个View LottieAnimationView 上。</p>    <p>AnimatableLayer 的其它子类如 ShapeLayer,RectLayouer 等作为 LayerView 中 List<AnimatableLayer> 的元素。</p>    <h2>3. 绘制</h2>    <p>LottieAnimationView 继承自 AppCompatImageView ,封装了一些动画的操作,如:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/00fa447fcd12dddf9364097feab72e47.png"></p>    <p>具体的绘制时委托为 LottieDrawable 完成的,我们看下 LottieDrawable 中的 draw() 方法:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/f89162f48eb9880436712d13e1dfc532.png"></p>    <p>LottieDrawable 继承自 AnimatableLayer ,其 draw() 方法如下:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/17410412e90e756e01ae808a0a184dc5.png"></p>    <p>可以看到先绘制了本层的内容,然后开始绘制包含的 layers 的内容:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/5b66693421f4f9bcf7a89abd52ba9259.png"></p>    <p>这个过程于界面中ViewGroup嵌套绘制类似。</p>    <h2>实现分析</h2>    <p>上面我们根据动画绘制的思路分析了下Lottie实现机制,下面从正面来捋一下程序的执行过程:</p>    <ol>     <li>创建 LottieAnimationView lottieAnimationView</li>     <li>创建 LottieDrawable lottieDrawable</li>     <li>使用 LottieComposition 中的静态方法解析json文件创建 LottieComposition lottieComposition ,这个过程中已经创建来多个 Layer 对象。</li>     <li>lottieDrawable.setComposition(lottieComposition)</li>    </ol>    <p><img src="https://simg.open-open.com/show/cd5ba19fad9fdb7f93d901c4ee48ca5f.png"></p>    <p>先清理之前的数据,然后开始 buildLayersForComposition ,即根据 lottieComposition 建立多个 layerView ,此时已经创建好了多个Drawable,并通过List建立的为以 lottieDrawable 为根的一个drawable树。</p>    <pre>  <code class="language-java">lottieAnimationView.setImageDrawable(lottieDrawable)  lottieAnimationView.playAnimation()  </code></pre>    <p style="text-align:center"><img src="https://simg.open-open.com/show/82677692dfbcd7d671faddff85279188.png"></p>    <p>直接委托给了 lottieDrawable , lottieDrawable 中有 private final ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/68a2891cac47902b751698c36f5d9b39.png"></p>    <p>重点看下 setProgress 方法</p>    <p><img src="https://simg.open-open.com/show/b84f397d0ea34122b9bec787f6229e72.png"></p>    <p>调用了 private final List<KeyframeAnimation<?>> animations = new ArrayList<>() 的 setProgress :</p>    <p><img src="https://simg.open-open.com/show/82f0dbe33bf7d80bb3212b2e5a39348f.png"></p>    <p>在 onValueChanged 时,各个创建好的Drawable会根据需求进行重绘,达到动画的效果。</p>    <p>Lottie把动画从View的动效转移到了Drawable上。</p>    <h2>Lottie的性能</h2>    <p>可以看到Lottie把json描述的动画数据映射到Drawable之后,实现动画时用到了 ValueAnimator ,在动画更新时使用Drawable而非View,个人感觉在不需要交互时Drawable显然比View更加轻量。以下是Lottie性能的官方的说明:</p>    <ol>     <li>如果没有mask和mattes,那么性能和内存非常好,没有bitmap创建,大部分操作都是简单的cavas绘制。</li>     <li>如果存在mattes,将会创建2~3个bitmap。bitmap在动画加载到window时被创建,被window删除时回收。所以不宜在RecyclerView中使用包涵mattes或者mask的动画,否则会引起bitmap抖动。除了内存抖动,mattes和mask中必要的bitmap.eraseColor()和canvas.drawBitmap()也会降低动画性能。对于简单的动画,在实际使用时性能不太明显。</li>     <li>如果在列表中使用动画,推荐使用缓存LottieAnimationView.setAnimation(String, CacheStrategy) 。</li>    </ol>    <p> </p>    <p> </p>    <p> </p>    <p>来自:http://www.jianshu.com/p/81be1bf9600c</p>    <p> </p>