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

现在越来越多的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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值