欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

giffun源码学习之--gif的暂停继续+使用Glide监听图片下载进度

程序员文章站 2022-05-26 23:30:08
...

一、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.效果图

giffun源码学习之--gif的暂停继续+使用Glide监听图片下载进度

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.效果图

giffun源码学习之--gif的暂停继续+使用Glide监听图片下载进度

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