项目链接
https://github.com/airbnb/lottie-android
原文链接:
https://github.com/jeanboydev/Android-ReadTheFuckingSourceCode/blob/master/Lottie动画开源库使用&源码分析.md
Lottie是Airbnb开源的动画项目,它能够同时支持iOS,Android与ReactNative的开发,使用流程如下图所示
如图所示,通过安装AE上的bodymovin的插件,能够将AE中的动画工程文件转换为通用的json格式描述文件(bodymovin插件本身是用于网页上呈现各种AE效果的一个开源库),lottie所做的事情就是实现在不同移动端平台上呈现AE动画的方式,从而达到动画文件的一次绘制、一次转换,随处可用的效果,这个跟Java一次编译随处运行效果一样
1.导入
dependencies {
compile 'com.airbnb.android:lottie:1.5.3'
}
2.布局文件中添加
3.代码调用
LottieAnimationView animationView = (LottieAnimationView) findViewById(R.id.animation_view);
animationView.setAnimation("hello-world.json");
animationView.loop(true);
4.动画监听
animationView.addAnimatorUpdateListener((animation) -> {
// Do something.
});
animationView.playAnimation();
...
if (animationView.isAnimating()) {
// Do something.
}
...
animationView.setProgress(0.5f);
...
// 自定义速度与时长
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f)
.setDuration(500);
animator.addUpdateListener(animation -> {
animationView.setProgress(animation.getAnimatedValue());
});
animator.start();
...
animationView.cancelAnimation();
官方说法
Lottie使用json文件来作为动画数据源,json文件是通过Bodymovin插件导出的,查看sample中给出的json文件,其实就是把图片中的元素进行来拆分,并且描述每个元素的动画执行路径和执行时间。Lottie的功能就是读取这些数据,然后绘制到屏幕上。
首先要解析json,建立数据到对象的映射,然后根据数据对象创建合适的Drawable绘制到view上,动画的实现可以通过操作读取到的元素完成。
Lottie使用LottieComposition来作为After Effects的数据对象,即把Json文件映射为到LottieComposition,该类中提供了解析json的静态方法。
static LottieComposition fromJsonSync(Resources res, JSONObject json) {
Rect bounds = null;
float scale = res.getDisplayMetrics().density;
int width = json.optInt("w", -1);
int height = json.optInt("h", -1);
if (width != -1 && height != -1) {
int scaledWidth = (int) (width * scale);
int scaledHeight = (int) (height * scale);
bounds = new Rect(0, 0, scaledWidth, scaledHeight);
}
long startFrame = json.optLong("ip", 0);
long endFrame = json.optLong("op", 0);
int frameRate = json.optInt("fr", 0);
LottieComposition composition =
new LottieComposition(bounds, startFrame, endFrame, frameRate, scale);
JSONArray assetsJson = json.optJSONArray("assets");
parseImages(assetsJson, composition);
parsePrecomps(assetsJson, composition);
parseLayers(json, composition);
return composition;
}
private static void parseLayers(JSONObject json, LottieComposition composition) {
JSONArray jsonLayers = json.optJSONArray("layers");
int length = jsonLayers.length();
for (int i = 0; i < length; i++) {
Layer layer = Layer.Factory.newInstance(jsonLayers.optJSONObject(i), composition);
addLayer(composition.layers, composition.layerMap, layer);
}
}
LottieDrawable 继承自 AnimatableLayer 继承自 Drawable
AnimatableLayer
首先看下AnimatableLayer继承了Drawable主要重写了draw,在代码中可以看出,借用canvas的save、restoreToCount来实现像PS那种图层叠加的效果。
@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.transform != null) {
alpha = alpha * this.transform.getOpacity().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);
}
LottieDrawable
boolean setComposition(LottieComposition composition) {
if (getCallback() == null) {
throw new IllegalStateException(
"You or your view must set a Drawable.Callback before setting the composition. This " +
"gets done automatically when added to an ImageView. " +
"Either call ImageView.setImageDrawable() before setComposition() or call " +
"setCallback(yourView.getCallback()) first.");
}
if (this.composition == composition) {
return false;
}
clearComposition();
this.composition = composition;
setSpeed(speed);
setBounds(0, 0, composition.getBounds().width(), composition.getBounds().height());
buildLayersForComposition(composition);
setProgress(getProgress());
return true;
}
该方法在LottieAnimationView中调用,该方法中实际调用的核心函数是
void buildLayersForComposition(LottieComposition composition)
private void buildLayersForComposition(LottieComposition composition) {
if (composition == null) {
throw new IllegalStateException("Composition is null");
}
LongSparseArray layerMap = new LongSparseArray<>(composition.getLayers().size());
List layers = new ArrayList<>(composition.getLayers().size());
LayerView mattedLayer = null;
for (int i = composition.getLayers().size() - 1; i >= 0; i--) {
Layer layer = composition.getLayers().get(i);
LayerView layerView;
layerView = new LayerView(layer, composition, this, canvasPool);
layerMap.put(layerView.getId(), layerView);
if (mattedLayer != null) {
mattedLayer.setMatteLayer(layerView);
mattedLayer = null;
} else {
layers.add(layerView);
if (layer.getMatteType() == Layer.MatteType.Add) {
mattedLayer = layerView;
} else if (layer.getMatteType() == Layer.MatteType.Invert) {
mattedLayer = layerView;
}
}
}
for (int i = 0; i < layers.size(); i++) {
LayerView layerView = layers.get(i);
addLayer(layerView);
}
for (int i = 0; i < layerMap.size(); i++) {
long key = layerMap.keyAt(i);
LayerView layerView = layerMap.get(key);
LayerView parentLayer = layerMap.get(layerView.getLayerModel().getParentId());
if (parentLayer != null) {
layerView.setParentLayer(parentLayer);
}
}
}
LottieAnimationView 继承自 AppCompatImageView,封装了一些动画的操作
public class LottieAnimationView extends AppCompatImageView {
private final LottieDrawable lottieDrawable = new LottieDrawable();
@Nullable private LottieComposition composition;
public LottieAnimationView(Context context) {
super(context);
init(null);
}
public LottieAnimationView(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public LottieAnimationView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
private void init(@Nullable AttributeSet attrs) {
TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.LottieAnimationView);
String fileName = ta.getString(R.styleable.LottieAnimationView_lottie_fileName);
if (!isInEditMode() && fileName != null) {
setAnimation(fileName);
}
if (ta.getBoolean(R.styleable.LottieAnimationView_lottie_autoPlay, false)) {
lottieDrawable.playAnimation();
}
lottieDrawable.loop(ta.getBoolean(R.styleable.LottieAnimationView_lottie_loop, false));
setImageAssetsFolder(ta.getString(R.styleable.LottieAnimationView_lottie_imageAssetsFolder));
int cacheStrategy = ta.getInt(
R.styleable.LottieAnimationView_lottie_cacheStrategy,
CacheStrategy.None.ordinal());
defaultCacheStrategy = CacheStrategy.values()[cacheStrategy];
ta.recycle();
setLayerType(LAYER_TYPE_SOFTWARE, null);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
float systemAnimationScale = Settings.Global.getFloat(getContext().getContentResolver(),
Settings.Global.ANIMATOR_DURATION_SCALE, 1.0f);
if (systemAnimationScale == 0f) {
lottieDrawable.systemAnimationsAreDisabled();
}
}
}
public void playAnimation() {
lottieDrawable.playAnimation();
}
@SuppressWarnings("unused") public void reverseAnimation() {
lottieDrawable.reverseAnimation();
}
@SuppressWarnings("unused") public void setSpeed(float speed) {
lottieDrawable.setSpeed(speed);
}
public void cancelAnimation() {
lottieDrawable.cancelAnimation();
}
public void pauseAnimation() {
float progress = getProgress();
lottieDrawable.cancelAnimation();
setProgress(progress);
}
public void setProgress(@FloatRange(from = 0f, to = 1f) float progress) {
lottieDrawable.setProgress(progress);
}
}
更多文章:
https://github.com/jeanboydev/Android-ReadTheFuckingSourceCode
欢迎你「扫一扫」下面的二维码,关注我的公众号,可以接受最新的文章推送,有丰厚的抽奖活动和福利等着你哦!
如果你有什么疑问或者问题,可以 点击这里 提交 issue,也可以发邮件给我 [email protected]。
同时欢迎你 来一起交流学习,群里有很多大牛和学习资料,相信一定能帮助到你!