Lottie动画框架入门及源码简析

2023-12-26 04:18

本文主要是介绍Lottie动画框架入门及源码简析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

现在越来越多的APP中添加动画来提升用户体验,下面简单介绍下Airbnb开源的动画框架Lottie的使用

一、基本使用

首先添加依赖
compile ‘com.airbnb.android:lottie:1.0.1’

方法一

1、xml文件中添加布局文件

 <com.airbnb.lottie.LottieAnimationViewandroid:id="@+id/lottie_anim"android:layout_width="wrap_content"android:layout_height="wrap_content"/>

2、Activity中对动画进行控制,注释写的很清楚了

//初始化控件
LottieAnimationView mLottieAnimView= (LottieAnimationView) findViewById(R.id.lottie_anim);/***  添加json格式文件从assets中导入*  第一个参数:Context*  第二个参数:动画文件*  第三个参数:动画数据监听*/LottieComposition.fromAssetFileName(this, "LottieLogo.json", new LottieComposition.OnCompositionLoadedListener() {@Overridepublic void onCompositionLoaded(LottieComposition composition) {//设置动画数据mLottieAnimView.setComposition(composition);//播放动画mLottieAnimView.playAnimation();//设置循环mLottieAnimView.loop(true);}});// 设置动画监听mLottieAnimView.addAnimatorUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {}});}

直接运行程序动画就开始播放了

方法二

1、直接通过XML布局文件控制动画,不通过Activity进行操作

<com.airbnb.lottie.LottieAnimationViewandroid:id="@+id/lottie_anim"android:layout_width="wrap_content"android:layout_height="wrap_content"app:lottie_fileName="LottieLogo.json"app:lottie_autoPlay="true"app:lottie_loop="true"/>
属性说明:

//指定动画文件名
app:lottie_fileName=”“

//设置自动播放 true/false
app:lottie_autoPlay=”true”

//设置动画是否循环 true/false
app:lottie_loop=”true”

接下来直接运行项目即可

方法三

1、xml文件中添加布局文件

 <com.airbnb.lottie.LottieAnimationViewandroid:id="@+id/lottie_anim"android:layout_width="wrap_content"android:layout_height="wrap_content"/><Buttonandroid:id="@+id/btn_start"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="开始"/>

2、通过在Activity中按钮来控制动画播放

 mLottieAnim = (LottieAnimationView) findViewById(R.id.lottie_anim);Button mBtnStart= (Button) findViewById(R.id.btn_start);mBtnStart.setOnClickListener(this);//通过直接指定动画名的方式设置动画mLottieAnim.setAnimation("LottieLogo.json");//点击后播放@Overridepublic void onClick(View v) {//判断动画是否在播放if (!mLottieAnim.isAnimating()){//设置进度mLottieAnim.setProgress(0);//开始播放动画mLottieAnim.playAnimation();}}

OK,简单使用介绍完毕

二、常用API

        // 判断动画是否正在播放中mLottieAnim.isAnimating()//设置动画数据  可以接收json格式数据  String格式 、Animation格式mLottieAnim.setAnimation();//播放动画mLottieAnim.playAnimation();//暂停动画mLottieAnim.pauseAnimation();//取消动画mLottieAnim.cancelAnimation();//获取动画时长,一定要在动画开始播放后才能获取否则为0mLottieAnim.getDuration();// 设置动画更新监听mLottieAnim.addAnimatorUpdateListener();//移除动画更新监听mLottieAnim.removeUpdateListener();//动画监听,可以用于监听动画的开始、结束、取消、重复mLottieAnim.addAnimatorListener();// 移除动画监听mLottieAnim.removeAnimatorListener();// 设置合成物  LottieComposition类型mLottieAnim.setComposition();// 设置循环mLottieAnim.loop();// 添加assets中动画文件LottieComposition.fromAssetFileName()// 使用同步方式接收动画文件LottieComposition.fromFileSync();// 添加json格式动画文件LottieComposition.fromJson()// 使用同步方式接收json格式文件LottieComposition.fromJsonSync()// 接收流的形式动画LottieComposition.fromInputStream()

三、源码分析

由简到难,一步步分析

1、类LottieComposition中方法

A: 加载assets中文件

/*** Loads a composition from a file stored in /assets.*/public static Cancellable fromAssetFileName(Context context, String fileName, OnCompositionLoadedListener loadedListener) {InputStream stream;try {stream = context.getAssets().open(fileName);} catch (IOException e) {throw new IllegalStateException("Unable to find file " + fileName, e);}return fromInputStream(context, stream, loadedListener);}

可以看到通过读取assets中文件返回一个流格式,直接跳到fromInputStream()方法

B:接下来我们查看fromInputStream()方法

 /*** Loads a composition from an arbitrary input stream.** ex: fromInputStream(context, new FileInputStream(filePath), (composition) -> {});*/public static Cancellable fromInputStream(Context context, InputStream stream, OnCompositionLoadedListener loadedListener) {FileCompositionLoader loader = new FileCompositionLoader(context.getResources(), loadedListener);loader.execute(stream);return loader;}

通过上面代码我们发现直接通过FileCompositionLoader来执行流文件 loader.execute(stream);

C:查看FileCompositionLoader

private static final class FileCompositionLoader extends CompositionLoader<InputStream> {private final Resources res;private final OnCompositionLoadedListener loadedListener;FileCompositionLoader(Resources res, OnCompositionLoadedListener loadedListener) {this.res = res;this.loadedListener = loadedListener;}@Overrideprotected LottieComposition doInBackground(InputStream... params) {return fromInputStream(res, params[0]);}@Overrideprotected void onPostExecute(LottieComposition composition) {loadedListener.onCompositionLoaded(composition);}}

发现FileCompositionLoader继承CompositionLoader,并且在doInBackground()方法中返回了方法
fromInputStream(res, params[0]);

D:查看CompositionLoader

  private abstract static class CompositionLoader<Params>extends AsyncTask<Params, Void, LottieComposition>implements Cancellable {@Overridepublic void cancel() {cancel(true);}}

CompositionLoader继承了AsyncTask可见该类是采用异步执行的方式加载流

E:fromInputStream(res, params[0])
C中我们发现在方法doInBackground()中异步执行了该方法,继续分析

 @SuppressWarnings("WeakerAccess")public static LottieComposition fromInputStream(Resources res, InputStream file) {try {int size = file.available();byte[] buffer = new byte[size];//noinspection ResultOfMethodCallIgnoredfile.read(buffer);file.close();String json = new String(buffer, "UTF-8");JSONObject jsonObject = new JSONObject(json);return LottieComposition.fromJsonSync(res,jsonObject);} catch (IOException e) {throw new IllegalStateException("Unable to find file.", e);} catch (JSONException e) {throw new IllegalStateException("Unable to load JSON.", e);}}

通过查看发现该方法将数据转化成json格式并返回给LottieComposition.fromJsonSync(res,jsonObject)方法执行,可见fromJsonSync对json数据进行了解析操作

F:继续跟进方法

@SuppressWarnings("WeakerAccess")public static LottieComposition fromJsonSync(Resources res, JSONObject json) {LottieComposition composition = new LottieComposition(res);int width = -1;int height = -1;try {width = json.getInt("w");height = json.getInt("h");} catch (JSONException e) {// ignore.}if (width != -1 && height != -1) {int scaledWidth = (int) (width * composition.scale);int scaledHeight = (int) (height * composition.scale);if (Math.max(scaledWidth, scaledHeight) > MAX_PIXELS) {float factor = (float) MAX_PIXELS / (float) Math.max(scaledWidth, scaledHeight);scaledWidth *= factor;scaledHeight *= factor;composition.scale *= factor;}composition.bounds = new Rect(0, 0, scaledWidth, scaledHeight);}try {composition.startFrame = json.getLong("ip");composition.endFrame = json.getLong("op");composition.frameRate = json.getInt("fr");} catch (JSONException e) {//}if (composition.endFrame != 0 && composition.frameRate != 0) {long frameDuration = composition.endFrame - composition.startFrame;composition.duration = (long) (frameDuration / (float) composition.frameRate * 1000);}try {JSONArray jsonLayers = json.getJSONArray("layers");for (int i = 0; i < jsonLayers.length(); i++) {Layer layer = Layer.fromJson(jsonLayers.getJSONObject(i), composition);addLayer(composition, layer);}} catch (JSONException e) {throw new IllegalStateException("Unable to find layers.", e);}// These are precomps. This naively adds the precomp layers to the main composition.// TODO: Significant work will have to be done to properly support them.try {JSONArray assets = json.getJSONArray("assets");for (int i = 0; i < assets.length(); i++) {JSONObject asset = assets.getJSONObject(i);JSONArray layers = asset.getJSONArray("layers");for (int j = 0; j < layers.length(); j++) {Layer layer = Layer.fromJson(layers.getJSONObject(j), composition);addLayer(composition, layer);}}} catch (JSONException e) {// Do nothing.}return composition;}

通过查看方法,方法中对json格式数据进行了解析并且通过 LottieComposition composition对象进行了接收并且调用了Layer.fromJson和addLayer(composition, layer);方法

G:查看Layer.fromJson及addLayer(composition, layer);

通过fromJson()方法将json数据解析并赋值给layer

 static Layer fromJson(JSONObject json, LottieComposition composition) {Layer layer = new Layer(composition);try {if (L.DBG) Log.d(TAG, "Parsing new layer.");layer.layerName = json.getString("nm");if (L.DBG) Log.d(TAG, "\tName=" + layer.layerName);layer.layerId = json.getLong("ind");if (L.DBG) Log.d(TAG, "\tId=" + layer.layerId);layer.frameRate = composition.getFrameRate();int layerType = json.getInt("ty");if (layerType <= LottieLayerType.Shape.ordinal()) {layer.layerType = LottieLayerType.values()[layerType];} else {layer.layerType = LottieLayerType.Unknown;}.....省略代码段return layer;

通过addLayer方法将解析出来的json数据Layer添加到composition.layers中
可以将composition理解为一个包含图层信息的对象

 private static void addLayer(LottieComposition composition, Layer layer) {composition.layers.add(layer);composition.layerMap.put(layer.getId(), layer);if (!layer.getMasks().isEmpty()) {composition.hasMasks = true;}if (layer.getMatteType() != null && layer.getMatteType() != Layer.MatteType.None) {composition.hasMattes = true;}}

H:通过以上的分析我们只发现了composition对象存储了解析出来的json信息,下面我们就分析怎样将composition对象转化为动画
接下来查看LottieAnimationView中的setAnimation()方法

  /*** Sets the animation from a file in the assets directory.* This will load and deserialize the file asynchronously.** Will not cache the composition once loaded.*/public void setAnimation(String animationName) {setAnimation(animationName, CacheStrategy.None);}

上面的方法返回setAnimation(animationName, CacheStrategy.None);
那么我们接着查看setAnimation方法做了什么

 @SuppressWarnings("WeakerAccess")public void setAnimation(final String animationName, final CacheStrategy cacheStrategy) {this.animationName = animationName;if (weakRefCache != null && weakRefCache.containsKey(animationName)) {WeakReference<LottieComposition> compRef = weakRefCache.get(animationName);if (compRef.get() != null) {setComposition(compRef.get());return;}} else if (strongRefCache != null && strongRefCache.containsKey(animationName)) {setComposition(strongRefCache.get(animationName));return;}isAnimationLoading = true;setProgressWhenCompositionSet = false;playAnimationWhenCompositionSet = false;this.animationName = animationName;cancelLoaderTask();compositionLoader = LottieComposition.fromAssetFileName(getContext(), animationName, new LottieComposition.OnCompositionLoadedListener() {@Overridepublic void onCompositionLoaded(LottieComposition composition) {if (cacheStrategy == CacheStrategy.Strong) {if (strongRefCache == null) {strongRefCache = new HashMap<>();}strongRefCache.put(animationName, composition);} else if (cacheStrategy == CacheStrategy.Weak) {if (weakRefCache == null) {weakRefCache = new HashMap<>();}weakRefCache.put(animationName, new WeakReference<>(composition));}setComposition(composition);}});} 

通过上面的方法我们发现最后调用了 setComposition(composition);
继续分析,查看setComposition(composition);

  public void setComposition(@NonNull LottieComposition composition) {if (L.DBG) {Log.v(TAG, "Set Composition \n" + composition);}lottieDrawable.setCallback(this);lottieDrawable.setComposition(composition);// If you set a different composition on the view, the bounds will not update unless// the drawable is different than the original.setImageDrawable(null);setImageDrawable(lottieDrawable);isAnimationLoading = false;if (setProgressWhenCompositionSet) {setProgressWhenCompositionSet = false;setProgress(progress);} else {setProgress(0f);}this.composition = composition;if (playAnimationWhenCompositionSet) {playAnimationWhenCompositionSet = false;playAnimation();}requestLayout();}

通过上面的分析发现方法 lottieDrawable.setComposition(composition);处理了接收到的composition对象
继续跟踪该方法

 public void 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.");}clearComposition();this.composition = composition;animator.setDuration(composition.getDuration());setBounds(0, 0, composition.getBounds().width(), composition.getBounds().height());buildLayersForComposition(composition);getCallback().invalidateDrawable(this);}private void clearComposition() {recycleBitmaps();clearLayers();}

有木有发现一个很熟悉的单词
animator.setDuration(composition.getDuration());
到这里我们终于将composition和动画建立了联系
最终我们发现了下面这个方法
private final ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
哈哈,原来是通过ValueAnimator来实现的,好了简单分析了下,就到这里,如有不足,请指正。

参考:http://www.jianshu.com/p/0882ea3b59e3

这篇关于Lottie动画框架入门及源码简析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/538080

相关文章

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

数论入门整理(updating)

一、gcd lcm 基础中的基础,一般用来处理计算第一步什么的,分数化简之类。 LL gcd(LL a, LL b) { return b ? gcd(b, a % b) : a; } <pre name="code" class="cpp">LL lcm(LL a, LL b){LL c = gcd(a, b);return a / c * b;} 例题:

Java 创建图形用户界面(GUI)入门指南(Swing库 JFrame 类)概述

概述 基本概念 Java Swing 的架构 Java Swing 是一个为 Java 设计的 GUI 工具包,是 JAVA 基础类的一部分,基于 Java AWT 构建,提供了一系列轻量级、可定制的图形用户界面(GUI)组件。 与 AWT 相比,Swing 提供了许多比 AWT 更好的屏幕显示元素,更加灵活和可定制,具有更好的跨平台性能。 组件和容器 Java Swing 提供了许多

【IPV6从入门到起飞】5-1 IPV6+Home Assistant(搭建基本环境)

【IPV6从入门到起飞】5-1 IPV6+Home Assistant #搭建基本环境 1 背景2 docker下载 hass3 创建容器4 浏览器访问 hass5 手机APP远程访问hass6 更多玩法 1 背景 既然电脑可以IPV6入站,手机流量可以访问IPV6网络的服务,为什么不在电脑搭建Home Assistant(hass),来控制你的设备呢?@智能家居 @万物互联

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

如何在Visual Studio中调试.NET源码

今天偶然在看别人代码时,发现在他的代码里使用了Any判断List<T>是否为空。 我一般的做法是先判断是否为null,再判断Count。 看了一下Count的源码如下: 1 [__DynamicallyInvokable]2 public int Count3 {4 [__DynamicallyInvokable]5 get

poj 2104 and hdu 2665 划分树模板入门题

题意: 给一个数组n(1e5)个数,给一个范围(fr, to, k),求这个范围中第k大的数。 解析: 划分树入门。 bing神的模板。 坑爹的地方是把-l 看成了-1........ 一直re。 代码: poj 2104: #include <iostream>#include <cstdio>#include <cstdlib>#include <al

cross-plateform 跨平台应用程序-03-如果只选择一个框架,应该选择哪一个?

跨平台系列 cross-plateform 跨平台应用程序-01-概览 cross-plateform 跨平台应用程序-02-有哪些主流技术栈? cross-plateform 跨平台应用程序-03-如果只选择一个框架,应该选择哪一个? cross-plateform 跨平台应用程序-04-React Native 介绍 cross-plateform 跨平台应用程序-05-Flutte