giffun源码学习之--gif的暂停继续+使用Glide监听图片下载进度
文章目录
一、giffun简介
giffun是郭霖大佬开源的趣享gif项目,项目地址:https://github.com/guolindev/giffun
非常有学习价值。本篇介绍其中对于gif播放的控制与下载进度监听。
注:郭神在项目中使用的是Glide 3.7.0版本的源码,笔者是在Glide 4.9.0版本仿照郭神的方式实现gif图播放控制与监听图片下载进度
二、导入Glide 4.9.0
apply plugin: 'kotlin-kapt'
android{
...
}
dependencies {
...
implementation 'com.github.bumptech.glide:glide:4.9.0'
kapt 'com.github.bumptech.glide:compiler:4.9.0'
}
三、gif播放暂停、继续功能
1.效果图
2.原理
gif图的播放暂停使用的是GifDrawable.stop()方法,继续播放使用的是GifDrawable.start()方法。
3.代码实现
3.1新建GifTarget,继承自ImageViewTarget
/**
* Description:
* 提供Gif图暂停和继续的Target
*
* @author Alpinist Wang
* Company: Mobile CPX
* Date: 2019/2/20
*/
class GifTarget(iv: ImageView) : ImageViewTarget<Drawable>(iv) {
private var gifDrawable: GifDrawable? = null
override fun setResource(resource: Drawable?) {
}
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
super.onResourceReady(resource, transition)
setDrawable(resource)
if (resource is GifDrawable) {
gifDrawable = resource
}
}
/**
* 恢复GIF播放。
*/
fun resumePlaying() {
gifDrawable?.start()
}
/**
* 暂停GIF播放。
*/
fun pausePlaying() {
gifDrawable?.stop()
}
}
当图片下载完成后,先调用setDrawable(resource),将图片显示到传入的ImageView上,再将resource转为GifDrawable。暴露resumePlaying方法和pausePlaying方法控制播放和暂停。
3.2.使用Glide将图片加载到GifTarget中
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val url = "http://photocdn.sohu.com/20150806/mp26124175_1438862681216_4.gif"
val gifTarget = Glide.with(this)
.load(url)
.into(GifTarget(iv))
btn_pause.setOnClickListener {
gifTarget.pausePlaying()
}
btn_resume.setOnClickListener {
gifTarget.resumePlaying()
}
}
}
布局代码很简单,一个ImageView,两个Button
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ImageView
android:id="@+id/iv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btn_pause"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="暂停"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btn_resume"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="继续"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
四、图片加载进度监听
1.效果图
2.原理
图片加载进度监听是很常用的功能,郭神已经写过一篇文章讲述监听图片下载进度:Android图片加载框架最全解析(七),实现带进度的Glide图片加载功能:https://blog.csdn.net/guolin_blog/article/details/78357251
原理是使用OkHttp替换Glide默认的HttpUrlConnection来进行网络请求,通过OkHttpClient设置拦截器监听下载进度。
3.代码实现
3.1.将Glide库替换为OkHttp3 集成库
apply plugin: 'kotlin-kapt'
android{
...
}
dependencies {
...
implementation 'com.github.bumptech.glide:okhttp3-integration:4.9.0'
kapt 'com.github.bumptech.glide:compiler:4.9.0'
}
3.2.全局GlideModule中替换GlideUrl加载库
新建MyAppGlideModule继承AppGlideModule,在registerComponents方法中替换GlideUrl加载库为OkHttp
/**
* Description:
* Glide全局配置
*
* @author Alpinist Wang
* Company: Mobile CPX
* Date: 2019/2/20
*/
@GlideModule
class MyAppGlideModule : AppGlideModule() {
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
super.registerComponents(context, glide, registry)
val client = OkHttpClient.Builder()
.addInterceptor(ProgressInterceptor())
.build()
registry.replace(GlideUrl::class.java, InputStream::class.java, OkHttpUrlLoader.Factory(client))
}
}
其中的OkHttpClient添加了一个拦截器ProgressInterceptor。
3.3.拦截器ProgressInterceptor
先写ProgressListener接口:
/**
* Glide网络请求下载进度的监听器。
* @author guolin
* @since 2017/10/25
*/
interface ProgressListener {
/**
* 当下载进度发生变化时,会回调此方法。
* @param progress
* 当前的下载进度,参数的值范围是0-100。
*/
fun onProgress(progress: Int)
}
新建一个ProgressResponseBody类,继承自ResponseBody
/**
* 自定义用于计算Glide网络请求下载进度的ResponseBody。
* @author guolin
* @since 2017/10/25
*/
class ProgressResponseBody(url: String, private val responseBody: ResponseBody) : ResponseBody() {
private var bufferedSource: BufferedSource? = null
private var listener: ProgressListener? = null
init {
listener = ProgressInterceptor.LISTENER_MAP[url]
}
override fun contentType(): MediaType? = responseBody.contentType()
override fun contentLength(): Long = responseBody.contentLength()
override fun source(): BufferedSource {
if (bufferedSource == null) {
bufferedSource = Okio.buffer(ProgressSource(responseBody.source()))
}
return bufferedSource!!
}
private inner class ProgressSource internal constructor(source: Source) : ForwardingSource(source) {
var totalBytesRead: Long = 0
var currentProgress: Int = 0
override fun read(sink: Buffer, byteCount: Long): Long {
val bytesRead = super.read(sink, byteCount)
val fullLength = responseBody.contentLength()
if (bytesRead == -1L) {
totalBytesRead = fullLength
} else {
totalBytesRead += bytesRead
}
val progress = (100f * totalBytesRead / fullLength).toInt()
if (progress != currentProgress) {
listener?.onProgress(progress)
}
if (listener != null && totalBytesRead == fullLength) {
listener = null
}
currentProgress = progress
return bytesRead
}
}
}
在ProgressSource中重写了read()方法,根据总字节数和当前已经读取的字节数计算出进度,回调给对应url的监听器。所有的监听器是在ProgressInterceptor中通过一个MutableMap维护的:
/**
* 用于监听Glide网络请求下载进度的拦截器。
* @author guolin
* @since 2017/10/25
*/
class ProgressInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val response = chain.proceed(request)
val url = request.url().toString()
val body = response.body()
return response.newBuilder().body(ProgressResponseBody(url, body!!)).build()
}
companion object {
val LISTENER_MAP: MutableMap<String, ProgressListener> = HashMap()
fun addListener(url: String, listener: ProgressListener) {
LISTENER_MAP[url] = listener
}
fun removeListener(url: String) {
LISTENER_MAP.remove(url)
}
}
}
为了区分不同的监听器,LISTENER_MAP以图片的url作为key值,ProgressResponseBody中也是通过url取出对应监听器的。
在ProgressInterceptor的intercept方法中通过Response的newBuilder()方法来创建了一个Response对象,并把它的body替换成了刚才的ProgressResponseBody。这样就能拦截到下载进度并通过对应的监听器回调进度了。
3.4.GifTarget中添加setProgressListener方法
/**
* Description:
* 监听下载进度的GifTarget
*
* @author Alpinist Wang
* Company: Mobile CPX
* Date: 2019/2/20
*/
class GifTarget(iv: ImageView) : ImageViewTarget<Drawable>(iv) {
private var url: String? = null
override fun setResource(resource: Drawable?) {
}
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
super.onResourceReady(resource, transition)
url?.let { ProgressInterceptor.removeListener(it) }
setDrawable(resource)
}
override fun onLoadFailed(errorDrawable: Drawable?) {
super.onLoadFailed(errorDrawable)
url?.let { ProgressInterceptor.removeListener(it) }
}
/**
* 设置网络请求下载进度监听器。
* @param url
* Glide请求的url地址。
* @param listener
* 下载进度的监听器。
*/
fun setProgressListener(url: String, listener: ProgressListener): GifTarget {
this.url = url
ProgressInterceptor.addListener(this.url!!, listener)
return this
}
}
setProgressListener接收一个url和一个ProgressListener,将listener添加到ProgressInterceptor中,在图片加载完成或者失败后根据url移除监听器防止内存泄漏。
3.5.使用Glide添加下载进度监听器
使用以下方式即可监听下载进度
val gifTarget = GifTarget(iv).apply {
setProgressListener(url, object : ProgressListener {
override fun onProgress(progress: Int) {
Log.d("~~~","$progress")
}
})
}
Glide.with(this)
.load(url)
.into(gifTarget)
效果图中为了方便演示,将加载图片设置在按钮的点击事件中,为了每次点击都能重新加载,在点击事件中清空了Glide的缓存,MainActivity中代码如下:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val url = "http://photocdn.sohu.com/20150806/mp26124175_1438862681216_4.gif"
btn_start.setOnClickListener {
Glide.with(this).clear(GifTarget(iv))
Glide.get(this).clearMemory()
val gifTarget = GifTarget(iv).apply {
setProgressListener(url, object : ProgressListener {
override fun onProgress(progress: Int) {
Log.d("~~~", "$progress")
pb.progress = progress
runOnUiThread {
tv_progress.text = "加载进度:$progress%"
}
}
})
}
Glide.with(this)
.load(url)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.into(gifTarget)
}
}
}
布局文件很简单,如下:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ImageView
android:id="@+id/iv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ProgressBar
android:id="@+id/pb"
style="@style/Base.Widget.AppCompat.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="15dp"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btn_start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="开始加载"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/pb" />
<TextView
android:id="@+id/tv_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="@id/btn_start"
app:layout_constraintStart_toEndOf="@id/btn_start"
app:layout_constraintTop_toTopOf="@id/btn_start" />
</android.support.constraint.ConstraintLayout>
以上,就是giffun中使用Glide监听图片下载进度的方法。