这样做动画交互,一点都不费力!

qcloudcommunity · · 1303 次点击 · 开始浏览    置顶
这是一个创建于 的主题,其中的信息可能已经有所发展或是发生改变。

> 本文由云+社区发表 > 作者:paulzeng > > > > **导语:**Lottie是Airbnb开源的一个面向 iOS、Android、React Native 的动画库,可实现非常复杂的动画,使用也及其简单,极大释放人力,值得一试。 ## 一、简介 Lottie 是Airbnb开源的一个面向 iOS、Android、React Native 的动画库,能分析 Adobe After Effects 导出的动画,并且能让原生 App 像使用静态素材一样使用这些动画,完美实现动画效果。 现在使用各平台的 native 代码实现一套复杂的动画是一件很困难并且耗时的事,我们需要为不同尺寸的屏幕加载不同的素材资源,还需要写大量难维护的代码,而Lottie可以做到同一个动画文件在不同平台上实现相同的效果,极大减少开发时间,实现不同的动画,只需要设置不同的动画文件即可,极大减少开发和维护成本。 官方效果图: ![img](https://blog-10039692.file.myqcloud.com/1504855429015_184_1504855429322.gif) ![img](https://blog-10039692.file.myqcloud.com/1504855447071_5029_1504855448938.gif) ## 二、如何使用 Lottie支持多平台,使用同一个JSON动画文件,可在不同平台实现相同的效果。 Android 通过Airbnb的开源项目[lottie-android](https://github.com/airbnb/lottie-android)实现,最低支持 API 16; IOS 通过Airbnb的开源项目[lottie-ios](https://github.com/airbnb/lottie-ios)实现,最低支持 IOS 7; React Native,通过Airbnb的开源项目[lottie-react-native](https://github.com/airbnb/lottie-react-native)实现; ![img](https://blog-10039692.file.myqcloud.com/1504855556048_5141_1504855558014.gif) 这是React logo的动画,以下以Android平台为例如何使用Lottie ### 1.下载Lottie 在项目的 build.gradle 文件添加依赖 ```js dependencies { compile 'com.airbnb.android:lottie:2.1.0' } ``` ### 2.添加 Adobe After Effects 导出的动画文件 Lottie默认读取Assets中的文件,我们需要把动画文件react.json 保存在app/src/main/assets文件里。(文件比较大,只展示了部分内容,[文件链接](https://github.com/airbnb/lottie-android/blob/master/LottieSample/src/main/assets/lottiefiles.com%20-%20React.json)) ```js { "v": "4.6.0", "fr": 29.9700012207031, "ip": 0, "op": 141.000005743048, "w": 800, "h": 800, "ddd": 0, "assets": [ ], "layers": [ { "ddd": 0, "ind": 0, "ty": 4, "nm": "center_circle", "ks": {...}, "ao": 0, "shapes": [...], "ip": 0, "op": 900.000036657751, "st": 0, "bm": 0, "sr": 1 }, {...}, {...}, {...} ] } ``` ## 3.使用Lottie 在布局文件中直接添加Lottie的LottieAnimationView控件,即可在界面显示React logo动画效果 ```js <com.airbnb.lottie.LottieAnimationView android:id="@+id/animation_view" android:layout_width="wrap_content" android:layout_height="wrap_content" app:lottie_fileName="react.json" app:lottie_loop="true" app:lottie_autoPlay="true" /> ``` ## 4.引入Lottie影响 ### (1)兼容性 Lottie 最低支持版本API 16,低版本系统需要做降级动画或者不展示动画 ### (2)安装包 | 影响项 | 使用前 | 使用后 | 结论 | | ---------- | ------- | ------- | -------------- | | 方法数 | 144807 | 145891 | 增加1084个方法 | | 安装包大小 | 41969KB | 42037KB | 增大68KB | 这是用全民K歌release包的测试数据,lottie本身方法数不小,有方法数超标和安装包过大的风险,业务可自行评估 注:LottieAnimationView继承于V7的AppCompatImageView,需要引入V7兼容包,根据业务需要,可以源码引入Lottie,让LottieAnimationView继承与ImageView,就不用引入V7兼容包,可减小安装大小。 ## 三、使用小技巧 ### 1.加载SDCard动画文件 ```js StringBuilder stringBuilder = new StringBuilder(); BufferedReader bufferedReader = new BufferedReader(new FileReader(new File(JSON_PATH + "react.json"))); String content = null; while ((content = bufferedReader.readLine()) != null){ stringBuilder.append(content); } JSONObject jsonObject = new JSONObject(stringBuilder.toString()); animationView.setAnimation(jsonObject); animationView.loop(true); animationView.playAnimation(); ``` ### 2.加载SDCard图片 ```js animationView.setImageAssetDelegate(new ImageAssetDelegate() { @Override public Bitmap fetchBitmap(LottieImageAsset asset) { try { FileInputStream fileInputStream = new FileInputStream(IMAGE_PATH + asset.getFileName()); return BitmapFactory.decodeStream(fileInputStream); ///把流转化为Bitmap图片 } catch (Exception e) { Log.e(TAG, "", e); } return null; } }); ``` ### 3.加载SDCard字体 ```js animationView.setFontAssetDelegate(new FontAssetDelegate(){ public Typeface fetchFont(String fontFamily) { Typeface customFont = Typeface.createFromFile(FONT_PATH + fontFamily); return customFont; } }); ``` ### 4.缓存动画 ```js /* * Lottie内部有两个缓存map(强引用缓存,弱引用缓存),在动画文件加载完成后会根据设置的缓存策略缓存动画,方便下次使用。 */ animationView.setAnimation(animation, LottieAnimationView.CacheStrategy.Strong); //强缓存 animationView.setAnimation(animation, LottieAnimationView.CacheStrategy.Weak); //弱缓存 ``` ## 四、Lottie实现原理 ![img](https://blog-10039692.file.myqcloud.com/1504855744201_1024_1504855744511.png) 设计师把一张复杂的图片使用多个图层来表示,每个图层展示一部分内容,图层中的内容也可以拆分为多个元素。拆分元素之后,根据动画需求,可以单独对图层或者图层中的元素做平移、旋转、收缩等动画。 Lottie的使用的资源是需要先通过[bodymovin](https://github.com/bodymovin/bodymovin)( bodymovin 插件本身是用于网页上呈现各种AE效果的一个开源库)将 Adobe After Effects (AE)生成的aep动画工程文件转换为通用的json格式描述文件。Lottie则负责解析动画的数据,计算每个动画在某个时间点的状态,准确地绘制到屏幕上。 导出的json动画描述文件: ```js { "v": "4.6.0", "fr": 29.9700012207031, "ip": 0, "op": 141.000005743048, "w": 800, "h": 800, "ddd": 0, "assets": [ ], "layers": [ {...}, ] } ``` Lottie主要类图: ![img](https://blog-10039692.file.myqcloud.com/1504855776525_4203_1504855776749.png) 图:lottie_class Lottie对外通过控件LottieAnimationView暴露接口,控制动画。 LottieAnimationView继承自ImageView,通过当前时间绘制canvas显示到界面上。这里有两个关键类:LottieComposition 负责解析json描述文件,把json内容转成Java数据对象;LottieDrawable负责绘制,把LottieComposition转成的数据对象绘制成drawable显示到View上。顺序如下: ![img](https://blog-10039692.file.myqcloud.com/1504855810238_4104_1504855810230.jpg) ### 1.json文件解析 LottieComposition负责解析json文件,建立数据到java对象的映射关系。 **(1)解析json外部结构** LottieComposition封装整个动画的信息,包括动画大小,动画时长,帧率,用到的图片,字体,图层等等。 json外部结构 ```js { "v": "4.6.0", //bodymovin的版本 "fr": 29.9700012207031, //帧率 "ip": 0, //起始关键帧 "op": 141.000005743048, //结束关键帧 "w": 800, //动画宽度 "h": 800, //动画高度 "ddd": 0, "assets": [...] //资源信息 "layers": [...] //图层信息 } //解析json的源码 static LottieComposition fromJsonSync(Resources res, JSONObject json) { Rect bounds = null; float scale = res.getDisplayMetrics().density; int width = json.optInt("w", -1); int height = json.optInt("h", -1); if (width != -1 && height != -1) { int scaledWidth = (int) (width * scale); int scaledHeight = (int) (height * scale); bounds = new Rect(0, 0, scaledWidth, scaledHeight); } long startFrame = json.optLong("ip", 0); long endFrame = json.optLong("op", 0); float frameRate = (float) json.optDouble("fr", 0); String version = json.optString("v"); String[] versions = version.split("[.]"); int major = Integer.parseInt(versions[0]); int minor = Integer.parseInt(versions[1]); int patch = Integer.parseInt(versions[2]); LottieComposition composition = new LottieComposition( bounds, startFrame, endFrame, frameRate, scale, major, minor, patch); JSONArray assetsJson = json.optJSONArray("assets"); parseImages(assetsJson, composition); //解析图片 parsePrecomps(assetsJson, composition); parseFonts(json.optJSONObject("fonts"), composition); //解析字体 parseChars(json.optJSONArray("chars"), composition); //解析字符 parseLayers(json, composition); //解析图层 return composition; } ``` **(2)解析图片资源** ```js LottieImageAsset类封装图片信息 "assets": [ //资源信息 { //第一张图片 "id": "image_0", //图片id "w": 58, //图片宽度 "h": 31, //图片高度 "u": "images/", //图片路径 "p": "img_0.png" //图片名称 }, {...} //第n张图片 ] static LottieImageAsset newInstance(JSONObject imageJson) { return new LottieImageAsset(imageJson.optInt("w"), imageJson.optInt("h"), imageJson.optString("id"), imageJson.optString("p")); } ``` **(3)解析图层** Layer封装图层信息,现在lottie只支持PreComp,Solid,Image,Null,Shape,Text这6中图层。 ```js "layers": [ //图层信息 { //第一层动画 "ddd": 0, "ind": 0, //layer id 图层 id "ty": 4, //图层类型 "nm": "center_circle", "ks": {...}, //动画 "ao": 0, "shapes": [...], "ip": 0, //inFrame 该图层起始关键帧 "op": 90, //outFrame 该图层结束关键帧 "st": 0, //startFrame 开始 "bm": 0, "sr": 1 }, {...} //第n层动画 ] ``` ### 2.如何动起来 Lottie时序图: ![img](https://blog-10039692.file.myqcloud.com/1504855893605_6007_1504855893878.jpg) 利用属性动画控制进度,每次进度改变通知到每一层,触发LottieAnimationView重绘。 (1)利用属性动画计算进度 这里用到了属性动画来产生一个0~1的插值,根据不同的插值来设置当前动画进度。 代码如下: ```js public LottieDrawable() { 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((float) animation.getAnimatedValue()); } } }); } ``` (2)通过CompositionLayer把进度传递到各个图层 ```js @Override public void setProgress(@FloatRange(from = 0f, to = 1f) float progress) { super.setProgress(progress); if (timeRemapping != null) { long duration = lottieDrawable.getComposition().getDuration(); long remappedTime = (long) (timeRemapping.getValue() * 1000); progress = remappedTime / (float) duration; } if (layerModel.getTimeStretch() != 0) { progress /= layerModel.getTimeStretch(); } progress -= layerModel.getStartProgress(); for (int i = layers.size() - 1; i >= 0; i--) { layers.get(i).setProgress(progress); } } ``` (3)通知进度改变 ```js void setProgress(@FloatRange(from = 0f, to = 1f) float progress) { if (progress < getStartDelayProgress()) { progress = 0f; } else if (progress > getEndProgress()) { progress = 1f; } if (progress == this.progress) { return; } this.progress = progress; for (int i = 0; i < listeners.size(); i++) { listeners.get(i).onValueChanged(); } } ``` (4)最终回调到LottieAnimationView的invalidateDrawable ```js @Override public void invalidateDrawable(@NonNull Drawable dr) { if (getDrawable() == lottieDrawable) { // We always want to invalidate the root drawable so it redraws the whole drawable. // Eventually it would be great to be able to invalidate just the changed region. super.invalidateDrawable(lottieDrawable); } else { // Otherwise work as regular ImageView super.invalidateDrawable(dr); } } ``` (5)最后触发LottieDrawable重绘 ```js @Override public void draw(@NonNull Canvas canvas) { ... matrix.reset(); matrix.preScale(scale, scale); compositionLayer.draw(canvas, matrix, alpha); //这里会调用所有layer的绘制方法 if (hasExtraScale) { canvas.restore(); } } ``` ## 五、性能 ### 1.官方说明 如果没有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) 。 ### 2.属性动画和Lottie动画对比 以下性能对比是以K歌内单个礼物动画效果 | | 属性动画 | lottie使用硬件加速 | lottie未使用硬件加速 | | ---- | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | | 帧率 | ![img](https://blog-10039692.file.myqcloud.com/1504855429015_184_1504855429322.gif) | ![img](https://blog-10039692.file.myqcloud.com/1504855429015_184_1504855429322.gif) | ![img](https://blog-10039692.file.myqcloud.com/1504855429015_184_1504855429322.gif) | | 内容 | ![img](https://blog-10039692.file.myqcloud.com/1504855429015_184_1504855429322.gif) | ![img](https://blog-10039692.file.myqcloud.com/1504855429015_184_1504855429322.gif) | ![img](https://blog-10039692.file.myqcloud.com/1504855429015_184_1504855429322.gif) | | CPU | ![img](https://blog-10039692.file.myqcloud.com/1504855429015_184_1504855429322.gif) | ![img](https://blog-10039692.file.myqcloud.com/1504855429015_184_1504855429322.gif) | ![img](https://blog-10039692.file.myqcloud.com/1504855429015_184_1504855429322.gif) | Lottie动画在未开启硬件加速的情况下,帧率、内存,CPU都比属性动画差,开启硬件加速后,性能差不多。 ### 3、未开启硬件加速,Lottie动画大小帧率对比 | 0.12倍 | 1倍 | | ------------------------------------------------------------ | ------------------------------------------------------------ | | ![img](https://blog-10039692.file.myqcloud.com/1504855429015_184_1504855429322.gif) | ![img](https://blog-10039692.file.myqcloud.com/1504855429015_184_1504855429322.gif) | 主要耗时在draw方法,绘制区域越小,耗时越小 ## 六、K歌可用的场景 ### 1.特性引导视频 全民K歌每个大版本的首次启动都会有视频引导动画,每次都会在清晰度和文件大小平衡,最终导出一个大概有3-4M的引导视频,使用lottie可提高动画清晰度和减小安装包大小 ### 2.loading动画 ![img](https://blog-10039692.file.myqcloud.com/1504856351022_651_1504856351960.gif) ![img](https://blog-10039692.file.myqcloud.com/1504856370445_224_1504856371949.gif) ### 3.礼物动画 ![img](https://mc.qcloudimg.com/static/img/d02161cd9c6e4d3286d223ae18c7f303/image.gif) 这是全民K歌的礼物面板,内部有大量礼物动画,每个礼物动画都不相同,动画过程中有大量的旋转,透明度,大小的变化,需要用属性动画实现,非常麻烦,代码可维护性也比较差。对比使用lottie后,有几大优势: 1、100%实现设计效果 2、客户端代码量极少,易维护 3、每个动画可动态配置动画样式(加载不同的json) 4、所有动画都可动态配置,动画配置文件,素材都可从网上加载 ### 4.说唱 ![img](https://mc.qcloudimg.com/static/img/1b8255d9d0f214bb0971c76df76d1681/image.gif) K歌的说唱功能需要歌词按照特定的动画展示出来,这里就涉及歌词放大,缩小,旋转等等特效。实现时,根据当前时间,在canvas上歌词绘制出来,最终再和声音融合在一起生成一个MV视频,这里就导致动画不能特别复杂,并且有一定的规律。 如果使用lottie后,可以把效果导出到json动画文件里,客户端加载动画文件,循环设置进度,读取每帧画面,再和声音融合生成MV。 优势: 1.动画丰富 2.代码量少 3.可使用设计导出的字体 代码 ```js animationView.setProgress(progress); //设置当前进度 animationView.buildDrawingCache(); //强制缓存绘制数据 Bitmap image = animationView.getDrawingCache(); //获取当前绘制数据 ``` ## 七、总结 **1.劣势** (1)性能不够好—某些动画特效,内存和性能不够好;相对于属性动画,在展示大动画时,帧率较低 **2.优势** (1)开发效率高—代码实现简单,更换动画方便,易于调试和维护。 (2)数据源多样性—可从assets,sdcard,网络加载动画资源,能做到不发版本,动态更新 (3)跨平台—设计稿导出一份动画描述文件,android,ios,react native通用 Lottie使用简单,易于上手,非常值得尝试。 ## 八、参考资料 1.[GitHub - airbnb/lottie-android: Render After Effects animations natively on Android and iOS](https://github.com/airbnb/lottie-android) 2.[Lottie的使用及原理浅析 - 彩笔学长 - CSDN博客](http://blog.csdn.net/XSF50717/article/details/55121478) 3.[从json文件到炫酷动画-Lottie实现思路和源码分析 - 简书](http://www.jianshu.com/p/81be1bf9600c) 4.[Most Popular - LottieFiles](https://www.lottiefiles.com/) **此文已由作者授权腾讯云+社区在各渠道发布** **获取更多新鲜技术干货,可以关注我们[腾讯云技术社区-云加社区官方号及知乎机构号](https://www.zhihu.com/org/teng-xun-yun-ji-zhu-she-qu/activities)**

有疑问加站长微信联系(非本文作者)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1303 次点击  
加入收藏 微博
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传