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开发。
我们先看看官方给出的实现的动画效果:
![](https://i-blog.csdnimg.cn/blog_migrate/5a6e92fe39d0093b93777088b178cce5.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/2b9e7a77422f19403528e376d2d580c9.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/bd41acdd8857264827793f88ca0647cb.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/1d0c57b7fad14d0c06abd3a30d73edd1.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/ee6b135a6bac17916f4f6fe894fc7713.gif)
这些动画如果让你实现起来,你可能会觉得很麻烦,但是通过Lottie这一切就变得很容易。
想了解更多请参考官方介绍
使用方法
首先由视觉设计师通过Adobe After Effects做好这些动画,这个比我们用代码来实现会容易的很多,然后Bodymovin导出json文件,这些json文件描述了该动画的一些关键点的坐标以及运动轨迹,然后把json文件放到项目的app/src/main/assets目录下,代码中在build.gradle中添加依赖:
dependencies {
compile 'com.airbnb.android:lottie:1.0.1'
}
在布局文件上加上:
<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"
或者代码中实现:
LottieAnimationView animationView = (LottieAnimationView) findViewById(R.id.animation_view);
animationView.setAnimation("hello-world.json");
animationView.loop(true);
此方法将加载文件并在后台解析动画,并在完成后异步开始呈现动画。
Lottie只支持Jellybean (API 16)或以上版本。
通过源码我们可以发现LottieAnimationView
是继承自AppCompatImageView
,我们可以像使用其他View
一样来使用它。
LottieAnimationView animationView = (LottieAnimationView) findViewById(R.id.animation_view)
甚至可以从网络上下载json数据:
LottieComposition composition = LottieComposition.fromJson(getResources(), jsonObject, (composition) -> {
animationView.setComposition(composition);
animationView.playAnimation();
});
或者使用
setAnimation(JSONObject);
我们还可以控制动画或者添加监听器:
animationView.addAnimatorUpdateListener((animation) -> {
// Do something.
});
animationView.playAnimation();
...
if (animationView.isAnimating()) {
// Do something.
}
...
animationView.setProgress(0.5f);
...
// Custom animation speed or duration.
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f)
.setDuration(500);
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);
});
如果动画会被频繁的复用,LottieAnimationView
有一套缓存策略,可以使用
LottieAnimationView#setAnimation(String, CacheStrategy)
来实现它,CacheStrategy
可以是Strong
,Weak
或者是None
,这样LottieAnimationView
就可以持有一个已经加载和解析动画的强引用或者弱引用。
源码分析
下面我们就从LottieAnimationView
作为切入点来一步一步分析。
LottieAnimationView
LottieAnimationView
继承自AppCompatImageView
,封装了一些动画的操作:
public void playAnimation()
public void cancelAnimation()
public void pauseAnimation()
public void setProgress(@FloatRange(from = 0f, to = 1f)
public float getProgress()
public long getDuration()
public boolean isAnimating()
等等;
LottieAnimationView
有两个很重要的成员变量:
@Nullable private LottieComposition.Cancellable compositionLoader;
private final LottieDrawable lottieDrawable = new LottieDrawable();
LottieComposition
和LottieDrawable
将会在下面专门进行分析,他们分别进行了两个重要的工作:json文件的解析和动画的绘制。
compositionLoader
进行了动画解析工作,得到LottieComposition
。
我们看到的动画便是在LottieDrawable
上面绘制出来的,lottieDrawable
在setComposition
方法中被添加到LottieAnimationView
上面最终显示出来。
setImageDrawable(lottieDrawable);
解析JSON文件
JSON文件
其实在 Bodymovin 插件这里也是比较神奇的,它是怎么生成json文件的呢?这个后面有时间再研究。解析出来的json文件是这样子的:
{
"assets": [
],
"layers": [
{
"ddd": 0,
"ind": 0,
"ty": 1,
"nm": "MASTER",
"ks": {
"o": {
"k": 0
},
"r": {
"k": 0
},
"p": {
"k": [
164.457,
140.822,
0
]
},
"a": {
"k": [
60,
60,
0
]
},
"s": {
"k": [
100,
100,
100
]
}
},
"ao": 0,
"sw": 120,
"sh": 120,
"sc": "#ffffff",
"ip": 12,
"op": 179,
"st": 0,
"bm": 0,
"sr": 1
},
……
],
"v": "4.4.26",
"ddd": 0,
"ip": 0,
"op": 179,
"fr": 30,
"w": 325,
"h": 202
}
- 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);
其实上面这些函数最终的解析工作是在public static LottieComposition fromJsonSync(Resources res, JSONObject json)
里面进行的。进行了动画几个属性的解析以及Layer
解析。
下面看一下LottieComposition
里面的几个变量:
private final LongSparseArray<Layer> layerMap = new LongSparseArray<>();
private final List<Layer> layers = new ArrayList<>();
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;
Layer
Layer
就是对应了做图中图层的概念,一个完整的动画就是由这些图层叠加起来的。
Layer
里面有个静态方法:
static Layer fromJson(JSONObject json, LottieComposition composition);
它解析json文件的数据并转化为Layer
对象,
private final List<Object> shapes = new ArrayList<>();
private String layerName;
private long layerId;
private LottieLayerType layerType;
private long parentId = -1;
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)
绘制
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 != 0) {
int alpha = backgroundAlpha;
if (this.alpha != null) {
alpha = alpha * this.alpha.getValue() / 255;
}
solidBackgroundPaint.setAlpha(alpha);
if (alpha > 0) {
canvas.drawRect(getBounds(), solidBackgroundPaint);
}
}
for (int i = 0; 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