GIF,是一种动画图片格式,其原理是将多张图片放在一个文件中连续播放,从而产生动画效果,其中一张图片称为一帧。如果通过 PS 打开一个 GIF 图片,便可以看到每一帧的图片。在 Android 中加载显示一张静态的图片(比如常用的 png 格式,jpg 格式)很简单,官方提供了 ImageView 来显示一张图片,而对于动态图片(比如 GIF 格式),官方并没有直接给出可用的组件,需要开发者自行编写相关自定义组件。在 Fresco 中,也是通过编写一个自定义类(AnimatedDrawable)来加载显示 GIF 图片,本文从源码入手,分析在 Fresco 中加载 GIF 图片的原理

简单使用 GIF 图片

在介绍 Fresco 中加载 GIF 图片的实现原理之前,先看看它的简单使用

根据官方文档中的描述,使用 GIF 图片的方式和其他图片大致是相同的,下面通过代码来介绍下它的具体使用

布局

如同使用静态图片一样,使用 GIF 图片也是通过 SimpleDraweeView 显示

根据 URL 加载网络 GIF 图片

如同使用静态图片一样,GIF 图片的来源可以是网络或本地存储,示例中使用了网络加载

通过以上代码便能实现最简单的加载 GIF 图片,可以看到,设置 setAutoPlayAnimations 值为 true 就能使 GIF 图片下载完成后自动播放,如果不设置或者设置其值为 false,则没有动画,将只显示 GIF 中第一帧的图片,运行效果如下:

上面的代码是执行下载完成后自动播放动画,也可以设置手动播放,通过设置一个下载监听器监听是否下载完成,具体的步骤在官方文档中有给出,这里就不再赘述

源码分析

了解基础使用后,开始分析实现原理

怎样播放动画

很好奇为什么添加一行 setAutoPlayAnimations(true) 就能让 GIF 图片动起来,于是先分析这里的代码

通过快捷键 CTRL + 鼠标左键 点击 setAutoPlayAnimations(true) 进入到这个方法所在的类,可以看到,这个方法是在 AbstractDraweeControllerBuilder.java 类中,方法将参数的 boolean 值传递给一个全局变量 mAutoPlayAnimations, 这个变量控制是否自动播放动画,可以在下面这个方法中看到他的使用:

在这个方法中,通过 mAutoPlayAnimations 的值来判断是否给 Controller 添加一个播放的监听器,监听器的内容如下:

到这里,自动播放的思路就大致清晰了,通过一个 boolean 值来控制是否给 controller 添加监听器,监听图片是否加载完成,加载完成则调用动画的 start() 方法。

自动播放的原理是这样,那么很容易理解到手动控制动画播放的原理,就是开发者自己编写这个监听器,然后添加到 controller 中,这个代码可以在 官方文档中看到

加载动画图片

在使用中,对 controller 配置完成后,只需要调用 SimpleDraweeView 的 SetController(DraweeController controller) 方法就能完成使用,接下来就从这里入手,看看 controller 是怎样一步一步执行的

通过 CTRL + 鼠标左键点击 setController(controller) 进入到 DraweeView.java 类中看到这个方法的具体实现

这里将 controller 传递给了 DraweeHolder,接下来进入到 DraweeHolder.java 类中

进入到 attachController() 方法中

可以看到,这里主要是调用了 mController 的 onAttach() 方法,而这个 mController 在前面的 setController() 方法中由传入的 controller,最终执行了 controller 的 onAttach() 方法

那么这个 onAttach() 方法的具体实现在哪里呢,首先来梳理下 Controller 的继承关系,如下图

从图中可以看到,控制器的最终实现由 PipeDraweeViewController 和 VolleyDraweeViewController 来完成,在调用 Controller 的 onAttach() 方法后,整个图片加载的流程大致描述如下:

  • 在 onAttach() 中调用 submitRequest()
  • 在 submitRequest() 中设置监听器,订阅图片数据,添加回调
  • 如果数据加载成功,则调用 onNewResultInternal() 方法(还有一个加载失败时的回调和加载进度的回调)
  • 在 onNewResultInternal() 中将获取到的数据通过 createDrawable() 转换成 Drawable,并设置到视图中,通知监听器。

创建 Drawable

通过上面的分析,得到一个图片数据(网络加载或者本地加载)后,最终会使用 createDrawable() 方法来将 dataSource 中的数据 转换成 Drawable 才能在 Android 设备上显示。也就是说 createDrawable() 方法才是正篇文章的重点!在 AbstractDraweeController.java 中,createDrawable() 是一个抽象方法,具体实现交由其子类完成,那到底是由 PipelineDraweeController 来完成的,还是由 VolleyDraweeController 来完成的呢?可以在 Fresco 的初始化中找到答案

根据官方文档的描述,在 Application 中初始化 Fresco 的时候,需要执行如下代码:

通过查看源码,可以发现,在这个初始化过程中,默认创建了一个 PipelineDraweeControllerBuilderSupplier 用于为 SimpleDraweeView 提供控制器,也就是说,默认的控制器实现是由 PipelineDraweeController 完成的。接下来就到这个类中看看其具体的 createDrawable() 方法的实现

在这个方法中,根据不同的资源类型创建不同的图片,这里主要关注创建动态图片,可以看到,是通过 mAnimatedDrawableFactory 创建的 Drawable,创建的 Drawable 类型是自定义的 AnimatedDrawable,关于工厂模式创建的过程就不细说,下面主要看看创建出来的 AnimatedDrawable 类

自定义动画图片类 AnimatedDrawable

通过阅读源码,可以发现在 AnimatedDrawable 中,将 GIF 图片逐帧画在界面上,通过调用 start() 方法,开始逐帧更新界面:

关于AnimatedDrawable 类的具体实现可以查看源码,代码在imagepipeline/animated/base/AnimatedDrawable.java

发表评论

电子邮件地址不会被公开。 必填项已用*标注