本文由云+社区发表

作者:paulzeng

**导语:**Lottie是Airbnb开源的一个面向 iOS、Android、React Native 的动画库,可实现非常复杂的动画,使用也及其简单,极大释放人力,值得一试。

一、简介

Lottie 是Airbnb开源的一个面向 iOS、Android、React Native 的动画库,能分析 Adobe After Effects 导出的动画,并且能让原生 App 像使用静态素材一样使用这些动画,完美实现动画效果。

现在使用各平台的 native 代码实现一套复杂的动画是一件很困难并且耗时的事,我们需要为不同尺寸的屏幕加载不同的素材资源,还需要写大量难维护的代码,而Lottie可以做到同一个动画文件在不同平台上实现相同的效果,极大减少开发时间,实现不同的动画,只需要设置不同的动画文件即可,极大减少开发和维护成本。

官方效果图:

二、如何使用

Lottie支持多平台,使用同一个JSON动画文件,可在不同平台实现相同的效果。

Android 通过Airbnb的开源项目lottie-android实现,最低支持 API 16;

IOS 通过Airbnb的开源项目lottie-ios实现,最低支持 IOS 7;

React Native,通过Airbnb的开源项目lottie-react-native实现;

这是React logo的动画,以下以Android平台为例如何使用Lottie

1.下载Lottie

在项目的 build.gradle 文件添加依赖

dependencies {  compile 'com.airbnb.android:lottie:2.1.0'
}

2.添加 Adobe After Effects 导出的动画文件

Lottie默认读取Assets中的文件,我们需要把动画文件react.json 保存在app/src/main/assets文件里。(文件比较大,只展示了部分内容,文件链接)

{"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动画效果

<com.airbnb.lottie.LottieAnimationViewandroid: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动画文件

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图片

animationView.setImageAssetDelegate(new ImageAssetDelegate() {@Overridepublic 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字体

animationView.setFontAssetDelegate(new FontAssetDelegate(){public Typeface fetchFont(String fontFamily) {Typeface customFont = Typeface.createFromFile(FONT_PATH + fontFamily);return customFont;}
});

4.缓存动画

/*
* Lottie内部有两个缓存map(强引用缓存,弱引用缓存),在动画文件加载完成后会根据设置的缓存策略缓存动画,方便下次使用。
*/
animationView.setAnimation(animation, LottieAnimationView.CacheStrategy.Strong);    //强缓存animationView.setAnimation(animation, LottieAnimationView.CacheStrategy.Weak);      //弱缓存

四、Lottie实现原理

设计师把一张复杂的图片使用多个图层来表示,每个图层展示一部分内容,图层中的内容也可以拆分为多个元素。拆分元素之后,根据动画需求,可以单独对图层或者图层中的元素做平移、旋转、收缩等动画。

Lottie的使用的资源是需要先通过bodymovin( bodymovin 插件本身是用于网页上呈现各种AE效果的一个开源库)将 Adobe After Effects (AE)生成的aep动画工程文件转换为通用的json格式描述文件。Lottie则负责解析动画的数据,计算每个动画在某个时间点的状态,准确地绘制到屏幕上。

导出的json动画描述文件:

{"v": "4.6.0", "fr": 29.9700012207031, "ip": 0, "op": 141.000005743048, "w": 800, "h": 800, "ddd": 0, "assets": [ ], "layers": [{...}, ]
}

Lottie主要类图:

图:lottie_class

Lottie对外通过控件LottieAnimationView暴露接口,控制动画。

LottieAnimationView继承自ImageView,通过当前时间绘制canvas显示到界面上。这里有两个关键类:LottieComposition 负责解析json描述文件,把json内容转成Java数据对象;LottieDrawable负责绘制,把LottieComposition转成的数据对象绘制成drawable显示到View上。顺序如下:

1.json文件解析

LottieComposition负责解析json文件,建立数据到java对象的映射关系。

(1)解析json外部结构

LottieComposition封装整个动画的信息,包括动画大小,动画时长,帧率,用到的图片,字体,图层等等。

json外部结构

{"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)解析图片资源

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中图层。

"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时序图:

利用属性动画控制进度,每次进度改变通知到每一层,触发LottieAnimationView重绘。

(1)利用属性动画计算进度

这里用到了属性动画来产生一个0~1的插值,根据不同的插值来设置当前动画进度。

代码如下:

public LottieDrawable() {animator.setRepeatCount(0);animator.setInterpolator(new LinearInterpolator());animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {if (systemAnimationsAreDisabled) {animator.cancel();setProgress(1f);} else {setProgress((float) animation.getAnimatedValue());}}});
}

(2)通过CompositionLayer把进度传递到各个图层

@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)通知进度改变

  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

@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 ImageViewsuper.invalidateDrawable(dr);}
}

(5)最后触发LottieDrawable重绘

@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未使用硬件加速
帧率
内容
CPU

Lottie动画在未开启硬件加速的情况下,帧率、内存,CPU都比属性动画差,开启硬件加速后,性能差不多。

3、未开启硬件加速,Lottie动画大小帧率对比

0.12倍 1倍

主要耗时在draw方法,绘制区域越小,耗时越小

六、K歌可用的场景

1.特性引导视频

全民K歌每个大版本的首次启动都会有视频引导动画,每次都会在清晰度和文件大小平衡,最终导出一个大概有3-4M的引导视频,使用lottie可提高动画清晰度和减小安装包大小

2.loading动画

3.礼物动画

这是全民K歌的礼物面板,内部有大量礼物动画,每个礼物动画都不相同,动画过程中有大量的旋转,透明度,大小的变化,需要用属性动画实现,非常麻烦,代码可维护性也比较差。对比使用lottie后,有几大优势:

1、100%实现设计效果

2、客户端代码量极少,易维护

3、每个动画可动态配置动画样式(加载不同的json)

4、所有动画都可动态配置,动画配置文件,素材都可从网上加载

4.说唱

K歌的说唱功能需要歌词按照特定的动画展示出来,这里就涉及歌词放大,缩小,旋转等等特效。实现时,根据当前时间,在canvas上歌词绘制出来,最终再和声音融合在一起生成一个MV视频,这里就导致动画不能特别复杂,并且有一定的规律。

如果使用lottie后,可以把效果导出到json动画文件里,客户端加载动画文件,循环设置进度,读取每帧画面,再和声音融合生成MV。

优势:

1.动画丰富

2.代码量少

3.可使用设计导出的字体

代码

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

2.Lottie的使用及原理浅析 - 彩笔学长 - CSDN博客

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

4.Most Popular - LottieFiles

此文已由作者授权腾讯云+社区在各渠道发布

获取更多新鲜技术干货,可以关注我们腾讯云技术社区-云加社区官方号及知乎机构号

这样做动画交互,一点都不费力!相关推荐

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

    目录 一.简介 二.如何使用 1.下载Lottie 2.添加 Adobe After Effects 导出的动画文件 3.使用Lottie 4.引入Lottie影响 (1)兼容性 (2)安装包 三.使 ...

  2. 做减法才是真本事,别以为你很能学,做加法一点都不难

    文章目录 顶级的高手才敢做减法 前言 一.做减法才是真本事 二.大数据梦想联盟活动开启 顶级的高手才敢做减法 前言 大多数人不懂,不会,不做,才是你的机会,你得行动,不能畏首畏尾 大数据等于趋势,一个 ...

  3. 30多岁程序员哭诉:得了抑郁症,请假在家躺尸数周,一点都没有好转。现在不敢辞职,工作又做不下去,每天想自杀!...

    自古有三十而立之说,但30岁却成了一个危险的"职场抑郁带". 据统计,在患有职业压力症或有抑郁倾向的群体中,30岁上下的占了半数以上. 近日,一名程序猿在论坛发帖: 30多岁了,得 ...

  4. ios开发——使用CALayer和Core Animation做动画效果

    一. CALayer (一). CALayer简介 在iOS中,你能看得见摸得着的东西基本上都是UIView,比如一个按钮.一个文本标签.一个文本输入框.一个图标等等,这些都是UIView,其实UIV ...

  5. 基于Processing的动画交互

    基于processing的动画交互 基本介绍 总览结果 分部设计及源代码 1. 添加字体 2.球随鼠标 3.两个对象之间的引力 4.小型粒子系统 5.心得想法 基本介绍 大致看了<代码本色> ...

  6. 动画交互应用——神秘力量

    目录 一 前言 二 应用背景 三 主要功能及用法 场景一--一根橡皮绳 场景二--一个跷跷板 场景三--一座滑滑梯 四 应用创意 1 视觉独特的故事背景 2 真实的物理系统模拟 3 简单的画风及操作 ...

  7. unity2D动画-角色切片与2DAnimation插件做动画

    unity2D动画-角色切片做动画 写在前面的话 开发环境与准备 用角色切片做动画 终于可以Key动画了 2DAnimation插件做动画 总结 写在前面的话 更新 建议有复杂2D动画需求的话用spi ...

  8. 对RecyclerView Item做动画

    对RecyclerView Item做动画 对RecyclerView Item做动画,刚刚开始研究的时候一些坑,在这里把一些设计思路分享出去 添加动态位移,静态位移,缩放等动画,保证了动画状态的平滑 ...

  9. CABasicAnimation,CAKeyframeAnimation,CATransition,CAAnimationGroup,UIBezierPath之间做动画的不同点和各自的使用范围。

    CABasicAnimation,CAKeyframeAnimation,CATransition,CAAnimationGroup,UIBezierPath之间做动画的不同点和各自的使用范围. CA ...

最新文章

  1. Dynamic CRM 2013学习笔记(十八)根据主表状态用JS控制子表自定义按钮
  2. Java程序员的日常—— 《编程思想》关于类的使用常识
  3. pyqt 多线程使用
  4. nacos启动失败:org.springframework.boot.web.server.WebServerExceptio
  5. MySQL安装板多少钱_MySQL安装板怎么安装
  6. linux的静态编译elf无法调试,[翻译]自己动手编写一个Linux调试器系列之4 ELF文件格式与DWARF调试格式 by lantie@15PB...
  7. 15个优雅的Python编程技巧,掌握后瞬间玩转Python
  8. 1218 标签的显示与隐藏
  9. html5 心,HTML5你必须知道的28个新特性
  10. javaweb基础(36)_jdbc进行批处理
  11. vmware您无权输入许可证秘钥
  12. 电商项目java经验_分布式电商系统项目总结
  13. [Latex]visio画图导入矢量图到Latex | 裁剪pdf | 去掉pdf白边
  14. 【总结】深度学习阶段性总结
  15. [GIS原理] 8 GIS基本空间分析-叠置分析|缓冲区分析|窗口分析
  16. 面对骚扰短信,不是回TD退订,而是要回0000!(附正确拦截骚扰短信教程)
  17. win7不能在本地计算机启动防火墙,Win7防火墙启动不了的原因及解决办法
  18. JS VLC插件 js
  19. 英文参考文献按照首字母排序使用matlab实现
  20. 【解决方案】基于国标GB28181协议视频智能分析平台EasyCVR/EasyGBS打造的智慧企业AR云景解决方案

热门文章

  1. java筑基期(8)----jquery(高级)
  2. Elasticsearch:如何使用 Elasticsearch PHP 客户端创建简单的搜索引擎
  3. 使用Python爬取最好大学网大学排名
  4. SIP电话(一)之程控交换机-FreeSWITCH的使用总结
  5. 温州人不炒股票炒外汇 1亿美金爆炒出300亿交易量
  6. 阿里云邮件服务器怎么设置才能在QQ邮箱访问,互发邮件?
  7. 小米10刷华为鸿蒙,现在华为手机适可以刷小米系统吗?网友:千万别刷!
  8. Functional vs OOP vs Procedural三种方法 JavaScript 示例
  9. 计算机应用能力 实施办法,《计算机应用基》实施细则(2017版).doc
  10. 轮播插件——flexslider