美文网首页
Lottie不一样的使用

Lottie不一样的使用

作者: 草帽小子J | 来源:发表于2019-01-17 20:16 被阅读0次

    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的值;

    相关文章

      网友评论

          本文标题:Lottie不一样的使用

          本文链接:https://www.haomeiwen.com/subject/jrdidqtx.html