Lottie 源码浅探

本贴最后更新于 2193 天前,其中的信息可能已经物是人非

前置知识:

Lottie 对动画的变换主要是通过 Matrix 实现, 因此需要了解 Matrix 相关知识,可以参考下面的博客:

https://blog.csdn.net/pathuang68/article/details/6991867

一、动画 Json 传入方法:

Json 动画传入的方式定义在 LottieComposition.Factory 中,分别为:

1.      fromAssetFileName:从 assets 中加载,参数 filename 指 assets 中 json 的名称

2.      fromRawFile:从 res/raw 中加载,参数 resId 就是 raw 中 json 的 id

3.      fromInputStream:从 InputStream 中加载

4.      fromJsonString:从 Json 字串中加载

5.      fromJsonReader:从 JsonReader 中加载(如果需要解析的是 JsonObject,可以通过 new JsonReader(newStringReader(jsonObject))的方式加载,不过这种方式不推荐)

二、解析(Lottie 中的解析方法都位于 com.airbnb.lottie.parser 下):

1.      动画 json 传入后,最终会调用到 LottieComposition.Factory.fromJsonReader 方法,该方法会将解析事件委托给 AsyncCompositionLoader 异步线程,而 AsyncCompositionLoader 会调用 LottieComposition.Factory.fromJsonSync 方法,该方法调用 LottieCompositionParser.parse 方法开始进行动画 json 的解析,并将解析结果生成 LottieComposition。

2.      AsyncCompositionLoader 中会调用 LottieCompositionParser.parse 方法进行具体解析,在 LottieCompositionParser.parse 方法中:

1)      layers 会调用 parseLayers,parseLayers 调用 LayerParser.parse 解析

2)      assets 会调用 parseAssets 解析

3)      fonts 会调用 parseFonts,parseFonts 调用 FontParser.parse 解析

4)      chars 会调用 parseChars,parseChars 调用 FontCharacterParser.parse 解析

5)      w 会解析成 width

6)      h 会解析成 height

7)      ip 会解析成 startFrame

8)      op 会解析成 endFrame

9)      fr 会解析成 frameRate(动画速率)

10)  v 会解析成 version(插件版本),进行版本支持性校验

3.      LottieCompositionParser 会根据上面的解析结果生成 LottieComposition:

1)      Rect bounds:通过 scaledWidth(width 与屏幕密度的乘积)、scaledHeight(height 与屏幕密度的乘积)确定

2)      startFrame、endFrame、frameRate 就是上一步解析的值

3)      layers、layerMap 是 layers 中解析的值

4)      precomps 是 assets 中解析的值

5)      images 是 assets 中解析出的图片的值

6)      characters 是 chars 中解析的值

7)      fonts 是 fonts 中解析的值

三、Layer 具体解析:

LayerParser 解析 layers 中的内容,其中关键是 ks,ks 的内容包含了动画用到的一些值,ks 的解析是通过 AnimatableTransformParser.parse 方法进行,LayerParser.parse 会通过上面解析的数据最终生成 Layer:

1)      nm:解析为 layerName

2)      ind:解析为 layerId

3)      refId:解析为 refId

4)      ty:解析为 layerType(附录 1)

5)      parent:解析为 parentId

6)      sw:解析为 solidWidth

7)      sh:解析为 solidHeight

8)      sc:解析为 solidColor

9)      ks:解析为 transform

10)  tt:解析为 mattType

11)  masksProperties:数组,里面数据会通过 MaskParser.parse 解析成 Mask 并存入 masks 中

12)  shapes:数组,里面数据会通过 ContentModelParser.parse 解析成 ContentModel 并存入 shapes 中

13)  t:文本,会进一步解析:

(1)    d:通过 AnimatableValueParser.parseDocumentData 解析成 text

(2)    a:通过 AnimatableTextPropertiesParser.parse 方法解析成 textProperties

14)  ef:Lottie 中目前不支持这种方式的效果,如果要用,需要将这种效果之直接添加到 shape 的 contents 中

15)  sr:解析成 timeStretch

16)  st:解析成 startFrame

17)  w:解析成 preCompWidth

18)  h:解析成 preCompHeight

19)  ip:解析成 inFrame

20)  op:解析成 outFrame

21)  tm:解析成 timeRemapping

22)  cl:解析成 cl

AnimatableTransformParser 解析 ks 内容,包括”a”(位置信息)、”p”(位移)、”s”(缩放)、”rz”(控制 3d 图层,暂时不支持)、”r”、”o”、”so”、”eo”,其实质是解析上面各字段下配置的 k 的值(解析方法为 KeyframesParser.parse),并根据 k 的值生成对应的 AnimatableValue,而 k 值用到的几个主要解析方式是:

1)      PathParser:用于解析 k 数组,解析成 PointF,用于生成 AnimatablePathValue。

2)      FloatParser:用于解析 k 数字,解析成 Float,用于生成 AnimatableFloatValue。

3)      IntegerParser:用于解析 k 数字,解析成 Integer,用于生成 AnimatableIntegerValue。

4)      ScaleXYParser:用于解析 k 数组,解析成 ScaleXY,用于生成 AnimatableScaleValue。

AnimatableTransformParser 中具体字段解析:

1)      a(位置信息):调用 AnimatablePathValueParser.parse 解析 k 中配置的值,并将生成的结果赋值给 anchorPoint

2)      p(位移信息):调用 AnimatablePathValueParser.parseSplitPath 解析,并将解析生成的结果赋值给 postion

3)      s(缩放信息):调用 AnimatableValueParser.parseScale 解析,并将解析结果赋值给 scale

4)      rz:3D 图层,不支持

5)      r(翻转信息):调用 AnimatableValueParser.parseFloat 解析,并将解析结果赋值给 rotation

6)      o(不透明度):调用 AnimatableValueParser.parseFloat 方法解析,并将解析结果赋值给 opacity

7)      so(开始时不透明度):调用 AnimatableValueParser.parseFloat 方法解析,并将解析结果赋值给 startOpacity

8)      eo(结束时不透明度):调用 AnimatableValueParser.parseFloat 方法解析,并将解析结果赋值给 endOpacity

四、AnimatablePathValueParser、AnimatableValueParser 重要方法解析:

1.      AnimatablePathValueParser 中:

1)      parse:若待解析的 JsonReader 中的值是数组,将调用 PathKeyframeParser.parse 方法解析并将解析结果存入 keyframes(List<Keyframe>)中,之后会调用 KeyframesParser.setEndFrames 将 keyframes 中 Keyframe 的 startFrame 与 endFrame 依次串联起来;若不是数组,就直接调用 JsonUtils.jsonToPoint 方法,并将解析结果生成的 Keyframe 存入 keyframes 中。最后根据 keyframes 生成 AnimatablePathValue。

2)      parseSplitPath:

(1)    k:调用 AnimatablePathValueParser.parse 解析,并将解析结果赋值给 pathAnimation

(2)    x:若 JsonReader 值为 String,不解析,并将 hasExpressions 置为 true;若不是 String,则调用 AnimatableValueParser.parseFloat 解析,并将结果返回给 xAnimation

(3)    y:若 JsonReader 值为 String,不解析,并将 hasExpressions 置为 true;若不是 String,则调用 AnimatableValueParser.parseFloat 解析,并将结果返回给 yAnimation

如果 pathAnimation 不为空(k 直接成功),则直接返回 pathAnimation;否则返回通过 xAnimation、yAnimation 生成的 AnimatableSplitDimensPathValue。

2.      AnimatableValueParser 中 parseFloat、parseInteger、parsePoint 等方法中最终会调用 parse 方法解析,而 parse 方法则调用 KeyframesParser.parse 解析,:

1)      parseFloat:调用 parse 并将 FloatParser 作为最终解析方法,解析结果生成 AnimatableFloatValue

2)      parseInteger:调用 parse 并将 IntegerParser 作为最终解析方法,解析结果生成 AnimatableIntegerValue

3)      parsePoint:调用 parse 并将 PointFParser 作为最终解析方法,解析结果生成 AnimatablePointValue

4)      parseScale:调用 parse 并将 ScaleXYParser 作为最终解析方法,解析结果生成 AnimatableScaleValue

5)      parseShapeData:调用 parse 并将 ShapeDataParser 作为最终解析方法,解析结果生成 AnimatableShapeValue

6)      parseDocumentData:调用 parse 并将 DocumentDataParser 作为最终解析方法,解析结果生成 AnimatableTextFrame

7)      parseColor:调用 parse 并将 ColorParser 作为最终解析方法,解析结果生成 AnimatableColorValue

8)      parseGradientColor(渐变色):调用 parse 并将 GradientColorParser 作为最终解析方法,解析结果生成 AnimatableGradientColorValue

3.      KeyframesParser.parse:

Parse 方法中首先会判断 JsonReader 值是否为 STRING 类型,如果是就直接返回空数据集 keyframes(List<Keyframe>),若不是,则解析 JsonReader 中 k 中配置的值,k 中的值基本是 3 种类型,分别是数字数组、对象数组、对象:

1)      若 k 中值为数组,则判断数组中元素是否为数字,若是,则调用 KeyframeParser.parse 解析整个数组(animated 为 false);若不是数字,则调用 KeyframeParser.parse 依次解析数组中元素(animated 为 true)

2)      若 k 中值不是数组,则调用 KeyframeParser.parse 解析整个数组(animated 为 false)

上面的解析结果都会存入 keyframes 中,最后会调用 setEndFrames 方法将 keyframes 中的 Keyframe 的 startFrame 与 endFrame 串联,最后将 keyframes 返回给调用者

4.      KeyframeParser:

1)      parse:判断传入的 animated 值,若为 true,则调用 parseKeyframe;否则调用 parseStaticValue

2)      parseKeyframe 方法是用于解析 k 中对象数组中的值:

(1)    t:解析成 startFrame

(2)    s:调用传入的最终解析方法解析成 stratValue

(3)    e:调用传入的最终解析方法解析成 endValue

(4)    o:调用 JsonUtils.jsonToPoint 解析成 cp1

(5)    i:调用 JsonUtils.JsonToPoint 解析成 cp2

(6)    h:解析成 hold(若 h 值为 1 则为 true,否则为 false)

(7)    to:调用 JsonUtils.jsonToPoint 解析成 pathCp1

(8)    ti:调用 JsonUtils.jsonToPoint 解析成 pathCp2

解析完后会对解析的值进行进一步操作:

(1)    若 hold 为 ture,将 endValue 设置为 startValue,并将 interpolator 设置为 LINEAR_INTERPOLATOR;

(2)    若 hold 为 false,并且 cp1 与 cp2 均不为空,则通过 MiscUtils.clamp 筛选出合适的 cp1、cp2 的值,并通过 PathInterpolatorCompat.create 创建 interpolator(其中有 PathInterpolator 缓存的逻辑,想了解的可以看下)。

(3)    若为其他情况,则将 interpolator 设置为 LINEAR_INTERPOLATOR

最后,通过解析的 startValue、endValue、startFrame 以及创建的 interpolator 生成 Keyframe 并返回给调用者

3)      parseStaticValue:调用传入的最终解析方法解析出 value,并根据 value 生成 Keyframe

5.      FloatParser.parse:

调用 JsonUtils.valueFromObject 解析出 float 值并将结果与传入的 scale 相乘后返回

6.      IntegerParser.parse:

调用调用 JsonUtils.valueFromObject 解析出 float 值并将结果与传入的 scale 相乘,再通过 Math.round 取出对应整值并返回

7.      PointFParser.parse:

如果 reader 中值是 NUMBER 类型,直接通过 JsonReader.nextDouble 与 scale 生成 PointF;若 reader 为数组或对象,则调用 JsonUtils.jsonToPoint 方法解析出 PointF

8.      ScaleXYParser.parse:

若是数组,就调用 JsonReader.beginArray 开始解析,否则就直接解析。解析 JsonReader 中连续的两个 double 值,并根据这两个值与 scale 生成 ScaleXY

9.      ShapeDataParser.parse:

(1)    c:解析成 closed

(2)    v:调用 JsonUitls.jsonToPoints 方法解析成 pointsArray

(3)    i:调用 JsonUtils.jsonToPoints 方法解析成 inTangents

(4)    o:调用 JsonUtils.jsonToPoints 方法解析成 outTangents

若解析的 pointsArray 为空,就构建空的 ShapeData

若 pointsArray 不为空,则先取出 pointsArray 中第一个值,将其设置为 initialPoint。然后从第二个值开始循环 pointsArray 中的值,在 for 循环中,根据 pointsArray 中当前的值与 inTangents 中当前值生成 shapeCp1,pointsArray 中前一个值与 outTangents 中前一个值生成 shapeCp2,然后通过 shapeCp1 与 shapeCp2 以及 pointsArray 当前值生成 CubicCurveData 并加入 curves 中。

然后判断 closed 的值,如果 closed 为 true(标志这个 Shape 是封闭图形),则将 pointsArray 第一值作为当前值,最后一个值作为前一个值,进行上一步 for 循环中的操作,并将生成的 CubicCurveData 也填入 curves 中。

最后根据 initialPoint、closed、curves 生成 ShapeData。

10.  DocumentDataParser.parse:

(1)    t:解析成 text

(2)    f:解析成 fontName

(3)    s:解析成 size

(4)    j:解析成 justification

(5)    tr:解析成 tracking

(6)    lh:解析成 lineHeight

(7)    ls:解析成 baselineShift

(8)    fc:调用 JsonUtils.jsonToColor 解析成 fillColor

(9)    sc:调用 JsonUtils.jsonToColor 解析成 strokeColor

(10) sw:解析成 strokeWidth

(11) of:解析成 strokeOverFill

最后通过上面解析的值生成 DocumentData

11.  ColorParser.parse:

若是数组,调用 JsonReader.beginArray 开始解析,否则直接解析。取 JsonReader 中连续的 4 个 double 值,分别解析成 r、g、b、a,若这四个值都小于,则说明它们被配置成色值的比例,将它们分别乘以 255,最后调用 Color.argb 生成颜色值(int 类型)

12.  GradientColorParser.parse:

若为数组,则调用 reader.beginArray 开始解析,否则直接解析。将 JsonReader 中全部的 double 值解析出并存入 array 中,然后将 array 中的数据每 4 个进行循环,这四个值中第一个存入 positions,第二、三、四分别为 r、g、b 值,通过 r、g、b 生成 color 值后存入 colors,之后通过 positions、colors 值生成 gradientColor 并通过 addOpacityStopsToGradientIfNeeded 设置透明度(透明度是存放在颜色值的后面,不被 4 整除的部分),最后将 gradientColor 返回给调用者

13.  JsonUtils:

1)      jsonToColor:取出传入的 JsonReader 中连续的 3 个 double 值,分别设置为 r、g、b,然后调用 Color.argb 方法生成颜色值

2)      jsonToPoints:若是数组,循环数组中的值,调用 jsonToPoint 解析出 PointF 并存入 points,最后将 points 返回

3)      jsonToPoint:

(1)    若传入的 JsonReader 为数字,调用 jsonNumbersToPoint 并返回

(2)    若传入的 JsonReader 为数组,调用 jsonArrayToPoint 并返回

(3)    若传入的 JsonReader 为对象,调用 jsonObjectToPoint 并返回

4)      jsonNumbersToPoint:

取出 JsonReader 连续的两个 double 值,分别置成 x、y,通过 x、y、scale 生成 PointF

5)      jsonArrayToPoint:

取出数组中连续的两个 double 值,分别置成 x、y,通过 x、y、scale 生成 PointF

6)      jsonObjectToPoint:

取出对象中的 x 值置为 x,对象中的 y 值置为 y,通过 x、y、scale 生成 PointF

7)      valueFromObject:

若传入的 JsonReader 为数字,返回第一个 double 值;若是数组,返回数组中第一个值。

五、Shape 解析(layers 中的 shapes):

LayerParser 会调用 ContentModelParser.parse 将 shapes 数组依次解析成 ContentModel 并存入 shapes 中。

ContentModleParser.parse 首先会解析 ty,之后根据 ty 解析成不同的 ContentModel(附录 2)。

1.      gr:调用 ShapeGroupParser.parse 解析成 ShapeGroup,这个是 Shape 组,里面会包含各种子 Shape。

ShapeGroupParser 中会调用 ContentModelParser.parse 将 it 数组中的内容依次解析成 ContentModel,并存入 items 中。

2.      st:调用 ShapeStrokeParser.parse 解析成 ShapeStroke,Shape 线条的信息。

ShapeStrokeParser.parse 中会解析如下字段:

1)      nm:name

2)      c:color,调用 AnimatableValueParser.parseColor 解析

3)      w:width,调用 AnimatableValueParser.parseFloat 解析

4)      o:opacity,调用 AnimatableValueParser.parseInteger 解析

5)      lc:capType,ShapeStroke.LineCapType 类型(附录 3)

6)      lj:joinType,ShapeStroke.LineJoinType 类型(附录 4)

7)      d:数组,首先会解析 n、v(v 会解析成 val):

(1)    n 为 o,将 val 赋值给 offset

(2)    n 为 d 或 g,将 val 添加到 lineDashPattern 中

解析完,如果 lineDashPattern 的个数如果是 1,就将该数据再加入 lineDashPattern 一次

                   最后会根据上面解析出的值生成 ShapeStroke

3.      gs:调用 GradientStrokeParser.parse 解析成 GradientStroke

1)      nm:name

2)      o:opacity

3)      t:gradientType,渐变类型(GradientType.Linear 或 GradientTypeRadial)

4)      s:startPoint

5)      e:endPoint

6)      w:width

7)      lc:capType

8)      lj:joinType

9)      d:lineDashPattern 或 offset

10)  g:会进一步解析 g 中的字段

(1)    p:points

(2)    k:color,调用 AnimatableValueParser.parseGradientColor 并传入 points 解析

最后根据解析的值生成 GradientStroke

4.      fl:调用 ShapeFillParser.parse 解析成 ShapeFill

1)      nm:name

2)      c:color

3)      o:opacity

4)      fillEnabled:fillEnabled,boolean 类型

5)      r:fillTypeInt,int 类型,之后会根据 fillTypeInt 的值设置 fillType

最后根据解析的值生成 ShapeFill

5.      gf:调用 GradientFillParser.parse 解析成 GradientFill

1)      nm:name

2)      g:color,AnimatableGradientColorValue 类型

3)      o:opacity

4)      t:gradientType

5)      s:startPoint

6)      e:endPoint

7)      r:fillType

6.      tr:调用 AnimatableTransFormParser.parse 解析成 AnimatableTransform

7.      sh:调用 ShapePathParser.parse 解析成 ShapePath,Shape 的绘制路径

1)      nm:name

2)      ind:ind

3)      ks:shape,调用 AnimatableValueParser.parseShapeData 解析

8.      el:调用 CircleShapeParser.parse 解析成 CirCleShape

1)      nm:name

2)      p:position

3)      s:size

4)      d:reversed,boolean 类型,通过 d 的值是否为 3 确定

9.      rc:调用 RectangleShapeParser.parse 解析成 RectangleShape

1)      nm:name

2)      p:position

3)      s:size

4)      r:roundedness,调用 AnimatableValueParser.parseFloat 解析

10.  tm:调用 ShapeTrimPathParser.parse 解析成 ShapeTrimPath

1)      s:start,AnimatableValueParser.parseFloat 解析

2)      e:end,AnimatableValueParser.parseFloat 解析

3)      o:offset,AnimatableValueParser.parseFloat 解析

4)      nm:name

5)      m:type,ShapeTrimPath.Type 类型,附录 5

11.  sr:调用 PolystarShapeParser.parse 解析成 PolystarShape

1)      nm:name

2)      sy:type,PolystarShape.Type,附录 6

3)      pt:points

4)      p:position

5)      r:totation

6)      or:outerRadius,AnimatableValueParser.parseFloat 解析

7)      os:outerRoundedness,AnimatableValueParser.parseFloat 解析

8)      ir:innerRadius,AnimatableValueParser.parseFloat 解析

9)      is:innerRoundedness,AnimatableValueParser.parseFloat 解析

12.  mm:调用 MergePathParser.parse 解析成 MergePath,只支持 KitKat 及之后的版本

1)      nm:name

2)      mode:mode,MergePaths.MergePathsMode 类型,附录 7

13.  rp:调用 RepeaterParser.parse 解析成 Repeater

1)      nm:name

2)      c:copies,AnimatableValueParser.parseFloat 解析

3)      o:offset,AnimatableValueParser.parseFloat 解析

4)      tr:transform

六、Assets 解析(assets):

LottieCompositionParser 中调用 parseAssets 方法解析 assets 中的内容。assets 中内容分两类,一类是图层信息,一类是图片信息:

1.      图层信息:

图层信息解析出的内容存放在 precomps 中,主要解析 id 与 layers 的内容。

assets 中的 layers 与外层的 layers 一样是调用 LayerParser.parse 解析

2.      图片信息(解析出的值会生成 LottieImageAsset 并存入 images 中):

1)      id:解析成 id

2)      w:解析成 width

3)      h:解析成 height

4)      u:解析成 relativeFolder

5)      p:解析成 imageFileName

七、LottieDrawable 构建:

动画 json 解析完成后,会生成 LottieComposition,onCompositionLoaded 方法中将 LottieComposition 设置给 LottieDrawable。

LottieDrawable 的 setComposition 方法会通过 buildCompositionLayer、animator.setComposition、setScale、updateBounds、等方法重新构建 LottieDrawable

buildCompositionLayer 会重新构建 LottieDrawable 中的 CompositionLayer。

animator.setComposition 会重置 animator(LottieValueAnimator)minFrame、maxFrame、frame 与 lastFrameTimeNs 的信息,minFrame 会取原 minFrame 与 composition 的 startFrame(json 中的 ip 值)中的最大值,maxFrame 会取原 maxFrame 与 composition 的 endFrame(json 中的 op 值)中的最小值,frame 会通过一系列比较获取。

八、CompositionLayer 构建:

CompositionLayer 中存储了动画的所有层级的信息。

它在 LottieDrawable 中构建

compositionLayer = new CompositionLayer(this,LayerParser.parse(composition), composition.getLayers(), composition);

LayerParser.parse(composition)构建了最外层的 Layer,高和宽是 json 最外层的 h、w 的值。

composition.getLayers()获取的是 json 最外层 layers 中的内容。

CompositionLayer 的构造方法会遍历传入的 composition.getLayers 中的 Layer,并将 Layer 通过 BaseLayer.forModel 方法,根据 layerType 生成不同的 BaseLayer,对应关系如下表:

layerType

BaseLayer 子类

备注

Shape(ty=4)

ShapeLayer

 

PreComp(ty =0)

CompositionLayer

也就是 Assets 中的内容

Solid(ty=1)

SolidLayer

 

Image(ty=2)

ImageLayer

 

Null(ty=3)

NullLayer

 

Text(ty=5)

TextLayer

 

Unknown/default

null

 

生成的 BaseLayer 会存入 layerMap。

之后会根据前一个 BaseLayer 的 matteType(配置文件中的 tt)值判断是否为 mattedLayer,若是 MattedLayer 就将当前的 BaseLayer 设置为前一个 BaseLayer 的 MatteLayer,否则就添加到 layers 最前的位置。下表标识该 layer 是否为 MattedLayer

matteType

是否为 MattedLayer

 

Add/Invert(tt=1 或 2)

 

其他情况

 

最后会遍历 layerMap,找到每一个 BaseLayer 的 parentLayer(根据 BaseLayer 的 parentId 查找,parentId 对应于 json 中的 parent),并设置到该 BaseLayer 中。

九、绘制:

Json 解析完成后,会调用 setComposition 方法,该方法中会重新设置 LottieComposition,并调用 LottieDrawable.setComposition 方法刷新 LottieDrawable。

上面步骤完成后会调用 setImageDrawable、requestLayout 方法重绘 LottieAnimationView。

LottieAnimationView 的 onDraw 方法会调用 LottieDrawable 的 draw 方法(详细步骤需要查看 ImageView 的绘制流程)。下面具体分析下 LottieDrawable 的 draw 流程。

首先会确定动画的 scale,scale 是通过动画外层的 width 与 height 与 canvas 的比例确定,取高、宽比例中较小的值。若 scale 后的动画大于 canvas,会调用 canvas 的 translate、scale 方法重置 canvas。然后会将获取的 scale 填入矩阵 matrix。

之后会调用 BaseLayer.draw 方法绘制动画中的全部 Layer,具体步骤如下:

BaseLayer.draw()方法中有三个参数,分别是 canvas(画布)、parentMatrix(最外层动画 Matrix)、parentAlpha(最外层透明度)。

1.      调用 buildParentLayerListIfNeeded 方法构建出 CompositionLayer 的全部 parentLayer 并添加到 parentLayers 中。

2.      会将 parentMatrix 设置为 matrix,并将 parentLayers 中的 Layer 动画 Matrix 与传入的 matrix 相乘。

3.      然后,根据传入的 parentAlpha 与该 BaseLayer 的 transform 的 opacity 属性(即 json 中{“layers”:[{“ks” : {“o”: {…}}}]}中从 o 中解析出来的值,一般就是 o 中 k 的值)计算出透明度

4.      如果该 BaseLayer 没有 matteLayer 与 mask,就将 canvas、matrix、alpha 传入 drawLayer 方法进一步绘制,LottieDrawalbe 中调用的是 CompositionLayer 的 drawLayer 方法

十、CompositionLayer 绘制:

1.      CompositionLayer 的 drawLayer 中首先保存 canvas 状态, 然后通过构建时生成的 Layer 的宽、高(具体分析见 CompositionLayer 构建)设置到 newClipRect(RectF 类型),然后将 newClipRect 按上步传入的 parentMatrix 进行变换。

2.      之后,会循环 CompositionLayer 中的 layers(见 CompositionLayer 的构建),并调用对应 BaseLayer 的 draw 方法将所有 layer 依次绘制出来,同时,会按照 newClipRect 重新裁剪 canva(这段代码可能会造成动画显示不全)。最后,将 canvas 设置回之前的状态

十一、      ShapeLayer 构建:

CompostionLayer 构造方法中遍历解析出的 layers(见三),通过 BaseLayer.forModel 根据生成对应的 BaseLayer。
ShapeLayer 对应的 type 为 4,BaseLayer.forModel 直接调用 ShapeLayer 构造函数生成 ShapeLayer。

ShapeLayer 构造函数首先通过 Layer 中的 shapes(List)构建 shapeGroup,然后通过 shapeGroup 构建出 contentGroup。(lottie 文件中也可能会配置 ShapeGroup 即”gr”。之后会调用 ContentGroup 的 setContents,setContents 会循环 ContentGroup 中的 contents,并调用 Content.setContents 方法将其前后的 contents 传入。
ContentGroup 构造方法中:

1.      通过 contentsFromModels 方法将 Layer 中的 shapes(即 List)通过 toContent 方法全部转换成对应的 Content(附录 2),并存入 contents 中

2.      通过 findTransform 方法取出该 ShapeLayer 对应的 AnimatableTransform(”tr”),然后通过 AnimatableTransform 生成 TransformKeyframeAnimation 赋值给 transformAnimation

3.      找出 contents 中全部的 GreedyContent 子类存入 greedyContents 中,并对 greedyContents 进行进一步操作。

十二、      ShapeLayer 绘制:

ShapeLayer 的 drawLayer 会委托给 contentGroup.draw 方法进行。contentGroup.draw 中首先会处理动画与透明度,之后遍历构造时生成的 contents,调用其中 DrawingContent 的子类的 draw 进行实际的绘制。

DrawingContent 子类见附录 2

DrawingConteng 实际上有 3 种:

1.      画线图(StrokeContent、GradientStrokeContent):

StrokeContent 与 GradientStrokeContent 的区别是线条的颜色是否为渐变色。

2.      画填充图(FillContent、GradientFillContent):

FillContent 与 GradientFillContent 的区别是填充色是否为渐变色。

3.      画需要重复的图(RepeaterContent):

十三、      StrokeContent 构建与绘制:

StrokeContent 是 BaseStrokeContent 的子类,大部分逻辑都在 BaseStrokeContent 中,以下是 BaseStrokeContent 的分析:

1.      构造函数:

构造函数中主要是完成的 Paint 以及 Animation 的初始化。

2.      setContents:

setContents 会遍历与其同处一个 ContentGroup 下的其他 Content,找出其中的 TrimPathContent 与其对应的全部 PathContent,然后生成 PathGroup 并存入 pathGroups 中

3.      draw:

1)      通过配置中的 width、color、alpha 对 paint 进行设置。

2)      遍历 pathGroups,对每一个 PathGroup 分情况处理:

(1)    若 pathGroup 中 trimPath 不为空,则调用 applyTrimPath 方法,applyTrimPath 方法中会通过 PathMeasure 根据 TrimPathContent 对 PathContent 构成的 Path 进行处理。

PathMeasure 用法可以参考:

https://blog.csdn.net/u013831257/article/details/51565591

(2)    若 pathGroup 中 trimPath 为空,直接通过 canvas.drawPath 绘制 PathContent 构成的 Path

十四、      动画过程:

Lottie 动画过程是通过 LottieValueAnimator 进行控制,animator 中会维护两个容器,一个存放向 Lottie 中注册的全部 ValueAnimator.AnimatorUpdateListener,一个存放向 Lottie 中注册的全部 AnimatorListener。

ValueAnimator.AnimatorUpdateListener 用于动画更新的监听,回调接口是 onAnimationUpdate。

AnimatorListener 用于动画开始、结束、取消、重复等监听。

LottieDrawable 构建的时候会向 animator 注册一个 AnimatorUpdateListener 的监听,这个监听会收到 animator 发送的动画更新的回调,在此回调中调用 compositionLayer 设置 progress 实现动画的更新。

十五、      附录:

1.      LayerType 含义:

LayerType 对应于 json 文件 layers 中的 ty 字段,映射关系如下:

ty

 

LayerType

含义

0

 

PreComp

 

1

 

Solid

 

2

 

Image

图片

3

 

Null

 

4

 

Shape

绘制形状

5

 

Text

文字

6

 

Unknown

 

 

2.      ContentModel(shape)中 type 含义,标黄的是 DrawingConent 的子类:

ty

ContentModel

toContent 类型

备注

gr

ShapeGroup

ContentGroup

Shape 组,里面包含子 Shape

st

ShapeStroke

StrokeContent

Shape 描边信息,如线的颜色、宽度、透明度等

gs

GradientStroke

GradientStrokeContent

 

fl

ShapeFill

FillContent

 

gf

GradientFill

GradientFillContent

 

tr

AnimatableTransform

null

Shape 的动画

sh

ShapePath

ShapeContent

Shape 的绘制路径

el

CircleShape

EllipseContent

 

rc

RectangleShape

RectangleContent

 

tm

ShapeTrimPath

TrimPathContent

修剪路径

sr

PolystarShape

PolystarContent

 

mm

MergePaths

MergePathsContent

 

rp

Repeater

RepeaterContent

 

 

3.      ShapeStroke.LineCapType 类型:

index

ShapeStroke.LineCapType 类型

Paint.Cap(线帽子)

0

LineCapType.Butt

Paint.Cap.BUTT 无线帽

1

LineCapType.Round

Paint.Cap.ROUND 圆线帽

2

LineCapType.Unknown

Paint.Cap.SQUARE 方线帽

 

4.      ShapeStroke.LineJoinType 类型:

index

ShapeStroke.LineJoinType 类型

Paint.Join(线段连接样式)

0

Miter

Paint.Join.MITER 锐角连接

1

Round

Paint.Join.ROUND 圆弧连接

2

Bevel

Paint.Join.BEVEL 斜接

 

5.      ShapeTrimPath.Type 类型:

id

ShapeTrimPath.Type 类型

备注

1

Simultaneously

 

2

Individually

 

 

6.      PolystarShape.Type 类型:

value

PolystarShape.Type 类型

备注

1

Star

 

2

Polygon

 

                  

7.      MergePaths.MergePathsMode 类型:

id

MergePaths.MergePathsMode

备注

1

Merge

 

2

Add

 

3

Subtract

 

4

Intersect

 

5

ExcludeIntersections

  • lottie
    1 引用
  • Android

    Android 是一种以 Linux 为基础的开放源码操作系统,主要使用于便携设备。2005 年由 Google 收购注资,并拉拢多家制造商组成开放手机联盟开发改良,逐渐扩展到到平板电脑及其他领域上。

    334 引用 • 323 回帖 • 4 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • Spark

    Spark 是 UC Berkeley AMP lab 所开源的类 Hadoop MapReduce 的通用并行框架。Spark 拥有 Hadoop MapReduce 所具有的优点;但不同于 MapReduce 的是 Job 中间输出结果可以保存在内存中,从而不再需要读写 HDFS,因此 Spark 能更好地适用于数据挖掘与机器学习等需要迭代的 MapReduce 的算法。

    74 引用 • 46 回帖 • 559 关注
  • 百度

    百度(Nasdaq:BIDU)是全球最大的中文搜索引擎、最大的中文网站。2000 年 1 月由李彦宏创立于北京中关村,致力于向人们提供“简单,可依赖”的信息获取方式。“百度”二字源于中国宋朝词人辛弃疾的《青玉案·元夕》词句“众里寻他千百度”,象征着百度对中文信息检索技术的执著追求。

    63 引用 • 785 回帖 • 164 关注
  • 微软

    微软是一家美国跨国科技公司,也是世界 PC 软件开发的先导,由比尔·盖茨与保罗·艾伦创办于 1975 年,公司总部设立在华盛顿州的雷德蒙德(Redmond,邻近西雅图)。以研发、制造、授权和提供广泛的电脑软件服务业务为主。

    8 引用 • 44 回帖
  • 友情链接

    确认过眼神后的灵魂连接,站在链在!

    24 引用 • 373 回帖
  • Caddy

    Caddy 是一款默认自动启用 HTTPS 的 HTTP/2 Web 服务器。

    12 引用 • 54 回帖 • 159 关注
  • Postman

    Postman 是一款简单好用的 HTTP API 调试工具。

    4 引用 • 3 回帖 • 7 关注
  • 电影

    这是一个不能说的秘密。

    121 引用 • 604 回帖 • 1 关注
  • 微服务

    微服务架构是一种架构模式,它提倡将单一应用划分成一组小的服务。服务之间互相协调,互相配合,为用户提供最终价值。每个服务运行在独立的进程中。服务于服务之间才用轻量级的通信机制互相沟通。每个服务都围绕着具体业务构建,能够被独立的部署。

    96 引用 • 155 回帖
  • FreeMarker

    FreeMarker 是一款好用且功能强大的 Java 模版引擎。

    23 引用 • 20 回帖 • 464 关注
  • Swift

    Swift 是苹果于 2014 年 WWDC(苹果开发者大会)发布的开发语言,可与 Objective-C 共同运行于 Mac OS 和 iOS 平台,用于搭建基于苹果平台的应用程序。

    36 引用 • 37 回帖 • 535 关注
  • ReactiveX

    ReactiveX 是一个专注于异步编程与控制可观察数据(或者事件)流的 API。它组合了观察者模式,迭代器模式和函数式编程的优秀思想。

    1 引用 • 2 回帖 • 161 关注
  • CongSec

    本标签主要用于分享网络空间安全专业的学习笔记

    1 引用 • 1 回帖 • 15 关注
  • 大数据

    大数据(big data)是指无法在一定时间范围内用常规软件工具进行捕捉、管理和处理的数据集合,是需要新处理模式才能具有更强的决策力、洞察发现力和流程优化能力的海量、高增长率和多样化的信息资产。

    93 引用 • 113 回帖
  • NetBeans

    NetBeans 是一个始于 1997 年的 Xelfi 计划,本身是捷克布拉格查理大学的数学及物理学院的学生计划。此计划延伸而成立了一家公司进而发展这个商用版本的 NetBeans IDE,直到 1999 年 Sun 买下此公司。Sun 于次年(2000 年)六月将 NetBeans IDE 开源,直到现在 NetBeans 的社群依然持续增长。

    78 引用 • 102 回帖 • 683 关注
  • TextBundle

    TextBundle 文件格式旨在应用程序之间交换 Markdown 或 Fountain 之类的纯文本文件时,提供更无缝的用户体验。

    1 引用 • 2 回帖 • 53 关注
  • 工具

    子曰:“工欲善其事,必先利其器。”

    288 引用 • 734 回帖 • 2 关注
  • Unity

    Unity 是由 Unity Technologies 开发的一个让开发者可以轻松创建诸如 2D、3D 多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎。

    25 引用 • 7 回帖 • 159 关注
  • Rust

    Rust 是一门赋予每个人构建可靠且高效软件能力的语言。Rust 由 Mozilla 开发,最早发布于 2014 年 9 月。

    58 引用 • 22 回帖
  • 负能量

    上帝为你关上了一扇门,然后就去睡觉了....努力不一定能成功,但不努力一定很轻松 (° ー °〃)

    88 引用 • 1235 回帖 • 410 关注
  • Pipe

    Pipe 是一款小而美的开源博客平台。Pipe 有着非常活跃的社区,可将文章作为帖子推送到社区,来自社区的回帖将作为博客评论进行联动(具体细节请浏览 B3log 构思 - 分布式社区网络)。

    这是一种全新的网络社区体验,让热爱记录和分享的你不再感到孤单!

    132 引用 • 1114 回帖 • 125 关注
  • 持续集成

    持续集成(Continuous Integration)是一种软件开发实践,即团队开发成员经常集成他们的工作,通过每个成员每天至少集成一次,也就意味着每天可能会发生多次集成。每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽早地发现集成错误。

    15 引用 • 7 回帖
  • Bug

    Bug 本意是指臭虫、缺陷、损坏、犯贫、窃听器、小虫等。现在人们把在程序中一些缺陷或问题统称为 bug(漏洞)。

    76 引用 • 1737 回帖 • 1 关注
  • Jenkins

    Jenkins 是一套开源的持续集成工具。它提供了非常丰富的插件,让构建、部署、自动化集成项目变得简单易用。

    53 引用 • 37 回帖 • 3 关注
  • 区块链

    区块链是分布式数据存储、点对点传输、共识机制、加密算法等计算机技术的新型应用模式。所谓共识机制是区块链系统中实现不同节点之间建立信任、获取权益的数学算法 。

    91 引用 • 751 回帖 • 1 关注
  • 自由行
    4 关注
  • API

    应用程序编程接口(Application Programming Interface)是一些预先定义的函数,目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力,而又无需访问源码,或理解内部工作机制的细节。

    77 引用 • 430 回帖 • 1 关注
  • 学习

    “梦想从学习开始,事业从实践起步” —— 习近平

    171 引用 • 512 回帖