Lottie源码简单分析以及使用

Github上的Lottie

LottieComposition

  public ArrayList<String> getWarnings() {
    return new ArrayList<>(Arrays.asList(warnings.toArray(new String[warnings.size()])));
  }

这个方法通过HashSet<String>转换为ArrayList来获取warnings保存的一些报错日志。其中Array做为其中的转换桥梁。

Layer layerModelForId(long id) {
    return layerMap.get(id);
  }

Layer主要是AE的图层概念转换,以Model的形式保存在layerMap。其中layerMap的数据来源于AE导出的json解析出来的layers数组。每个Layer具有对应的id,这个方法就是通过id找到对应的Layer。在LottieComposition这个类里面有四个数据集合,分为precomps、images、layerMap、layers。它们获取数据简单流程图如下

图1.png

其中
1. fromAssetFileName(Context context, String fileName,OnCompositionLoadedListener loadedListener)
2. fromFileSync(Context context, String fileName)
3. fromJson(Resources res, JSONObject json,OnCompositionLoadedListener loadedListener)
4. fromInputStream(Context context, InputStream stream,OnCompositionLoadedListener loadedListener)
5. fromInputStream(Resources res, InputStream stream)
6. fromJsonSync(Resources res, JSONObject json)

我们可以通过上面的任何一个方法设置要做动画的json文件

LottieComposition其他的一些属性,会在fromJsonSyncjson格式里面读取它们对应的初始值

  private final Rect bounds; //绘制动画的范围
  private final long startFrame;   // 开始帧数
  private final long endFrame; // 结束帧数
  private final int frameRate; //帧数率
  private final float dpScale; //缩放大小

LottieDrawable

自定义的一个Drawable,主要是管控动画的start、resume、pause、reverse等等,以及创建compositionLayer,为绘制动画的图层做准备,简单逻辑如下

图2.png

在我们使用的动画的fragment里面可以调用LottieAnimationViewresumeAnimation()、pasureAnimation()、reverseAnimation()等方法来控制动画的状态。然后然后通过LottieAnimationView分配到创建的LottieDrawable里面的对应的各个方法,然后对动画进行各种状态处理。LottieDrawable大概代码如下

private final ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
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());
        }
      }
    });
  }
public void resumeAnimation() {
    playAnimation(true);
  }
private void playAnimation(boolean setStartTime) {
    ...
    long playTime = setStartTime ? (long) (progress * animator.getDuration()) : 0;
    animator.start();
    if (setStartTime) {
      animator.setCurrentPlayTime(playTime);
    }
  }
public void cancelAnimation() {
    ...
    animator.cancel();
  }
 private void reverseAnimation(boolean setStartTime) {
   ...
    if (setStartTime) {
      animator.setCurrentPlayTime((long) (progress * animator.getDuration()));
    }
    animator.reverse();
  }

同时LottieDrawable也会在LottieAnimationView调用的setComposition()方法里面创建一个CompositionLayer对象,通过这个对象实现动画图层的各种绘制。大概代码如下

public boolean setComposition(LottieComposition composition) {
    ...
    buildCompositionLayer();
    applyColorFilters();

    setProgress(progress);
    ...

    return true;
  }

  private void buildCompositionLayer() {
    compositionLayer = new CompositionLayer(
        this, Layer.Factory.newInstance(composition), composition.getLayers(), composition);
  }

然后动画的执行后,会在动画的AnimatorUpdateListener回调中调用compositionLayersetProgress()方法,从而开始执行动画。

Layer

通过上文setProgress()方法的跟踪,最终会发现在BaseKeyframeAnimation类里面可以找到AnimationListener接口的onValueChanged()的调用

  void setProgress(@FloatRange(from = 0f, to = 1f) float progress) {
    ...
    for (int i = 0; i < listeners.size(); i++) {
      listeners.get(i).onValueChanged();
    }
  }

BaseKeyframeAnimation.AnimationListener接口,可以在BaseLayer里面有实现

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

通过invalidateSelf()方法的调用,LottieDrawabledraw()方法也得到不断的执行,从而驱使BaseLayerdraw()方法也得到执行,draw()方法如下

  @Override
  public void draw(Canvas canvas, Matrix parentMatrix, int parentAlpha) {
   ...
    drawLayer(canvas, matrix, alpha);
   ...
  }

通过drawLayer()这个抽象方法,实现各种不同的Layer效果,主要以下几个Layer

图3.png

其中会在CompositionLayer构造函数里面初始化不同的Layer

  CompositionLayer(LottieDrawable lottieDrawable, Layer layerModel, List<Layer> layerModels,
      LottieComposition composition) {
    super(lottieDrawable, layerModel);
     ...
    for (int i = layerModels.size() - 1; i >= 0; i--) {
      Layer lm = layerModels.get(i);
      BaseLayer layer = BaseLayer.forModel(lm, lottieDrawable, composition);
      ...
     }
...
  }

BaseLayer.java

根据不同的LayerType绘制不同的图层

 @Nullable
  static BaseLayer forModel(
    Layer layerModel, LottieDrawable drawable, LottieComposition composition) {
    switch (layerModel.getLayerType()) {
      case Shape:
        return new ShapeLayer(drawable, layerModel);
      case PreComp:
        return new CompositionLayer(drawable, layerModel,
            composition.getPrecomps(layerModel.getRefId()), composition);
      case Solid:
        return new SolidLayer(drawable, layerModel);
      case Image:
        return new ImageLayer(drawable, layerModel, composition.getDpScale());
      case Null:
        return new NullLayer(drawable, layerModel);
      case Text:
      case Unknown:
      default:
        // Do nothing
        Log.w(L.TAG, "Unknown layer type " + layerModel.getLayerType());
        return null;
    }
  }

LayerTypeLayer里面赋值

static Layer newInstance(JSONObject json, LottieComposition composition) {
....
  LayerType layerType;
      int layerTypeInt = json.optInt("ty", -1);
      if (layerTypeInt < LayerType.Unknown.ordinal()) {
        layerType = LayerType.values()[layerTypeInt];
      } else {
        layerType = LayerType.Unknown;
      }
....
}

BaseLayer.java里面

 @Override public void onValueChanged() {
    invalidateSelf();
  }

通过实现接口BaseKeyframeAnimation.AnimationListener,不断重绘,实现动画
从而调用draw()方法

  @Override
  public void draw(Canvas canvas, Matrix parentMatrix, int parentAlpha) {
    if (!visible) {
      return;
    }
    buildParentLayerListIfNeeded();
    matrix.reset();
    matrix.set(parentMatrix);
    for (int i = parentLayers.size() - 1; i >= 0; i--) {
      matrix.preConcat(parentLayers.get(i).transform.getMatrix());
    }
    int alpha = (int)
        ((parentAlpha / 255f * (float) transform.getOpacity().getValue() / 100f) * 255);
    if (!hasMatteOnThisLayer() && !hasMasksOnThisLayer()) {
      matrix.preConcat(transform.getMatrix());
      drawLayer(canvas, matrix, alpha);
      return;
    }

    rect.set(0, 0, 0, 0);
    getBounds(rect, matrix);
    intersectBoundsWithMatte(rect, matrix);

    matrix.preConcat(transform.getMatrix());
    intersectBoundsWithMask(rect, matrix);

    rect.set(0, 0, canvas.getWidth(), canvas.getHeight());

    canvas.saveLayer(rect, contentPaint, Canvas.ALL_SAVE_FLAG);
    // Clear the off screen buffer. This is necessary for some phones.
    clearCanvas(canvas);
    drawLayer(canvas, matrix, alpha);

    if (hasMasksOnThisLayer()) {
      applyMasks(canvas, matrix);
    }

    if (hasMatteOnThisLayer()) {
      canvas.saveLayer(rect, mattePaint, SAVE_FLAGS);
      clearCanvas(canvas);
      //noinspection ConstantConditions
      matteLayer.draw(canvas, parentMatrix, alpha);
      canvas.restore();
    }

    canvas.restore();
  }

Lottie的原理

首先会通过LottieComposition.Factory的对应类型设置 json资源文件,然后再fromJsonSync方法里面会把json文件解析出图层的大小并且绘制相应的图片资源文件和图层。资源加载完后,会在回调里面设置LottieAnimationViewComposition,从而调用LottieDrawablesetComposition()方法,在setComposition方法里面会通过buildCompositionLayer()方法去创建一个CompositionLayer图层,其中CompositionLayer继承BaseLayer,通过BaseLayerforModel()静态方法获取不同的图层类型,然后LottieDrawablesetComposition()方法里面会开始执行一个ValueAnimation动画,这个动画会驱使BaseLayerdraw()方法不断执行,通过Matrix的矩阵形式不断的绘制各个图层从而形成动画,而这些图层的矩阵变换的数据来源于BaseKeyframeAnimation里面有一个Keyframe对象会去Json里面获取相应得数据。

简单使用

  • 方式1
fun createFromAssetFile(lottieAnimView: LottieAnimationView, fileName: String, init: (LottieAnimationView.() -> Unit)?) {
        lottieAnimView.setAnimation(fileName)
        if(init!=null)
        lottieAnimView.init()
        lottieAnimView.playAnimation()
    }

直接传入assets里面的.json文件名字,然后LottieComposition通过fromInputStream()等方法读取文件转换为string类型,从而得到JsonObject对象,最后在fromJsonSync()方法里面绘制图层

  • 方式2
 private val assetFolders = object : HashMap<String, String>() {
        init {
            put("WeAccept.json", "Images/WeAccept")
        }
    }
fun createFromAssetFile(context: Context,lottieAnimView: LottieAnimationView,
                                     fileName: String, init: (LottieAnimationView.() -> Unit)?) {
        lottieAnimView.imageAssetsFolder = assetFolders[fileName]
        LottieComposition.Factory.fromAssetFileName(context,fileName) { composition ->
            lottieAnimView.setComposition(composition!!)
            if(init!=null)
                lottieAnimView.init()
            lottieAnimView.playAnimation()
        }
    }

其中imageAssetsFolder是设置有些json动画需要的图片资源文件,然后通过LottieComposition里面对应的类型设置json动画资源,LottieComposition.Factory里面主要有如下几种方式

图3.png

  • 方式3

从手机文件夹里面选择 json文件

val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "*/*"
intent.addCategory(Intent.CATEGORY_OPENABLE)
startActivityForResult(Intent.createChooser(intent,"Select a JSON file"),RC_FILE)

得到选择json文件,然后得到InputStream对象,从而通过fromInputStream启动动画

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if(resultCode != Activity.RESULT_OK) return
        when(requestCode){
            RC_FILE -> onFileLoaded(data!!.data)
        }
    }
    fun onFileLoaded(uri:Uri){
        var inputStream: InputStream?= null
        try {
            when(uri.scheme){
                "file" -> {
                    inputStream  = FileInputStream(uri.path)
                }
                "content" -> {
                    inputStream = contentResolver.openInputStream(uri)
                }
                 else -> onLoadError()
            }
            lottieAnim.cancelAnimation()
            if(inputStream!=null)
          createFromFileAnim(ctx,lottieAnim,inputStream)
        }catch (e: FileNotFoundException){
            onLoadError()
        }
    }

    private fun onLoadError() {
        Snackbar.make(drawableLayout!!, "Failed to load animation", Snackbar.LENGTH_LONG).show()
    }
 fun createFromFileAnim(ctx: Context, lottieAnimView: LottieAnimationView, inputStream: InputStream) {
        LottieComposition.Factory.fromInputStream(ctx,inputStream) { composition ->
            if (composition == null) {
                return@fromInputStream
            }
            lottieAnimView.setComposition(composition)
            lottieAnimView.playAnimation()
        }
    }
  • 方式4
 fun createJsonAnim(ctx: Context,lottieAnimView: LottieAnimationView, jsonString: String) {
        try {
            val json = JSONObject(jsonString)
            LottieComposition.Factory
                    .fromJson(ctx.resources, json, OnCompositionLoadedListener { composition ->
                        if (composition == null) {
                            return@OnCompositionLoadedListener
                        }
                        lottieAnimView.setComposition(composition)
                        lottieAnimView.playAnimation()
                    })
        } catch (e: JSONException) {

        }
    }

最后,写了一个自己的LottieSimle,小小学习下

LottieSimple

猜你喜欢

转载自blog.csdn.net/iamzgx/article/details/74530825