Lottie动画框架入门及源码简析
来源:互联网 发布:电脑淘宝怎么搜索店铺 编辑:程序博客网 时间:2024/06/03 14:48
现在越来越多的APP中添加动画来提升用户体验,下面简单介绍下Airbnb开源的动画框架Lottie的使用
一、基本使用
首先添加依赖
compile ‘com.airbnb.android:lottie:1.0.1’
方法一:
1、xml文件中添加布局文件
<com.airbnb.lottie.LottieAnimationView android: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() { @Override public void onCompositionLoaded(LottieComposition composition) { //设置动画数据 mLottieAnimView.setComposition(composition); //播放动画 mLottieAnimView.playAnimation(); //设置循环 mLottieAnimView.loop(true); } }); // 设置动画监听 mLottieAnimView.addAnimatorUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { } }); }
直接运行程序动画就开始播放了
方法二
1、直接通过XML布局文件控制动画,不通过Activity进行操作
<com.airbnb.lottie.LottieAnimationView android: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.LottieAnimationView android:id="@+id/lottie_anim" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android: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"); //点击后播放 @Override public 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(); //获取动画时长,一定要在动画开始播放后才能获取否则为0 mLottieAnim.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; } @Override protected LottieComposition doInBackground(InputStream... params) { return fromInputStream(res, params[0]); } @Override protected 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 { @Override public 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 ResultOfMethodCallIgnored file.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() { @Override public 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动画框架入门及源码简析
- Lottie-移动动画效果框架
- Lottie动画库 Android 端源码浅析
- Lottie动画库的使用 & 源码解析
- Android Lottie动画框架简单例子
- lottie动画效果使用简例
- Lottie动画学习一 —— 入门博客介绍
- 开源App动画Lottie
- Lottie 源码解析(一)
- iOS-Lottie源码解析
- android--Airbnb 动画库Lottie
- Lottie安卓开源动画库使用
- Lottie安卓开源动画库使用
- Lottie安卓开源动画库使用
- Lottie安卓开源动画库使用
- Android Lottie动画库介绍
- Android•Lottie 动画库填坑记
- iOS 动画实战之Lottie动画
- 短进程优先算法(C,Java实现)
- Java中Math类的常用方法
- Longest Palindromic Substring最长回文字符串算法
- ssk(字符串核)计算相似度
- 关于IO模式(浮空、推挽、开漏...)描述及应用
- Lottie动画框架入门及源码简析
- vector利用swap()函数进行内存的释放
- 第14章 绘制出质感的世界——光照与材质
- MyEclipse设置JAVA选中高亮显示
- 欢迎使用CSDN-markdown编辑器
- MySQL联合查询语法内联、左联、右联、全联
- cannot open shared object file: No such file or directory
- Android题目笔记(四)
- ES6-字符串扩展-at()和normalize()