Lottie
1.1、Lottie简介

LottieAirbnb开源的一个支持AndroidiOS以及ReactNative, 利用json文件的方式快速实现动画效果的库。

Android端库地址:https://github.com/airbnb/lottie-android

iOS端库地址:https://github.com/airbnb/lottie-ios

ReactNative端库地址:https://github.com/airbnb/lottie-react-native

Lottie资源库

1.2、Lottie的使用流程

1、动效设计人员在Adobe After Effects中设计动画;

2、动效设计人员通过Adobe After EffectsBodymoving插件导出一份记录动画信息的JSON文件;

3、开发人员使用Lottie的开源库读取这份JSON文件进行解析和渲染。

制作好的JSON文件可以进行预览:https://svgsprite.com/demo/bm/player.php?render=canvas&bg=fff

1.4、Lottie的性能和内存占用

如果Composition中没有masksmattes,那么性能和内存开销应该相当不大。 没有创建位图,大多数操作都是简单的画布操作。

如果使用了图片,那么性能和内存占用会很大,因为图片非常占用内存,这也是Lottie不建议的。

1.5、Lottie在直播礼物中的应用

在直播的礼物实现方案中,Lottie算是一个终极解决方案。虽然他也有一些限制,但是目前看是最好的解决方案。

Lottie的坏处是它是开源的,一旦作者不维护这个库了的话,可能会给使用者造成一些影响, 使用他的前提是你能够很好的理解它的原理,并能根据自己的项目需要做二次开发。

参考1

参考2

动效设计师在制作过程中要注意几点:

1、安装Adobe After Effects2015 CC版本,不要安装最新版本。 使用最新版本做出来的动画总是无法播放,原因未知。

2、不是所有的Adobe After Effects功能都支持的,Lottie在同类的库中对Adobe After Effects的功能支持是做多的, 在开始制作前,一定要明白哪些功能是不支持的。可以看https://github.com/airbnb/lottie-android,有详细说明。

3、在使用Adobe After EffectsBodymoving插件导出JSON文件的时候, 不要对Bodymoving插件做任何设置,使用默认设置即可。

Bodymovin主界面如下:

4、在使用Adobe After EffectsBodymoving插件导出JSON文件的时候,JSON文件的名字必须是data.json,并将所有的礼物都放在一个大文件夹(gifts), 每个礼物是单独的文件夹,文件夹的名字就是礼物的名字,不要出现中文,全部使用英文,导出多个礼物后的文件夹结构,如下示例:

/Users/liufupin/gifts/
├── cake         <============礼物的名称,名字必须唯一
│   └── data.json
├── car          <============礼物的名称,名字必须唯一
│   ├── data.json
│   └── images
│       ├── 1.png
│       ├── 2.png
│       └── 3.png
└── xx           <============礼物的名称,名字必须唯一
    ├── data.json
    └── images
        ├── 1.png
        ├── 2.png
        ├── 3.png
        ├── 4.png
        ├── .....
        └── y.png

由于iOS端的库存在设计上的缺陷,所有图片的名称必须不一样,所以,我们需要对所有礼物的图片做一个重命名, 并将JSON文件中的图片名称改成我们修改后的名称,这个工作我们让计算机来做。 另外,我们需要将每个礼物使用zip打包,这个工作我们也让计算机来做。

如果您使用的是macOS或者GNU/Linux操作系统, 我们为您准备了一个Shell脚本,如下:

#!/bin/sh

osType=`uname -s`
echo "osType=$osType"

BUILD_DIR=$PWD/build
SOURCE_DIR=$BUILD_DIR/source
ZIP_DIR=$BUILD_DIR/zip

function main() {
    #如果存在build目录,就删除掉
    if [ -d $BUILD_DIR ] ; then
        rm -rf $BUILD_DIR
    fi

    #获得所有的礼物文件夹
    giftDirs=`ls -F | grep "/$"`

    #重新创建build目录及其子目录
    mkdir -p $SOURCE_DIR
    mkdir -p $ZIP_DIR

    #复制所有的礼物文件夹到build/source目录
    for giftDir in $giftDirs
    do
        giftDir=`basename $giftDir`
        rm ${giftDir}/.DS_Store
        cp -r $giftDir $SOURCE_DIR/
    done

    cd $SOURCE_DIR

    for dir in $giftDirs
    do
        dir=`basename ${dir}`
        for imageFile in `ls ${dir}/images`
        do
            #修改成新的名字
            mv ${dir}/images/${imageFile} ${dir}/images/${dir}_${imageFile}
            #将data.json中图片名称替换成新的
            if [ $osType = 'Darwin' ] ; then
                sed -i ""  "s#${imageFile}#${dir}_${imageFile}#g" ${dir}/data.json
            else
                sed -i "s#${imageFile}#${dir}_${imageFile}#g" ${dir}/data.json
            fi
        done
        #打zip包
        zip -r $ZIP_DIR/${dir}.zip $dir
    done
    echo "Success Done"
}

main

下载这个脚本:

curl -LO https://raw.githubusercontent.com/leleliu008/auto/master/gifts/package-gift.sh

修改他具有可执行权限:

chmod a+x package-gift.sh

将这个脚本放到gifts目录下:

mv package-gift.sh ~/gifts/

执行命令:

./package-gift.sh

打包完成后的目录结构如下:

/Users/liufupin/gifts/
├── cake
│   └── data.json
├── car
│   ├── data.json
│   └── images
│       ├── 1.png
│       ├── 2.png
│       └── 3.png
├── xx
│   ├── data.json
│   └── images
│       ├── 1.png
│       ├── 2.png
│       ├── 3.png
│       ├── 4.png
│       ├── .....
│       └── y.png
├── build
│   ├── source
│   │   ├── cake
│   │   │   └── data.json
│   │   └── car
│   │   │   ├── data.json
│   │   │   └── images
│   │   │       ├── car_1.png
│   │   │       ├── car_2.png
│   │   │       └── car_3.png
│   │   └── xx
│   │       ├── data.json
│   │       └── images
│   │           ├── xx_1.png
│   │           ├── xx_2.png
│   │           ├── xx_3.png
│   │           ├── xx_3.png
│   │           ├── xx_....png
│   │           └── xx_y.png
│   └── zip         <============生成的zip包都在这里(build/zip/)
│       ├── cake.zip
│       ├── car.zip
│       └── xx.zip
└── package-gift.sh

我们不提倡使用Windows做开发和设计工作,所以,没有提供Windows上的批处理脚本。

当然,这个Shell脚本在Windows上也是可以执行的, 只是您需要安装git-for-windows,通过Git bash运行即可。

1.6、Lottie的Android库使用注意点
1.6.1、给LottieAnimationView绑定数据

Android中,任何View都有如下两对方法:

public void setTag(Object tag)
public Object getTag()

public void setTag(int key, Object tag)
public Object getTag(int key)

LottieAnimationViewImageView的子类,自然也有这两对方法,我们灵活使用这两对方法, 可以为LottieAnimationView根据情况绑定数据。

1.6.2、给LottieAnimationView设置图片资源处理逻辑

LottieAnimationView提供了如下方法:

public void setImageAssetDelegate(ImageAssetDelegate assetDelegate)

ImageAssetDelegate接口的定义如下:

public interface ImageAssetDelegate {
    Bitmap fetchBitmap(LottieImageAsset asset);
}

LottieImageAsset实体类的定义如下:

public class LottieImageAsset {
    private final int width;
    private final int height;
    private final String id;
    private final String fileName;

    private LottieImageAsset(int width, int height, String id, String fileName) {
        this.width = width;
        this.height = height;
        this.id = id;
        this.fileName = fileName;
    }

    static class Factory {
        private Factory() {
        }

        static LottieImageAsset newInstance(JSONObject imageJson) {
            return new LottieImageAsset(imageJson.optInt("w"), imageJson.optInt("h"), imageJson.optString("id"), imageJson.optString("p"));
        }
    }

    public int getWidth() {
        return width;
    }

    public int getHeight() {
        return height;
    }

    public String getId() {
        return id;
    }

    public String getFileName() {
        return fileName;
    }
}

我们可以把所有图片放在同一个目录下,为了避免相互覆盖,需要人为的把图片名称设置为互不相同,但是,这么做可能会有一些麻烦,当然我们可以使用脚本帮我们做这个事情。

下面是一个示例:

lottieAnimationView.setImageAssetDelegate(asset -> {
    Context context = lottieAnimationView.getContext();
    String giftId = (String) lottieAnimationView.getTag();
    String imageFilePath = "/sdcard/" + context.getPackageName() + "/gifts/" + giftId + "/images/" + asset.getFileName();
    return BitmapFactory.decodeFile(imageFilePath);
});
1.6.3、展示或者切换LottieAnimationView动画
/**
 * 展示礼物
 *
 * @param lottieAnimationView 展示区
 * @param giftBean            礼物
 */
public void showGift(final LottieAnimationView lottieAnimationView, final GiftBean giftBean) {
    Context context = lottieAnimationView.getContext();
    String giftId = giftBean.getId();
    File jsonFile = new File("/sdcard/" + context.getPackageName() + "/gifts/" + giftId + "/data.json");
    try {
        final InputStream inputStream = new FileInputStream(jsonFile);
        //从文件流中加载 json 数据
        LottieComposition.Factory.fromInputStream(context, inputStream, composition -> {
            //加载完文件后,将文件流关闭
            try {
                inputStream.close();
            } catch (IOException e) {
                Logger.e(TAG, "fromInputStream()", e);
            }
            //让展示区可见
            lottieAnimationView.setVisibility(View.VISIBLE);
            //给展示区绑定数据,这里是绑定礼物的ID
            lottieAnimationView.setTag(giftId);
            //给展示区设置Composition(AE中的术语,翻译成中文叫:合成)
            lottieAnimationView.setComposition(composition);
            //开始播放动画
            lottieAnimationView.playAnimation();
            //监听动画播放过程的事件
            lottieAnimationView.addAnimatorListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animation) {

                }

                @Override
                public void onAnimationEnd(Animator animation) {
                    //播放完后,隐藏掉展示区
                    lottieAnimationView.setVisibility(View.GONE);
                }

                @Override
                public void onAnimationCancel(Animator animation) {

                }

                @Override
                public void onAnimationRepeat(Animator animation) {

                }
            });
        });
    } catch (FileNotFoundException e) {
        Logger.e(TAG, "showGift()", e);
    }
}

说明:将JSON文件以文件流的方式读出来,转换成LottieComposition对象,将这个对象设置给LottieAnimationView对象,同时给LottieAnimationView对象绑定一些特定的数据,最后开始进行播放, 并设置动画事件监听器。

1.7、Lottie的iOS库使用注意点

LottieiOS库在设计上存在很大的缺陷,对于图片的处理非常不灵活,这是由于是个库的设计者不建议使用图片, 因为使用图片会导致内存占用和CPU性能大幅消耗,但是如果不使用图片,对动效设计师的要求就太高,针对我们国内设计师来说,不使用图片的话,效率会比较低。 所以,我们不得不使用图片。

LottieiOS库虽然支持图片,但是支持的很勉强,而且要求所有的图片的名称必须不一样,如果图片一样,后面的动画将无法播放。

LottieiOS库相比LottieAndroid库比较落后一些,如果有能力去修正他的一些问题或者bug的话, 建议去积极参与修改。