Android 图片加载库之Coil的详解与使用

一、介绍

        在Android,资源的呈现主要有三大形式:文字、图片、视频。图片有分为本地资源和网络资源。

网络资源需要通过下载然后绑定到ImageView中。

        在前期我们使用的图片加载框架如:picasso、gliade、imageload。这些框架都可以满足我们正常的图片加载到显示,且这些框架都是通过Java语言开发出来。接下来,我将介绍一款以kotlin语言开发的框架Coil。

Coil的介绍

一、介绍

 是一个 Android 图片加载库,通过Kotlin协程的方式加载图片

优势

  • 更快:

    Coil 在性能上有很多优化,包括内存缓存和磁盘缓存,把缩略图存保存在内存中,循环利用 bitmap,自动暂停和取消图片网络请求等。

  • 更轻量级:

    Coil 的包体很小,很多API依赖项目中的其他模块的框架(如:前提是你的 APP 里面集成了 OkHttp 和 Coroutines),Coil 和 Picasso 的方法数差不多,相比 Glide 和 Fresco 要轻量很多,kotlin的语法支持,代码更简洁。

  • 更容易使用:

    Coil 的 API 充分利用了 Kotlin 语言的新特性,简化和减少了很多样板代码,采用了类方法扩展,常用的API已通过ImageViews扩展到了ImageView中,可以直接使用

  • 更流行更新:

    Coil 首选 Kotlin 语言开发并且使用包含 Coroutines, OkHttp, Okio 和 AndroidX Lifecycles 在内最流行的开源库,很好的兼容最新Jetpack组件。

2、集成

io.coil-kt:coil-base : 基础组件,提供了基本的图片请求、图片解码、图片缓存等
io.coil-kt:coil : 默认组件,依赖于io.coil-kt:coil-base,提供了 Coil 类的单例对象以及 ImageViews 相关的扩展函数
io.coil-kt:coil-gif : 用于支持解码 GIFs
io.coil-kt:coil-svg : 用于支持解码 SVG
io.coil-kt:coil-video : 包含两个 fetchers 用于支持读取和解码 任何 Android 的支持的视频格式 的视频帧

//依赖库

    implementation("io.coil-kt:coil:1.2.2")
//    //选择添加
    implementation("io.coil-kt:coil-gif:1.2.2")//支持GIF
    implementation("io.coil-kt:coil-svg:1.2.2")//支持SVG
    implementation("io.coil-kt:coil-video:1.2.2")//支持Video

3、使用

由于在kotlin支持方法的扩展,所以ImageViews已支持提供了load方法,默认只需要传入图片地址即可。

@JvmSynthetic
inline fun ImageView.load(
    uri: String?,
    imageLoader: ImageLoader = context.imageLoader,
    builder: ImageRequest.Builder.() -> Unit = {}
): Disposable = loadAny(uri, imageLoader, builder)

/** @see ImageView.loadAny */
@JvmSynthetic
inline fun ImageView.load(
    url: HttpUrl?,
    imageLoader: ImageLoader = context.imageLoader,
    builder: ImageRequest.Builder.() -> Unit = {}
): Disposable = loadAny(url, imageLoader, builder)

/** @see ImageView.loadAny */
@JvmSynthetic
inline fun ImageView.load(
    uri: Uri?,
    imageLoader: ImageLoader = context.imageLoader,
    builder: ImageRequest.Builder.() -> Unit = {}
): Disposable = loadAny(uri, imageLoader, builder)

/** @see ImageView.loadAny */
@JvmSynthetic
inline fun ImageView.load(
    file: File?,
    imageLoader: ImageLoader = context.imageLoader,
    builder: ImageRequest.Builder.() -> Unit = {}
): Disposable = loadAny(file, imageLoader, builder)

/** @see ImageView.loadAny */
@JvmSynthetic
inline fun ImageView.load(
    @DrawableRes drawableResId: Int,
    imageLoader: ImageLoader = context.imageLoader,
    builder: ImageRequest.Builder.() -> Unit = {}
): Disposable = loadAny(drawableResId, imageLoader, builder)

/** @see ImageView.loadAny */
@JvmSynthetic
inline fun ImageView.load(
    drawable: Drawable?,
    imageLoader: ImageLoader = context.imageLoader,
    builder: ImageRequest.Builder.() -> Unit = {}
): Disposable = loadAny(drawable, imageLoader, builder)

/** @see ImageView.loadAny */
@JvmSynthetic
inline fun ImageView.load(
    bitmap: Bitmap?,
    imageLoader: ImageLoader = context.imageLoader,
    builder: ImageRequest.Builder.() -> Unit = {}
): Disposable = loadAny(bitmap, imageLoader, builder)

load支持的请求类型

uri: String:图片地址
url: HttpUrl:封装好的请求url
uri: Uri:Uri的资源封装
file: File:文件类型
drawableResId: Int:资源id
drawable: Drawable:drawable对象
bitmap: Bitmap:bitmap对象

加载load,返回一个Disposable,Disposable是IPC的接口,如下

interface Disposable {

    /**
     * Returns true if the request is complete or cancelling.
     */
    val isDisposed: Boolean

    /**
     * Cancels any in progress work and frees any resources associated with this request. This method is idempotent.
     */
    fun dispose()

    /**
     * Suspends until any in progress work completes.
     */
    @ExperimentalCoilApi
    suspend fun await()
}

通过Disposable可以判断是否完成,主动调用以及等待。

扩展:

Coil不仅提供了默认的加载,还可以扩展其他。

inline fun ImageView.load(
    uri: String?,
    imageLoader: ImageLoader = context.imageLoader,
    builder: ImageRequest.Builder.() -> Unit = {}
): Disposable = loadAny(uri, imageLoader, builder)

通过源码可以看到,requestbuild是支持扩展的。默认后面是{},可以在{}体中调用build中方法。

         bind.image.load(url){
                //build 扩展体
                crossfade(true) //渐进渐出
                placeholder(R.mipmap.ic_launcher) //加载中占位图
                error(R.mipmap.ic_launcher)  //加载失败占位图
                allowHardware(true)//硬件加速
                allowRgb565(true)//支持565格式

                lifecycle(lifecycle)//生命周期关联
                var default= DefaultRequestOptions()
                defaults(default)
            }

包括网络缓存等,我们可以自己根据业务配置。如果你需要可以自定义一个ImageRequest.Build

ImageView高阶变换

Transformation(图片变换)

  • BlurTransformation() : 高斯模糊变换
  • CircleCropTransformation() : 圆形裁剪变换
  • GrayscaleTransformation() : 灰度变换
  • RoundedCornersTransformation() : 圆角变换

高阶图片变形通过transformations()来完成,支持多样式。

BlurTransformation() : 高斯模糊变换

class BlurTransformation @JvmOverloads constructor(
    private val context: Context,
    private val radius: Float = DEFAULT_RADIUS,
    private val sampling: Float = DEFAULT_SAMPLING
)
 init {
        require(radius in 0.0..25.0) { "radius must be in [0, 25]." }
        require(sampling > 0) { "sampling must be > 0." }
    }

1.radius:模糊半径范围在0到25之间,数值越大,模糊越深

2.sampling:采样率必须大于,这个值是进行缩放图片大小的,(0,1),图片是越来越大,[1,++oo),图片是越来越小

GrayscaleTransformation() : 灰度变换

这个主要把彩色的图片置灰,常见特殊忌日,可以设置,利用了ColorMatrixColorFilter对颜色进行过滤。

RoundedCornersTransformation:圆角角度设置

class RoundedCornersTransformation(
    @Px private val topLeft: Float = 0f,
    @Px private val topRight: Float = 0f,
    @Px private val bottomLeft: Float = 0f,
    @Px private val bottomRight: Float = 0f
) 

A=topleft ,B=topright,C=bottomLeft,D=bottomRight。

如果只传一个参数,默认就是四个角的角度,角度默认值是0,任何角的坐标都是(x,y),所以你设置角度无法只设置一个叫的一半。

如果想了解view的角度自定义,可以查看:

Android ImageView 四个角自定义角度,以及角度的变换_addroundrect_蜗牛、Z的博客-CSDN博客

参数扩展

有人用习惯了Glide或者imageload,会发现,可以通过配置来管理加载中,加载失败等标识,Coil同样也支持,只是通过build提供的方法来完成。

inline fun ImageView.load(
    uri: String?,
    imageLoader: ImageLoader = context.imageLoader,
    builder: ImageRequest.Builder.() -> Unit = {}
): Disposable = loadAny(uri, imageLoader, builder)

        bind.load.setOnClickListener {
            bind.image.load("https://img-blog.csdnimg.cn/3a4114c916904ecbadb7a71b77294eef.gif"){
                crossfade(true) //渐进渐出
                placeholder(R.mipmap.ic_launcher) //加载中占位图
                error(R.mipmap.ic_launcher)  //加载失败占位图
                allowHardware(true)//硬件加速
//                transformations(BlurTransformation(context,25f,2f))
                //圆角
                transformations(RoundedCornersTransformation(90f,0f,0f,0f))
            }
        }

有人会有疑问:

builder: ImageRequest.Builder.() -> Unit = {}

这个写法,是返回builder类,通过指向类的内部方法来完成,最后返回build。

小试牛刀:

class Build {

      var msgs: String? = ""

    fun setMsg(msg: String) = apply {
        this.msgs = msg
    }
}


 

class TestBody(var build: Build.() -> Unit = {}) {

    fun log() {

        var mBuild = Build()
        mBuild.apply(build)
        mBuild.msgs?.let { MyLog.log(it) }
    }
}


fun main() {
    var test = TestBody({
        setMsg("hello")
    })

    test.log()
}

build是一个block object的,我们可以通过Build对象apply方法copy这个对象。

GIF动态图片加载

        Coil库不仅支持静态图片,也支持动态图片。只是动态和静态的解析对象不同ComponentRegistry在注册的时候,已提供了各种拦截与支持。

依赖库的引入:

implementation("io.coil-kt:coil-gif:1.2.2")//支持GIF

decoder目前提供两种方式:

api<28

/**
 * A [Decoder] that uses [Movie] to decode GIFs.
 *
 * NOTE: Prefer using [ImageDecoderDecoder] on API 28 and above.
 */
class GifDecoder : Decoder

api>=28

/**
 * A [Decoder] that uses [ImageDecoder] to decode GIFs, animated WebPs, and animated HEIFs.
 *
 * NOTE: Animated HEIF files are only supported on API 30 and above.
 */
@RequiresApi(28)
class ImageDecoderDecoder : Decoder

ImageLoad初始化:

注意GIFDecoder类的顶部注释:

NOTE: Prefer using ImageDecoderDecoder on API 28 and above.

        //创建 gif ImageLoader 实例
        val imageLoader = ImageLoader.Builder(applicationContext)
            .componentRegistry {
                if (SDK_INT >= 28) {
                    add(ImageDecoderDecoder(applicationContext))
                } else {
                    add(GifDecoder())
                }
            }.build()
        //设置全局唯一实例
        Coil.setImageLoader(imageLoader)

这样我们就完成了Gif的ImageLoader的设置

如何监听图片加载:

ImageRequest提供了fun listener(listener: Listener?) 方法。
bind.image.load("https://img-blog.csdnimg.cn/3a4114c916904ecbadb7a71b77294eef.gif") {
  
                listener(object : ImageRequest.Listener {
                    override fun onCancel(request: ImageRequest) {
                        super.onCancel(request)
                    }

                    override fun onSuccess(request: ImageRequest, metadata: ImageResult.Metadata) {
                        super.onSuccess(request, metadata)
                    }

                    override fun onError(request: ImageRequest, throwable: Throwable) {
                        super.onError(request, throwable)
                    }

                    override fun onStart(request: ImageRequest) {
                        super.onStart(request)
                    }
                })
            }

关于配置

上面都是通过方法设置配置参数,如何设置全局配置?需要我们自己去设置一个全局的 ImageLoader.Builder

  val okHttpClient = OkHttpClient.Builder()
            .cache(CoilUtils.createDefaultCache(this))
            .build()

        val imageLoader = ImageLoader.Builder(this)
            .availableMemoryPercentage(0.5f)
            .diskCachePolicy(CachePolicy.ENABLED)  //磁盘缓策略 ENABLED、READ_ONLY、WRITE_ONLY、DISABLED
            .crossfade(true) //淡入淡出
            .okHttpClient {  //设置okhttpClient实例
                okHttpClient
            }.build()

        Coil.setImageLoader(imageLoader)

api介绍:

1.availableMemoryPercentage:

设置用于此 ImageLoader 的内存缓存和位图池的可用内存百分比,如果设置了百分百,那么cache将会失效

//源码
   fun availableMemoryPercentage(@FloatRange(from = 0.0, to = 1.0) percent: Double) = apply {
            require(percent in 0.0..1.0) { "Percent must be in the range [0.0, 1.0]." }
            this.availableMemoryPercentage = percent
            this.memoryCache = null
        }


2.memoryCachePolicy :

内存缓存策略,有4中策略;


3.diskCachePolicy: 

磁盘缓存策略,方式和内存策略一致;

CachePolicy.ENABLED : 可读可写
CachePolicy.READ_ONLY : 只读
CachePolicy.WRITE_ONLY : 只写
CachePolicy.DISABLED : 不可读不可写,即禁用

短视频首帧

        Coil还有特别特别项,支持短视频首帧图片加载。正常在项目中,如果该项目有短视频业务,在视频不播放的状态下,如何获取首帧图片?这个问题是很多公司思考的问题。

        大公司在处理视频首帧都是通过服务器跑的。这样,在视频集在列表展示只处理单张图片即可。针对某一个视频,特别是小公司的时候,Coil明显很友好,可以在接收的范围类,降低技术和服务器的成本。

依赖库的引入:

implementation("io.coil-kt:coil-video:1.2.2")//支持Video

添加VideoDecoder


        //创建 gif ImageLoader 实例
        val imageLoader = ImageLoader.Builder(applicationContext)
            .componentRegistry {
               add(VideoFrameDecoder(context))
            }.build()

        //设置全局唯一实例
        Coil.setImageLoader(imageLoader)

剩余的设置,都是通用设置,如果项目需要支持GIF、VideoFrame,可以同时支持进去,都是通过componentRegistry add添加,可以追加进去。


 

猜你喜欢

转载自blog.csdn.net/qq36246172/article/details/129764778