天天看點

Android Lottie 使用以及源碼解析

Airbnb在GitHub上面開源了一個項目lottie-android,最近火的不要不要的,牢牢占據Trending排行榜(日、周、月)首位,下面我們就見識一下這個項目。 

首先放上Lottie在GitHub上面的項目位址:Android,iOS, 和React Native。

Lottie簡介

Lottie是一個為Android和IOS裝置提供的一個開源架構,它能夠解析通過Adobe After Effects 軟體做出來的動畫,動畫檔案通過Bodymovin導出json檔案,就可以通過Lottie中的LottieAnimationView來使用了。 

Bodymovin是一個After Effects的插件,它由Hernan Torrisi開發。 

我們先看看官方給出的實作的動畫效果:

Android Lottie 使用以及源碼解析
Android Lottie 使用以及源碼解析
Android Lottie 使用以及源碼解析
Android Lottie 使用以及源碼解析
Android Lottie 使用以及源碼解析

這些動畫如果讓你實作起來,你可能會覺得很麻煩,但是通過Lottie這一切就變得很容易。 

想了解更多請參考官方介紹

使用方法

首先由視覺設計師通過Adobe After Effects做好這些動畫,這個比我們用代碼來實作會容易的很多,然後Bodymovin導出json檔案,這些json檔案描述了該動畫的一些關鍵點的坐标以及運動軌迹,然後把json檔案放到項目的app/src/main/assets目錄下,代碼中在build.gradle中添加依賴:

dependencies {
  compile 'com.airbnb.android:lottie:1.0.1'
}           
  • 1
  • 2
  • 3

在布局檔案上加上:

<com.airbnb.lottie.LottieAnimationView
        android:id="@+id/animation_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:lottie_fileName="hello-world.json"
        app:lottie_loop="true"           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

或者代碼中實作:

LottieAnimationView animationView = (LottieAnimationView) findViewById(R.id.animation_view);
animationView.setAnimation("hello-world.json");
animationView.loop(true);           
  • 1
  • 2
  • 3

此方法将加載檔案并在背景解析動畫,并在完成後異步開始呈現動畫。 

Lottie隻支援Jellybean (API 16)或以上版本。 

通過源碼我們可以發現

LottieAnimationView

是繼承自

AppCompatImageView

,我們可以像使用其他

View

一樣來使用它。

LottieAnimationView animationView = (LottieAnimationView) findViewById(R.id.animation_view);           
  • 1

甚至可以從網絡上下載下傳json資料:

LottieComposition composition = LottieComposition.fromJson(getResources(), jsonObject, (composition) -> {
     animationView.setComposition(composition);
     animationView.playAnimation();
 });           
  • 1
  • 2
  • 3
  • 4

或者使用

setAnimation(JSONObject);           
  • 1

我們還可以控制動畫或者添加監聽器:

animationView.addAnimatorUpdateListener((animation) -> {
    // Do something.
});
animationView.playAnimation();
...
if (animationView.isAnimating()) {
    // Do something.
}
...
animationView.setProgress(5f);
...
// Custom animation speed or duration.
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f)
    .setDuration();
animator.addUpdateListener(animation -> {
    animationView.setProgress(animation.getAnimatedValue());
});
animator.start();
...
animationView.cancelAnimation();           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

LottieAnimationView

是使用

LottieDrawable

來渲染動畫的,如果有必要,還可以直接使用

LottieDrawable

LottieDrawable drawable = new LottieDrawable();
LottieComposition.fromAssetFileName(getContext(), "hello-world.json", (composition) -> {
    drawable.setComposition(composition);
});           
  • 1
  • 2
  • 3
  • 4

如果動畫會被頻繁的複用,

LottieAnimationView

有一套緩存政策,可以使用

LottieAnimationView#setAnimation(String, CacheStrategy)           
  • 1

來實作它,

CacheStrategy

可以是

Strong

Weak

或者是

None

,這樣

LottieAnimationView

就可以持有一個已經加載和解析動畫的強引用或者弱引用。

源碼分析

下面我們就從

LottieAnimationView

作為切入點來一步一步分析。

LottieAnimationView

LottieAnimationView

繼承自

AppCompatImageView

,封裝了一些動畫的操作:

public void playAnimation()
public void cancelAnimation()
public void pauseAnimation()
public void setProgress(@FloatRange(from = f, to = f)
public float getProgress()
public long getDuration()
public boolean isAnimating()           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

等等; 

LottieAnimationView

有兩個很重要的成員變量:

@Nullable private LottieComposition.Cancellable compositionLoader;
private final LottieDrawable lottieDrawable = new LottieDrawable();           
  • 1
  • 2

LottieComposition

LottieDrawable

将會在下面專門進行分析,他們分别進行了兩個重要的工作:json檔案的解析和動畫的繪制。 

compositionLoader

進行了動畫解析工作,得到

LottieComposition

。 

我們看到的動畫便是在

LottieDrawable

上面繪制出來的,

lottieDrawable

setComposition

方法中被添加到

LottieAnimationView

上面最終顯示出來。

setImageDrawable(lottieDrawable);           
  • 1

解析JSON檔案

JSON檔案

其實在 Bodymovin 插件這裡也是比較神奇的,它是怎麼生成json檔案的呢?這個後面有時間再研究。解析出來的json檔案是這樣子的:

{
  "assets": [

  ],
  "layers": [
    {
      "ddd": ,
      "ind": ,
      "ty": ,
      "nm": "MASTER",
      "ks": {
        "o": {
          "k": 
        },
        "r": {
          "k": 
        },
        "p": {
          "k": [
            ,
            ,
            
          ]
        },
        "a": {
          "k": [
            ,
            ,
            
          ]
        },
        "s": {
          "k": [
            ,
            ,
            
          ]
        }
      },
      "ao": ,
      "sw": ,
      "sh": ,
      "sc": "#ffffff",
      "ip": ,
      "op": ,
      "st": ,
      "bm": ,
      "sr": 
    },
    ……
  ],
  "v": "4.4.26",
  "ddd": ,
  "ip": ,
  "op": ,
  "fr": ,
  "w": ,
  "h": 
}           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59

重要的資料都在

layers

裡面,後面會介紹。

LottieComposition

Lottie使用

LottieComposition

來作為存儲json檔案的對象,即把json檔案映射到

LottieComposition

LottieComposition

中提供了解析json檔案的幾個靜态方法:

public static Cancellable fromAssetFileName(Context context, String fileName, OnCompositionLoadedListener loadedListener);
public static Cancellable fromInputStream(Context context, InputStream stream, OnCompositionLoadedListener loadedListener);
public static LottieComposition fromFileSync(Context context, String fileName);
public static Cancellable fromJson(Resources res, JSONObject json, OnCompositionLoadedListener loadedListener);
public static LottieComposition fromInputStream(Resources res, InputStream file);
public static LottieComposition fromJsonSync(Resources res, JSONObject json);           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

其實上面這些函數最終的解析工作是在

public static LottieComposition fromJsonSync(Resources res, JSONObject json)

裡面進行的。進行了動畫幾個屬性的解析以及

Layer

解析。 

下面看一下

LottieComposition

裡面的幾個變量:

private final LongSparseArray<Layer> layerMap = new LongSparseArray<>();
    private final List<Layer> layers = new ArrayList<>();           
  • 1
  • 2

layers

存儲json檔案中的

layers

數組裡面的資料,

Layer

就是對應了做圖中圖層的概念,一個完整的動畫就是由這些圖層疊加起來的,具體到下面再介紹。 

layerMap

存儲了

Layer

和其

id

的映射關系。 

下面幾個是動畫裡面常用的幾個屬性:

private Rect bounds;
    private long startFrame;
    private long endFrame;
    private int frameRate;
    private long duration;
    private boolean hasMasks;
    private boolean hasMattes;
    private float scale;           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

Layer

Layer

就是對應了做圖中圖層的概念,一個完整的動畫就是由這些圖層疊加起來的。 

Layer

裡面有個靜态方法:

static Layer fromJson(JSONObject json, LottieComposition composition);           
  • 1

它解析json檔案的資料并轉化為

Layer

對象,

private final List<Object> shapes = new ArrayList<>();

  private String layerName;
  private long layerId;
  private LottieLayerType layerType;
  private long parentId = -;
  private long inFrame;
  private long outFrame;
  private int frameRate;

  private final List<Mask> masks = new ArrayList<>();

  private int solidWidth;
  private int solidHeight;
  private int solidColor;

  private AnimatableIntegerValue opacity;
  private AnimatableFloatValue rotation;
  private IAnimatablePathValue position;

  private AnimatablePathValue anchor;
  private AnimatableScaleValue scale;

  private boolean hasOutAnimation;
  private boolean hasInAnimation;
  private boolean hasInOutAnimation;
  @Nullable private List<Float> inOutKeyFrames;
  @Nullable private List<Float> inOutKeyTimes;

  private MatteType matteType;
           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

一些成員變量一一對應json檔案

layers

數組中的屬性,動畫就是由他們組合而來的。

資料轉換

LottieDrawable

LottieDrawable

繼承自

AnimatableLayer

,關于

AnimatableLayer

我們後面再分析。 

AnimatableLayer

還有其他的子類,

LottieDrawable

可以了解為根布局,裡面包含着其他的

AnimatableLayer

的子類,他們的關系可以了解為

ViewGroup

以及

View

的關系,

ViewGroup

裡面可以包含

ViewGroup

以及

View

。這部分暫且不細說,下面會詳細介紹。 

LottieDrawable

會通過

buildLayersForComposition(LottieComposition composition)

進行動畫資料到動畫對象的映射。 

會根據

LottieComposition

裡面的每一個

Layer

生成一個對應的

LayerView

LayerView

LayerView

也是

AnimatableLayer

的子類,它在

setupForModel()

裡面會根據

Layer

裡面的資料生成不同的

AnimatableLayer

的子類,添加到變量

layers

中去。

else if (item instanceof ShapePath) {
        ShapePath shapePath = (ShapePath) item;
        ShapeLayerView shapeLayer =
            new ShapeLayerView(shapePath, currentFill, currentStroke, currentTrimPath,
                new ShapeTransform(composition), getCallback());
        addLayer(shapeLayer);
      } else if (item instanceof RectangleShape) {
        RectangleShape shapeRect = (RectangleShape) item;
        RectLayer shapeLayer =
            new RectLayer(shapeRect, currentFill, currentStroke, new ShapeTransform(composition),
                getCallback());
        addLayer(shapeLayer);
      } else if (item instanceof CircleShape) {
        CircleShape shapeCircle = (CircleShape) item;
        EllipseShapeLayer shapeLayer =
            new EllipseShapeLayer(shapeCircle, currentFill, currentStroke, currentTrimPath,
                new ShapeTransform(composition), getCallback());
        addLayer(shapeLayer);
      }           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

AnimatableLayer

AnimatableLayer的子類,分别對應着json檔案中的不同資料:

Drawable (android.graphics.drawable)
    AnimatableLayer (com.airbnb.lottie)
        ShapeLayerView (com.airbnb.lottie)
        LottieDrawable (com.airbnb.lottie)
        LayerView (com.airbnb.lottie)
        RectLayer (com.airbnb.lottie)
        RoundRectLayer in RectLayer (com.airbnb.lottie)
        MaskLayer (com.airbnb.lottie)
        EllipseShapeLayer (com.airbnb.lottie)
        ShapeLayer (com.airbnb.lottie)
        GroupLayerView (com.airbnb.lottie)
           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

繪制

LottieDrawable

animator

來觸發整個動畫的繪制,最終會調用

LottieAnimationView

public void invalidateDrawable(Drawable dr)

方法進行視圖的更新和重繪。 

繪制工作基本是由

LottieDrawable

來完成的,具體實在其父類

AnimatableLayer

public void draw(@NonNull Canvas canvas)

方法中進行:

@Override
  public void draw(@NonNull Canvas canvas) {
    int saveCount = canvas.save();
    applyTransformForLayer(canvas, this);

    int backgroundAlpha = Color.alpha(backgroundColor);
    if (backgroundAlpha != ) {
      int alpha = backgroundAlpha;
      if (this.alpha != null) {
        alpha = alpha * this.alpha.getValue() / ;
      }
      solidBackgroundPaint.setAlpha(alpha);
      if (alpha > ) {
        canvas.drawRect(getBounds(), solidBackgroundPaint);
      }
    }
    for (int i = ; i < layers.size(); i++) {
      layers.get(i).draw(canvas);
    }
    canvas.restoreToCount(saveCount);
  }           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

先繪制了本層的内容,然後開始繪制包含的

layers

的内容,這個過程類似與界面中

ViewGroup

嵌套繪制。如此完成各個

Layer

的繪制工作。

總結

由上面的分析我們得到了Lottie繪制動畫的思路: 

1. 建立 

LottieAnimationView lottieAnimationView

2. 在

LottieAnimationView

中建立

LottieDrawable lottieDrawable

3. 在

LottieAnimationView

中建立

compositionLoader

,進行json檔案解析得到

LottieComposition

,完成資料到對象

Layer

的映射。 

4. 解析完後通過

setComposition

方法把

LottieComposition

lottieDrawable

lottieDrawable

setComposition

方法中把

Layer

轉換為

LayerView

,為繪制做好準備。 

5. 在

LottieAnimationView

中把

lottieDrawable

設定

setImageDrawable

, 

6. 然後開始動畫

lottieDrawable.playAnimation()

轉自:http://blog.csdn.net/heqiangflytosky/article/details/60770415