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监听图片下载进度的方法。

原创文章 67 获赞 68 访问量 6万+

猜你喜欢

转载自blog.csdn.net/AlpinistWang/article/details/87811402