Lottie动画源码解析

Lottie是一个很成熟的开源动画框架,它支持直接使用从AE导出的动画文件,在不同平台均可快速使用,大大减轻了程序员的工作量,也让复杂的动画成为可能。该动画文件使用Json格式来描述内容,可以大大缩减文件的体积。在Android平台,Lottie的作用就是把Json文件做解析,使用code做控制,并通过Canvas绘制出来。Lottie效果如下:
请添加图片描述如果用纯代码去手搓以上的动画效果,估计码农们会原地狗带。而用Lottie作为衔接,这一切就很丝滑,就像显示一张图片一样简单,这就是Lottie的强大,可以说是降维打击。

1.文件结构

1.1最外层文件

Json的最外层结构如下:

{"v": "5.8.0",  //bodymovin的版本"fr": 60,      //帧率"ip": 0,       //起始关键帧"op": 102,     //结束关键帧"w": 1350,     //动画宽度"h": 800,      //动画高度"nm": "recommend_turn page_x0.75_original", //名称"ddd": 0,       //是否为3d"assets":[],   //资源信息"layers":[],   //图层信息"markers": []  //遮罩
}

其中的layers是动画最核心的内容,因为Lottie的动画是由不同的Layer组合而成的。

1.2 Layers层

结构如下:

"layers": [                            //图层信息{"ddd": 0,         //是否为3d"ind": 1,                     //图层id 唯一性"ty": 4,            //图层类型"nm": "page back 4",//图层名称"refId": "comp_0", // 引用的资源,图片/预合成层"td": 1,"sr": 1,"ks": {...},              // 变换。对应AE中的变换设置"ao": 0,"layer": [],         // 该图层包含的子图层"shaps": [],         // 形状图层"ip": 12,                     //该图层起始关键帧"op": 1782,         //该图层结束关键帧"st": -18,         "bm": 0}

ty : 定义了图层的类型,类型有ImageLayer、ShapeLayer、ScaleLayer、SolidLayer、TextLayer和NullLayer,不同的layer会使用不同的变化策略以及不同的资源。
refId :这是ImageLayer会用到的图片资源。
shaps :ShapeLayer会用到的资源,描述形状。
ks :变换的描述,所有图层都会用到这个资源,它是对动画怎么变化的具体描述,它内部包含了很多维度的变。

1.3 KS层

具体如下:

"ks": { // 变换。对应AE中的变换设置"o": { // 透明度"a": 0,"k": 100,"ix": 11},"r": { // 旋转"a": 0,"k": 0,"ix": 10},"p": { // 位置"a": 0,"k": [-167, 358.125, 0],"ix": 2},"a": { // 锚点"a": 0,"k": [667, 375, 0],"ix": 1},"s": { // 缩放"a": 0,"k": [100, 100, 100],"ix": 6}
}

2.文件解析过程

2.1 LottieTask

在LottieAnimationView的setAnimation方法是加载资源的入口,它内部会把加载资源的任务创建一个LottieTask,并且在执行完文件解析之后,创建相关的Layer,并封装在LottieComposition作为结果返回给LottieAnimationView,如下:

public void setAnimation(@RawRes final int rawRes) {LottieTask<LottieComposition> task = cacheComposition ?LottieCompositionFactory.fromRawRes(getContext(), rawRes) : LottieCompositionFactory.fromRawRes(getContext(), rawRes, null);//添加任务监听,在任务执行成功之后回调setCompositionTask(task); }

LottieCompositionFactory内部会创建一个LottieTask并添加到线程池中去异步执行:

public static LottieTask<LottieComposition> fromRawRes(Context context, @RawRes final int rawRes, @Nullable String cacheKey) {return cache(cacheKey, new Callable<LottieResult<LottieComposition>>() {@Overridepublic LottieResult<LottieComposition> call() {// 这里执行异步解析任务return fromRawResSync(context, rawRes);}});}

在LottieTask内部中添加线程池任务:

LottieTask(Callable<LottieResult<T>> runnable, boolean runNow) {EXECUTOR.execute(new LottieFutureTask(runnable));}

2.2 LayerParser

跟进fromRawResSync方法里面去,会调用到LottieCompositionMoshiParser去做解析最外层的Json数据,我们主要关注是解析layers字段:
LottieCompositionMoshiParser.java

public static LottieComposition parse(JsonReader reader) throws IOException {switch (reader.selectName(NAMES)) {case 6:parseLayers(reader, composition, layers, layerMap);}
}private static void parseLayers(JsonReader reader, LottieComposition composition,List<Layer> layers, LongSparseArray<Layer> layerMap) throws IOException {Layer layer = LayerParser.parse(reader, composition);layers.add(layer);layerMap.put(layer.getId(), layer);}

最后再调用了LayerParser做生成各个Layer数据结构,当然LayerParser内部还要对下一层的数据做解析,最后形成一个树形数据结构返回给LottieAnimationView。

3.创建绘制图层

3.1 CompositionLayer和BaseLayer

LottieTask在解析完之后,会把LottieComposition返回给LottieAnimationView:
LottieAnimationView.java

public void setComposition(@NonNull LottieComposition composition) {boolean isNewComposition = lottieDrawable.setComposition(composition);}

LottieAnimationView又把数据透传给LottieDrawable:
LottieDrawable.java

public boolean setComposition(LottieComposition composition) {buildCompositionLayer();
}compositionLayer = new CompositionLayer(this, LayerParser.parse(composition), composition.getLayers(), composition);

这里会创建一个CompositionLayer,它继承自BaseLayer,而在它内部会创建各个类型的图层,如下:

 static BaseLayer forModel(Layer layerModel, LottieDrawable drawable, LottieComposition composition) {switch (layerModel.getLayerType()) {case SHAPE:return new ShapeLayer(drawable, layerModel);case PRE_COMP:return new CompositionLayer(drawable, layerModel,composition.getPrecomps(layerModel.getRefId()), composition);case SOLID:return new SolidLayer(drawable, layerModel);case IMAGE:return new ImageLayer(drawable, layerModel);case NULL:return new NullLayer(drawable, layerModel);case TEXT:return new TextLayer(drawable, layerModel);}}

这些不同类型的Layer也是继承自BaseLayer,所以这里创建图层的过程,就是把之前Json文件解析的树形数据结构,转换成绘制图层的树形结构。CompositionLayer和其他各个BaseLayer的关系,就类似于View和ViewGroup之间的关系一样。

3.2 关键帧动画

同时在创建各个BaseLayer的时候,它们内部还会继续创建对应的关键帧动画类,我们重点看一下TransformKeyframeAnimation,也就是上面Json文件中对应的“ks”数据:
TransformKeyframeAnimation.java

  @NonNull private BaseKeyframeAnimation<PointF, PointF> anchorPoint;@NonNull private BaseKeyframeAnimation<?, PointF> position;@NonNull private BaseKeyframeAnimation<ScaleXY, ScaleXY> scale;@NonNull private BaseKeyframeAnimation<Float, Float> rotation;@NonNull private BaseKeyframeAnimation<Integer, Integer> opacity;@Nullable private FloatKeyframeAnimation skew;@Nullable private FloatKeyframeAnimation skewAngle;

可以看到内部定义了很多关键帧动画类,它们决定了该Layer在某个时刻应该绘制怎样的内容。
关系图如下:
在这里插入图片描述

4.动画绘制过程

4.1LottieValueAnimator计算progress

准备好了资源,创建好了图层,在LottieAnimationView 调用了playAnimation()方法之后,就开始播放动画了。同样,也会调用到LottieDrawable的playAnimation()方法:
LottieDrawable.java

public void playAnimation() {//调用animator去播放动画animator.playAnimation();   }

LottieDrawable会调用它内部的LottieValueAnimator去计算动画播放的具体帧:
LottieValueAnimator.java

 @MainThreadpublic void playAnimation() {  //这里会通知更新动画setFrame((int) (isReversed() ? getMaxFrame() : getMinFrame()));//这里注册Choreographer,以便同步vsync信号,并在doframe方法中继续更新动画postFrameCallback();}

setFrame会调用到父类里的notifyUpdate()方法,然后回调通知到LottieDrawable里面的监听接口:
LottieDrawable.java

private final ValueAnimator.AnimatorUpdateListener  progressUpdateListener = new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {if (compositionLayer != null) {compositionLayer.setProgress(animator.getAnimatedValueAbsolute());}}};

从这里可以看出,LottieValueAnimator的作用主要是根据屏幕的刷新信号,来计算应该播放的帧序列,也就是播放的具体进度。当存在卡断的情况下,Choreographer的doframe方法会延迟通知,就会导致LottieValueAnimator存在跳帧的情况。

4.2关键帧动画设置progress

接着看绘制流程,播放进度会给到CompositionLayer,而CompositionLayer又会把播放进度再分发给具体的每一个Layer,如下:
CompositionLayer.java

 setProgress(@FloatRange(from = 0f, to = 1f) float progress) {for (int i = layers.size() - 1; i >= 0; i--) {layers.get(i).setProgress(progress);}
}

每个Layer都是继承自BaseLayer,在BaseLayer中又会把进度给到内部的各个关键帧动画,最重要的是TransformKeyframeAnimation,它内部包含各个维度的变化序列:
TransformKeyframeAnimation.java

setProgress(float progress) {
if (opacity != null) {opacity.setProgress(progress);  //透明度}if (anchorPoint != null) {anchorPoint.setProgress(progress); //锚点}if (position != null) {position.setProgress(progress);  //位置}if (scale != null) {scale.setProgress(progress);  //缩放}if (rotation != null) {rotation.setProgress(progress); //旋转}if (skewAngle != null) {skewAngle.setProgress(progress); //斜角
}

4.3Layer请求刷新

在设置完progress之后,各个Layer就会触发onValueChanged()方法,并请求刷新自己:
BaseLayer.java

public void onValueChanged() {invalidateSelf();}private void invalidateSelf() {lottieDrawable.invalidateSelf();}

接着会调用到LottieDrawable的绘制:
LottieDrawable.java

public void invalidateSelf() {final Callback callback = getCallback();if (callback != null) {callback.invalidateDrawable(this);}}

4.4Layer绘制

当自定义的Drawable需要重绘的时候,就需要调用invalidateDrawable方法,请求绘制自己,然后会通过ImageView的onDraw方法里调用到Drawable的draw方法:
Drawable.java

 public void draw(@NonNull Canvas canvas) {drawInternal(canvas)}

接下来就会调用到CompositionLayer中的draw方法,在CompositionLayer中又会把实际绘制的工作交给各个Layer去完成:
CompositionLayer.java

 draw(Canvas canvas, Matrix parentMatrix, int parentAlpha) {drawLayer(canvas, matrix, alpha);
}drawLayer(Canvas canvas, Matrix parentMatrix, int parentAlpha) {for (int i = layers.size() - 1; i >= 0; i--) {BaseLayer layer = layers.get(i);layer.draw(canvas, parentMatrix, childAlpha);}
}

不同的Layer层,对draw的实现也不一样,比如ImageLayer是drawBitmap,绘制前会在父类调关键帧动画计算出来的参数,对Bitmap做位移、旋转、透明度变化等等变化操作,最后才是使用Canvas做绘制:
BaseLayer.java:

public void draw(Canvas canvas, Matrix parentMatrix, int parentAlpha) {matrix.preConcat(transform.getMatrix()); //通过关键帧动画做变化操作drawLayer(canvas, matrix, alpha); //调用子类的绘制
}

ImageLayer.java

 @Override public void drawLayer(@NonNull Canvas canvas, Matrix parentMatrix, int parentAlpha) {Bitmap bitmap = getBitmap(); //读取图片内容canvas.drawBitmap(bitmap, src, dst , paint);//绘制图片
}

至此,一帧动画的绘制就完成了,后面会由LottieValueAnimator的doFrame回调不停地重复上面的步骤,实现连续的动画效果。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/497350.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Cadence学习笔记 16 HDMI接口布局

基于Cadence 17.4&#xff0c;四层板4路HDMI电路 更多Cadence学习笔记&#xff1a;Cadence学习笔记 1 原理图库绘制Cadence学习笔记 2 PCB封装绘制Cadence学习笔记 3 MCU主控原理图绘制Cadence学习笔记 4 单片机原理图绘制Cadence学习笔记 5 四路HDMI原理图绘制Cadence学习笔记…

微服务篇-深入了解 MinIO 文件服务器(你还在使用阿里云 0SS 对象存储图片服务?教你使用 MinIO 文件服务器:实现从部署到具体使用)

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 MinIO 文件服务器概述 1.1 MinIO 使用 Docker 部署 1.2 MinIO 控制台的使用 2.0 使用 Java 操作 MinIO 3.0 使用 minioClient 对象的方法 3.1 判断桶是否存在 3.2…

logback之pattern详解以及源码分析

目录 &#xff08;一&#xff09;pattern关键字介绍 &#xff08;二&#xff09;源码分析 &#xff08;一&#xff09;pattern关键字介绍 %d或%date&#xff1a;表示日期&#xff0c;可配置格式化%d{yyyy-MM-dd HH:mm:ss} %r或%relative&#xff1a;也是日期&#xff0c;不过…

【期末复习】JavaEE(下)

1. MVC开发模式 1.1. 运行流程 1.2. SpringMVC 核心组件 1.3. 注解解释 2. ORM与MyBatis 2.1. ORM—对象关系映射 2.2. MyBatis 2.2.1. 创建步骤 会话是单例的&#xff0c;不能跨方法。&#xff08;单例的原因主要是从数据安全角度出发&#xff09; import org.apache.ibatis…

作业帮基于 Apache DolphinScheduler 3_0_0 的缺陷修复与优化

文|作业帮大数据团队&#xff08;阮文俊、孙建业&#xff09; 背 景 基于 Apache DolphinScheduler &#xff08;以下简称DolphinScheduler&#xff09;搭建的 UDA 任务调度平台有效支撑了公司的业务数据开发需求&#xff0c;处理着日均百万级别的任务量。 整个 UDA 的架构如…

电脑缺失sxs.dll文件要怎么解决?

一、文件丢失问题&#xff1a;以sxs.dll文件缺失为例 当你在运行某个程序时&#xff0c;如果系统提示“找不到sxs.dll文件”&#xff0c;这意味着你的系统中缺少了一个名为sxs.dll的动态链接库文件。sxs.dll文件通常与Microsoft的.NET Framework相关&#xff0c;是许多应用程序…

Web开发:ORM框架之使用Freesql的分表分页写法

一、自动分表&#xff08;高版本可用&#xff09; 特性写法 //假如是按月分表&#xff1a;[Table(Name "log_{yyyyMM}", AsTable "createtime2022-1-1(1 month)")]注意&#xff1a;①需包含log_202201这张表 ②递增规律是一个月一次&#xff0c;确保他们…

【数据结构与算法】单向链表

一、什么是链表 链表由一系列节点组成&#xff0c;每个节点都包含一个 data 域&#xff08;存放数据&#xff09;和一个 next 域&#xff08;指向下一节点&#xff09;。链表中的节点可以按照任意顺序存放在内存中&#xff0c;它们之间并不连续。每个节点都记录了下一个节点的地…

【ACCSS】2024年亚信安全云认证专家题库

文件包含&#xff1a; 亚信安全ACCSS认证2019年真题&#xff08;1&#xff09; 亚信安全ACCSS认证2019年真题&#xff08;2&#xff09; 亚信安全ACCSS认证2019年真题&#xff08;3&#xff09; 亚信安全ACCSS认证2020年真题&#xff08;1&#xff09; 亚信安全ACCSS认证2020年…

OpenCV-Python实战(10)——形态学

1、腐蚀 cv2.erode() 可以删除图像中的噪音点。 可以删除毛边。 分割图像&#xff08;当图像连接的不够紧密时&#xff09; 。 img cv2.erode(src*,kernel*,anchor*,iterations*,borderType*,borderValue*)img&#xff1a;目标图像。 src&#xff1a;原始图像。 kernel&…

用VBA将word文档处理成支持弹出式注释的epub文档可用的html内容

有一种epub文件&#xff0c;其中的注释以弹窗形式显示&#xff0c;如下图&#xff1a; 点击注释引用后&#xff0c;对应的注释内容会弹出在页面中显示&#xff0c;再次点击弹窗外的任意位置该弹窗即关闭&#xff0c;关闭后点击任意注释引用&#xff0c;对应的注释内容会弹窗显示…

Ngnix介绍、安装、实战及用法!!!

一、Nginx简介 1、Nginx概述 Nginx (“engine x”) 是一个高性能的 HTTP 和 反向代理服务器&#xff0c;特点是占有内存少&#xff0c;并发能力强&#xff0c;能经受高负载的考验,有报告表明能支持高达 50,000 个并发连接数 。 2、正向代理 正向代理&#xff1a;如果把局…

【超详细】Git的基本概念和基本使用方式

Git是程序开发中非常重要的工具&#xff0c;是一种分布式版本控制系统&#xff0c;可用于管理和追踪软件开发过程中的变化。那么关于Git的基本操作你知道吗&#xff1f;下面是Git的基本概念和使用方式的解释&#xff1a; 仓库&#xff08;Repository&#xff09;&#xff1a;Gi…

springboot503基于Sringboot+Vue个人驾校预约管理系统(论文+源码)_kaic

摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装个人驾校预约管理系统软件来发挥其高效地信息处理的作用&am…

【Rust自学】7.4. use关键字 Pt.2 :重导入与换国内镜像源教程

喜欢的话别忘了点赞、收藏加关注哦&#xff0c;对接下来的教程有兴趣的可以关注专栏。谢谢喵&#xff01;(&#xff65;ω&#xff65;) 7.4.1. 使用pub use重新导入名称 使用use将路径导入作用域内后。该名称在词作用域内是私有的。 以上一篇文章的代码为例&#xff1a; m…

Pandas03

Pandas01 Pandas02 文章目录 内容回顾1 排序和统计函数2 缺失值处理2.1 认识缺失值2.2 缺失值处理- 删除2.3 缺失值处理- 填充非时序数据时序数据 3 Pandas数据类型3.1 数值类型和字符串类型之间的转换3.2 日期时间类型3.3 日期时间索引 4 分组聚合4.1 分组聚合的API使用4.2 分…

平凉一窝丝:丝丝缕缕的舌尖风情

在平凉的美食版图中&#xff0c;食家巷一窝丝以其独特的工艺和诱人的口感&#xff0c;占据着重要的一席之地&#xff0c;成为了平凉人心目中的经典美食之一。从外观上看&#xff0c;平凉食家巷一窝丝如同一盘精心雕琢的金丝&#xff0c;根根细丝紧密盘绕&#xff0c;整齐而美观…

Wordly Wise 3000 国际背单词01 介绍 + 测词汇量

&#x1f4da; Wordly Wise 3000 国际背单词01 介绍 测词汇量 &#x1f31f; 大家好&#xff01;我们正式启动背Wordly Wise 3000单词&#xff0c;旨在利用国际资源和科学的学练方法&#xff0c;帮助大家更得效地坚持学练单词。我们将通过图文和Video等多种形式与大家分享经验…

C++进阶重点知识(一)|智能指针|右值|lambda|STL|正则表达式

目录 1智能指针1.shared_ptr1.1 shared_ptr的基本用法使用shared_ptr要注意的问题运用 2.unique_ptr独占的智能指针示例&#xff1a;管理动态内存 3.weak_ptr弱引用的智能指针weak_ptr的基本用法lock 的作用&#xff1a;weak_ptr返回this指针weak_ptr解决循环引用问题weak_ptr使…

计算机网络 (9)数据链路层

前言 计算机网络中的数据链路层&#xff08;Data Link Layer&#xff09;是OSI&#xff08;开放系统互连&#xff09;参考模型中的第二层&#xff0c;位于物理层和网络层之间。它在物理层提供的服务基础上&#xff0c;负责在相邻节点之间建立、维护和终止链路&#xff0c;确保数…