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開發。
我們先看看官方給出的實作的動畫效果:
這些動畫如果讓你實作起來,你可能會覺得很麻煩,但是通過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