Lottie不一样的使用

lottie的使用通读一遍官方文档基本就可以拿来用了,也可以看看我之前的Lottie使用;但今天要说的是另外一种情况,就是通过网络拿到动画资源的zip包,再来加载动画:
使用场景就是,zip包中含有动画的json文件以及动画需要的素材文件;
首先估计下会遇到的问题可能有如下:

  • json文件和素材文件都放在Asset目录下,LottieAnimationView可以直接加载,那么如何加载指定目录的资源(为解决加载网络资源).
  • 通过网络拿到的动画zip包怎么拆分出动画json文件和素材文件

解决第一个问题,我好奇的点进了setAnimation这个方法,想知道LottieAnimationView是如何把指定json文件转为动画实现的,发现它只是调用了同名方法setAnimation(animationName, defaultCacheStrategy)
通过参数,可以看出第二个参数是缓存策略;接着往下看:

 /**
   * Sets the animation from a file in the assets directory.
   * This will load and deserialize the file asynchronously.
   * <p>
   * You may also specify a cache strategy. Specifying {@link CacheStrategy#Strong} will hold a
   * strong reference to the composition once it is loaded
   * and deserialized. {@link CacheStrategy#Weak} will hold a weak reference to said composition.
   */
public void setAnimation(final String animationName, final CacheStrategy cacheStrategy) {
    this.animationName = animationName;
    animationResId = 0;
    if (ASSET_WEAK_REF_CACHE.containsKey(animationName)) {
      WeakReference<LottieComposition> compRef = ASSET_WEAK_REF_CACHE.get(animationName);
      LottieComposition ref = compRef.get();
      if (ref != null) {
        setComposition(ref);
        return;
      }
    } else if (ASSET_STRONG_REF_CACHE.containsKey(animationName)) {
      setComposition(ASSET_STRONG_REF_CACHE.get(animationName));
      return;
    }

    clearComposition();
    cancelLoaderTask();
    compositionLoader = LottieComposition.Factory.fromAssetFileName(getContext(), animationName,
        new OnCompositionLoadedListener() {
          @Override public void onCompositionLoaded(LottieComposition composition) {
            if (cacheStrategy == CacheStrategy.Strong) {
              ASSET_STRONG_REF_CACHE.put(animationName, composition);
            } else if (cacheStrategy == CacheStrategy.Weak) {
              ASSET_WEAK_REF_CACHE.put(animationName, new WeakReference<>(composition));
            }

            setComposition(composition);
          }
        });
  }

可以看到缓存策略相关包括强引用缓存,弱引用缓存,和无缓存模式,Json动画文件最终会通过LottieComposition.Factory.fromAssetFileName异步转化为Composition对象;继续跟进这个方法:

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

通过context.getAssets().open(fileName);将传入的文件以流的方式处理,跟到这里,估计就知道如何加载其他目录的文件。
通过流的方式拿到指定目录,并通过LottieComposition.Factory.fromAssetFileName创建Composition对象,然后再set给LottieAnimationView;
在加载包含素材文件的动画文件的时候,需要注意一点,稍不注意,就一定会遇到下面这个错误:

You must set an images folder before loading an image. Set it with LottieComposition#setImagesFolder or LottieDrawable#setImagesFolder

怎么解决尼?得看你是在那种场景遇到的这个问题,

  • 如果是本地加载,且动画文件包和素材文件都在assets下的时候,只需要在调用playAnimation之前调用setImageAssetsFolder("你的素材文件名")
  • 本地加载,动画文件和素材文件不在assets下的时候,那么就有点复杂了;
    在第二种情况的时候,跟进setImageAssetsFolder方法;
 public void setImageAssetsFolder(String imageAssetsFolder) {
    lottieDrawable.setImagesAssetsFolder(imageAssetsFolder);
  }

继续,

public void setImagesAssetsFolder(@Nullable String imageAssetsFolder) {
    this.imageAssetsFolder = imageAssetsFolder;
  }

将其赋值给了imageAssetsFolder,查看imageAssetsFolder的引用处,

private ImageAssetManager getImageAssetManager() {
    if (getCallback() == null) {
      // We can't get a bitmap since we can't get a Context from the callback.
      return null;
    }

    if (imageAssetManager != null && !imageAssetManager.hasSameContext(getContext())) {
      imageAssetManager.recycleBitmaps();
      imageAssetManager = null;
    }

    if (imageAssetManager == null) {
      imageAssetManager = new ImageAssetManager(getCallback(),
          imageAssetsFolder, imageAssetDelegate, composition.getImages());
    }

    return imageAssetManager;
  }

被当做参数传入了ImageAssetManager,跟进这个类,继续查找,最终发现被用到的地方

@Nullable public Bitmap bitmapForId(String id) {
    Bitmap bitmap = bitmaps.get(id);
    if (bitmap != null) {
      return bitmap;
    }

    LottieImageAsset imageAsset = imageAssets.get(id);
    if (imageAsset == null) {
      return null;
    }

    if (delegate != null) {
      bitmap = delegate.fetchBitmap(imageAsset);
      if (bitmap != null) {
        putBitmap(id, bitmap);
      }
      return bitmap;
    }

    String filename = imageAsset.getFileName();
    BitmapFactory.Options opts = new BitmapFactory.Options();
    opts.inScaled = true;
    opts.inDensity = 160;

    if (filename.startsWith("data:") && filename.indexOf("base64,") > 0) {
      // Contents look like a base64 data URI, with the format data:image/png;base64,<data>.
      byte[] data;
      try {
        data = Base64.decode(filename.substring(filename.indexOf(',') + 1), Base64.DEFAULT);
      } catch (IllegalArgumentException e) {
        Log.w(L.TAG, "data URL did not have correct base64 format.", e);
        return null;
      }
      bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, opts);
      return putBitmap(id, bitmap);
    }

    InputStream is;
    try {
      if (TextUtils.isEmpty(imagesFolder)) {
        throw new IllegalStateException("You must set an images folder before loading an image." +
            " Set it with LottieComposition#setImagesFolder or LottieDrawable#setImagesFolder");
      }
      is = context.getAssets().open(imagesFolder + filename);
    } catch (IOException e) {
      Log.w(L.TAG, "Unable to open asset.", e);
      return null;
    }
    bitmap = BitmapFactory.decodeStream(is, null, opts);
    return putBitmap(id, bitmap);
  }

确实加载动画所需要的图片资源都通过这个方法获取,传入一个图片文件名称,然后通过流获取Bitmap对象并返回。
如果Json动画文件使用了图片素材,里面的Json数据必然会声明该图片文件名。在Composition.Factory进行解析为Composition时,里面使用的图片都以键值对的方式存放到Composition的
private final Map<String, LottieImageAsset> images = new HashMap<>()中,LottieAnimationView.setCompostion(Compostion)最终落实到LottieDrawable.setCompostion(Compostion),LottieDrawable为了获取动画里面的bitmap对象,Lottie框架封装了ImageAssetBitmapManager对象,在LottieDrawable中创建,将图片的获取转移到imageAssetBitmapManager 中,并暴露public Bitmap bitmapForId(String id)的方法。
通过LottieImageAsset imageAsset = imageAssets.get(id)拿到之前的assets,Json动画文件解析的图片都存放到imageAssets中,id是当前需要加载的图片素材名,通过get获取到对应的LottieImageAsset对象;
通过

   if (delegate != null) {
      bitmap = delegate.fetchBitmap(imageAsset);
      if (bitmap != null) {
        putBitmap(id, bitmap);
      }
      return bitmap;
    }
    ...
    InputStream is;
    try {
      if (TextUtils.isEmpty(imagesFolder)) {
        throw new IllegalStateException("You must set an images folder before loading an image." +
            " Set it with LottieComposition#setImagesFolder or LottieDrawable#setImagesFolder");
      }
      is = context.getAssets().open(imagesFolder + filename);
    } catch (IOException e) {
      Log.w(L.TAG, "Unable to open asset.", e);
      return null;
    }

可以看出,当delegate == null的情况下,它会从Asset的imagesFolder目录下找素材文件。但是我们如果没有设置assetDelegate,而我们加载的动画json为其他目录,素材也并不是在Asset的imagesFolder目录下,所以就获取不到bitmap对象,也就会看到上面哪个错误

throw new IllegalStateException("You must set an images folder before loading an image." +" Set it with LottieComposition#setImagesFolder or LottieDrawable#setImagesFolder")**;

现在我们就需要知道ImageAssetDelegate是个什么东西了,发现在LottieDrawable中有这样一个方法

 public void setImageAssetDelegate(
      @SuppressWarnings("NullableProblems") ImageAssetDelegate assetDelegate) {
    this.imageAssetDelegate = assetDelegate;
    if (imageAssetManager != null) {
      imageAssetManager.setDelegate(assetDelegate);
    }
  }

继续查看引用,会在熟悉的LottieAnimationView中发现

 public void setImageAssetDelegate(ImageAssetDelegate assetDelegate) {
    lottieDrawable.setImageAssetDelegate(assetDelegate);
  }

我们可以看到ImageAssetDelegate是一个接口

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

因此可以在调用setImageAssetsFolder的时候,调用setImageAssetDelegate,传入实现的ImageAssetDelegate,即可解决上面的第二种情况遇到的问题;

加载网络zip包的动画文件方式

最简单的是不含素材文件的zip包:

其次是包含素材文件的zip包:

发表点一些可能遇到的问题:

  • 空指针问题:
@Nullable public Bitmap bitmapForId(String id) {
    Bitmap bitmap = bitmaps.get(id);
    if (bitmap != null) {
      return bitmap;
    }

    LottieImageAsset imageAsset = imageAssets.get(id);
    if (imageAsset == null) {
      return null;
    }

    if (delegate != null) {
      bitmap = delegate.fetchBitmap(imageAsset);
      if (bitmap != null) {
        putBitmap(id, bitmap);
      }
      return bitmap;
    }

    String filename = imageAsset.getFileName();
    BitmapFactory.Options opts = new BitmapFactory.Options();
    opts.inScaled = true;
    opts.inDensity = 160;

    if (filename.startsWith("data:") && filename.indexOf("base64,") > 0) {
      // Contents look like a base64 data URI, with the format data:image/png;base64,<data>.
      byte[] data;
      try {
        data = Base64.decode(filename.substring(filename.indexOf(',') + 1), Base64.DEFAULT);
      } catch (IllegalArgumentException e) {
        Log.w(L.TAG, "data URL did not have correct base64 format.", e);
        return null;
      }
      bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, opts);
      return putBitmap(id, bitmap);
    }

    InputStream is;
    try {
      if (TextUtils.isEmpty(imagesFolder)) {
        throw new IllegalStateException("You must set an images folder before loading an image." +
            " Set it with LottieComposition#setImagesFolder or LottieDrawable#setImagesFolder");
      }
      is = context.getAssets().open(imagesFolder + filename);
    } catch (IOException e) {
      Log.w(L.TAG, "Unable to open asset.", e);
      return null;
    }
    bitmap = BitmapFactory.decodeStream(is, null, opts);
    return putBitmap(id, bitmap);
  }

  public void recycleBitmaps() {
    synchronized (bitmapHashLock) {
      Iterator<Map.Entry<String, Bitmap>> it = bitmaps.entrySet().iterator();
      while (it.hasNext()) {
        Map.Entry<String, Bitmap> entry = it.next();
        entry.getValue().recycle();
        it.remove();
      }
    }
  }

在回收的时候,就会爆发;我们可以在加载的时候自己给兜住;

  • 动画View的设置,在LottieAnimationView设置宽高的时候应该遵循json文件中的宽高比;
  • 加载自己指定的文件动画时候,可能出现动画偏大或者偏小
    可以在设置
    通过设置ImageAssetDelegate的时候
@Override
    public Bitmap fetchBitmap(LottieImageAsset asset) {
        String filePath = currentImgFolder + File.separator + asset.getFileName();
        BitmapFactory.Options opts = new BitmapFactory.Options();
        opts.inDensity = 110;                                                                 //请留意这个值的设定
        return BitmapFactory.decodeFile(filePath, opts);                                     
    }

改变opts.inDensity的值;

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • 之前发过一个帖子,但是那个帖子有点问题我就重新发一个吧,下面的源码是我从今年开始不断整理源码区和其他网站上的安卓例...
    passiontim阅读 21,877评论 181 334
  • 我们已经成功得到了title,但是再仔细看看,还能发现更简便的方法。 Gumtree为标签添加了属性,就是item...
    小草_f57c阅读 317评论 0 0
  • 动态网页指几种可能: 1)需要用户交互,如常见的登录操作; 2)网页通过JS/ AJAX动态生成,如一个html里...
    小草_f57c阅读 511评论 0 0
  • jsp的实质是什么? 一、JSP的概念 我们的目标是要搞清楚什么是JSP,他和HTML的静态页面有什么区别呢? j...
    会飞的猪bj阅读 274评论 0 0
  • 欢迎使用 Cmd Markdown 编辑阅读器 我们理解您需要更便捷更高效的工具记录思想,整理笔记、知识,并将其中...
    larQ阅读 250评论 0 0