Lottie 源码浅探

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

前置知识:

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 回帖 • 1 关注

相关帖子

欢迎来到这里!

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

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

推荐标签 标签

  • WebComponents

    Web Components 是 W3C 定义的标准,它给了前端开发者扩展浏览器标签的能力,可以方便地定制可复用组件,更好的进行模块化开发,解放了前端开发者的生产力。

    1 引用
  • C++

    C++ 是在 C 语言的基础上开发的一种通用编程语言,应用广泛。C++ 支持多种编程范式,面向对象编程、泛型编程和过程化编程。

    107 引用 • 153 回帖
  • wolai

    我来 wolai:不仅仅是未来的云端笔记!

    2 引用 • 14 回帖
  • Openfire

    Openfire 是开源的、基于可拓展通讯和表示协议 (XMPP)、采用 Java 编程语言开发的实时协作服务器。Openfire 的效率很高,单台服务器可支持上万并发用户。

    6 引用 • 7 回帖 • 94 关注
  • RIP

    愿逝者安息!

    8 引用 • 92 回帖 • 351 关注
  • 京东

    京东是中国最大的自营式电商企业,2015 年第一季度在中国自营式 B2C 电商市场的占有率为 56.3%。2014 年 5 月,京东在美国纳斯达克证券交易所正式挂牌上市(股票代码:JD),是中国第一个成功赴美上市的大型综合型电商平台,与腾讯、百度等中国互联网巨头共同跻身全球前十大互联网公司排行榜。

    14 引用 • 102 回帖 • 376 关注
  • 机器学习

    机器学习(Machine Learning)是一门多领域交叉学科,涉及概率论、统计学、逼近论、凸分析、算法复杂度理论等多门学科。专门研究计算机怎样模拟或实现人类的学习行为,以获取新的知识或技能,重新组织已有的知识结构使之不断改善自身的性能。

    83 引用 • 37 回帖 • 1 关注
  • 面试

    面试造航母,上班拧螺丝。多面试,少加班。

    325 引用 • 1395 回帖
  • TGIF

    Thank God It's Friday! 感谢老天,总算到星期五啦!

    287 引用 • 4484 回帖 • 669 关注
  • V2EX

    V2EX 是创意工作者们的社区。这里目前汇聚了超过 400,000 名主要来自互联网行业、游戏行业和媒体行业的创意工作者。V2EX 希望能够成为创意工作者们的生活和事业的一部分。

    17 引用 • 236 回帖 • 327 关注
  • GAE

    Google App Engine(GAE)是 Google 管理的数据中心中用于 WEB 应用程序的开发和托管的平台。2008 年 4 月 发布第一个测试版本。目前支持 Python、Java 和 Go 开发部署。全球已有数十万的开发者在其上开发了众多的应用。

    14 引用 • 42 回帖 • 764 关注
  • 学习

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

    169 引用 • 506 回帖
  • SOHO

    为成为自由职业者在家办公而努力吧!

    7 引用 • 55 回帖 • 19 关注
  • JRebel

    JRebel 是一款 Java 虚拟机插件,它使得 Java 程序员能在不进行重部署的情况下,即时看到代码的改变对一个应用程序带来的影响。

    26 引用 • 78 回帖 • 664 关注
  • 酷鸟浏览器

    安全 · 稳定 · 快速
    为跨境从业人员提供专业的跨境浏览器

    3 引用 • 59 回帖 • 26 关注
  • 区块链

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

    91 引用 • 751 回帖 • 2 关注
  • 职场

    找到自己的位置,萌新烦恼少。

    127 引用 • 1705 回帖 • 1 关注
  • Gitea

    Gitea 是一个开源社区驱动的轻量级代码托管解决方案,后端采用 Go 编写,采用 MIT 许可证。

    4 引用 • 16 回帖 • 5 关注
  • Unity

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

    25 引用 • 7 回帖 • 173 关注
  • 前端

    前端技术一般分为前端设计和前端开发,前端设计可以理解为网站的视觉设计,前端开发则是网站的前台代码实现,包括 HTML、CSS 以及 JavaScript 等。

    247 引用 • 1348 回帖
  • JavaScript

    JavaScript 一种动态类型、弱类型、基于原型的直译式脚本语言,内置支持类型。它的解释器被称为 JavaScript 引擎,为浏览器的一部分,广泛用于客户端的脚本语言,最早是在 HTML 网页上使用,用来给 HTML 网页增加动态功能。

    729 引用 • 1327 回帖
  • SEO

    发布对别人有帮助的原创内容是最好的 SEO 方式。

    35 引用 • 200 回帖 • 22 关注
  • 房星科技

    房星网,我们不和没有钱的程序员谈理想,我们要让程序员又有理想又有钱。我们有雄厚的房地产行业线下资源,遍布昆明全城的 100 家门店、四千地产经纪人是我们坚实的后盾。

    6 引用 • 141 回帖 • 585 关注
  • 以太坊

    以太坊(Ethereum)并不是一个机构,而是一款能够在区块链上实现智能合约、开源的底层系统。以太坊是一个平台和一种编程语言 Solidity,使开发人员能够建立和发布下一代去中心化应用。 以太坊可以用来编程、分散、担保和交易任何事物:投票、域名、金融交易所、众筹、公司管理、合同和知识产权等等。

    34 引用 • 367 回帖
  • MySQL

    MySQL 是一个关系型数据库管理系统,由瑞典 MySQL AB 公司开发,目前属于 Oracle 公司。MySQL 是最流行的关系型数据库管理系统之一。

    690 引用 • 535 回帖
  • Sym

    Sym 是一款用 Java 实现的现代化社区(论坛/BBS/社交网络/博客)系统平台。

    下一代的社区系统,为未来而构建

    524 引用 • 4601 回帖 • 700 关注
  • SendCloud

    SendCloud 由搜狐武汉研发中心孵化的项目,是致力于为开发者提供高质量的触发邮件服务的云端邮件发送平台,为开发者提供便利的 API 接口来调用服务,让邮件准确迅速到达用户收件箱并获得强大的追踪数据。

    2 引用 • 8 回帖 • 483 关注