Kotlin应该这样写系列:
前言
Glide 是 Google 官方推荐的一款图片加载库,但是大家为了项目后期的维护都会对Glide进行封装使用。最常见的就是通过策略模式简单封装,可以很好地支持切换Glide 、 Picasso 等。让我们回顾下策略模式实现,分析其优劣,再借鉴 Coil 的优点打造一个Glide封装。
策略模式探讨
-
写一个接口,统一方法名,也可以有多个方法,可以自己根据需求添加
/** 图片加载策略 接口*/ public interface BaseImageLoaderStrategy { void loadImage(Context context,ImageOptions options); }
-
实现具体的加载策略
/*** 具体的加载策略, Glide 加载框架*/ public class GlideImageLoader implements BaseImageLoaderStrategy { @Override public void loadImage(Context context, ImageOptions options) { Glide.with(context) .load(options.getUrl()) .placeholder(options.getPlaceHolder()) .into(options.getImgView()); } }
-
ImageOptions Build模式封装
/** * 设置具体的参数,参数过多时候代码整洁 */ public class ImageOptions { private String url; //网络的 url private int placeHolder; //当没有成功加载的时候显示的图片 private ImageView imgView; //ImageView 的实例 private ImageOptions(Builder builder) { this.url = builder.url; this.placeHolder = builder.placeHolder; this.imgView = builder.imgView; } public String getUrl() { return url; } public int getPlaceHolder() { return placeHolder; } public ImageView getImgView() { return imgView; } public static class Builder{ private String url; private int placeHolder; private ImageView imgView; public Builder() { this.url = ""; this.placeHolder = R.mipmap.ic_launcher; this.imgView = null; } public Builder url(String url) { this.url = url; return this; } public Builder placeHolder(int placeHolder) { this.placeHolder = placeHolder; return this; } public Builder imgView(ImageView imgView) { this.imgView = imgView; return this; } public ImageOptions bulid(){ return new ImageOptions(this); } } }
-
调度策略
** * 图片加载框架 策略模式 * 设计为单例模式,并且暴露一个方法,可以设置加载模式,使用哪种图片加载框架。 */ public class ImageLoadreUtils { private static ImageLoadreUtils mInstance; private BaseImageLoaderStrategy imageLoaderStrategy; private ImageLoadreUtils() { //默认使用 Glide 加载模式 imageLoaderStrategy = new GlideImageLoader(); } public static ImageLoadreUtils getInstance() { if (mInstance == null) { synchronized (ImageLoadreUtils.class) { if (mInstance == null) { mInstance = new ImageLoadreUtils(); return mInstance; } } } return mInstance; } /*** 设置使用的图片加载框架*/ public void setImageLoaderStrategy(BaseImageLoaderStrategy imageLoaderStrategy){ this.imageLoaderStrategy = imageLoaderStrategy; } /*** 加载图片*/ public void loadImage(Context context, ImageLoader imageLoader){ imageLoaderStrategy.loadImage(context,imageLoader); }
-
优缺点分析
优点:1.可以根据需求替换不同的图片框架加载策略;2.Glide的同一API调用,有需求变化时可以快速变换;3.提升代码阅读和扩展性
缺点:要根据需求变更或增加策略接口方法,导致策略实现需要不断维护更新
Coil 真漂亮
kotlin 的出现,由于其扩展函数的特真香,新的图片框架coil出现,来看看她的使用
// URL
imageView.load("https://www.example.com/image.jpg")
//多参数
imageView.load("https://www.example.com/image.jpg") {
crossfade(true)
placeholder(R.drawable.image)
transformations(CircleCropTransformation())
}
简洁的API使用让人眼前一亮。那有什么办法让Glide也可以像coil一样呢?
Glide封装
-
继承
AppGlideModule
添加OkHttp进度监听器管理等//ProgressManager负责网络图片进度管理 具体实现可以百度 @GlideModule(glideName = "IGlideModule") class GlideModule : AppGlideModule() { override fun applyOptions(context: Context, builder: GlideBuilder) { } override fun registerComponents(context: Context, glide: Glide, registry: Registry) { registry.replace(GlideUrl::class.java, InputStream::class.java, OkHttpUrlLoader.Factory(ProgressManager.okHttpClient)) } override fun isManifestParsingEnabled(): Boolean { return false } }
-
封装Glide
/**Glide封装*/ object GlideImageLoader { @JvmStatic fun loadImage(options: ImageOptions) { Preconditions.checkNotNull(options, "ImageConfigImpl is required") val context = options.context Preconditions.checkNotNull(context, "Context is required") Preconditions.checkNotNull(options.imageView, "ImageView is required") val requestsWith = glideRequests(context) //根据类型获取 val glideRequest = when (options.res) { is String -> requestsWith.load(options.res as String) is Bitmap -> requestsWith.load(options.res as Bitmap) is Drawable -> requestsWith.load(options.res as Drawable) is Uri -> requestsWith.load(options.res as Uri) is URL -> requestsWith.load(options.res as URL) is File -> requestsWith.load(options.res as File) is Int -> requestsWith.load(options.res as Int) is ByteArray -> requestsWith.load(options.res as ByteArray) else -> requestsWith.load(options.res) } glideRequest.apply { // 占位图、错误图 ... //缓存配置,优先级,缩略图请求 ... //动画、transformation into(GlideImageViewTarget(options.imageView, options.res)) } options.onProgressListener?.let { ProgressManager.addListener(options.res.toString(), options.onProgressListener) } } private fun glideRequests(context: Any?): GlideRequests { return when (context) { is Context -> IGlideModule.with(context) is Activity -> IGlideModule.with(context) is FragmentActivity -> IGlideModule.with(context) is Fragment -> IGlideModule.with(context) is android.app.Fragment -> IGlideModule.with(context) is View -> IGlideModule.with(context) else -> throw NullPointerException("not support") } } /*** 清除本地缓存*/ suspend fun clearDiskCache(context: Context) = withContext(Dispatchers.IO) { Glide.get(context).clearDiskCache() } /*** 清除内存缓存*/ @JvmStatic fun clearMemory(context: Context) { Glide.get(context).clearMemory(); } /*** 取消图片加载*/ @JvmStatic fun clearImage(context: Context, imageView: ImageView?) { Glide.get(context).requestManagerRetriever[context].clear(imageView!!) } /*** 预加载*/ @JvmStatic fun preloadImage(context: Any?, url: String?) { glideRequests(context).load(url).preload() } /*** 暂停加载*/ @JvmStatic fun pauseRequests(context: Any?) { glideRequests(context).pauseRequests() } /**下载*/ suspend fun downloadImage(context: Context, imgUrl: String?): File? = withContext(Dispatchers.IO) { ... } }
/** * 图片加载库的配置,封装原始加载配置属性,进行转换 */ class ImageOptions { /*** 加载原始资源*/ var res: Any? = null /*** 显示容器*/ var imageView: ImageView? = null /*** imageView存在的上下文或者fragment\activity*/ var context: Any? = null get() { return field ?: imageView } /*** 加载占位图资源ID,如果placeholder是0表示没有占位图*/ @DrawableRes var placeHolderResId = 0 .... 省略其动画、错误图等等他属性 var centerCrop: Boolean = false /*** 网络进度监听器*/ var onProgressListener: OnProgressListener? = null /*** 加载监听*/ var requestListener: OnImageListener? = null ....省略缓存策略和优先级等等枚举 }
ImageView增加扩展函数
-
新建ImageExt.kt,增加常用加载方法和一个拥有所有配置参数的方法
private const val placeHolderImageView = 0 /** * 加载本地图片 */ @JvmOverloads fun ImageView.loadImage(@RawRes @DrawableRes drawableId: Int, @RawRes @DrawableRes errorId: Int = drawableId) { this.loadImage(load = drawableId, placeHolderResId = drawableId, errorResId = errorId) } @JvmOverloads fun ImageView.loadImage(url: String?, @RawRes @DrawableRes placeHolder: Int = placeHolderImageView, @RawRes @DrawableRes errorId: Int = placeHolder, loadListener: OnImageListener? = null) { this.loadImage(load = url, placeHolderResId = placeHolder, errorResId = errorId, requestListener = loadListener) } @JvmOverloads fun ImageView.loadProgressImage(url: String?, @RawRes @DrawableRes placeHolder: Int = placeHolderImageView, progressListener: OnProgressListener? = null) { this.loadImage(load = url, placeHolderResId = placeHolder, errorResId = placeHolder, onProgressListener = progressListener) } @JvmOverloads fun ImageView.loadResizeImage(url: String?, width: Int, height: Int, @RawRes @DrawableRes placeHolder: Int = placeHolderImageView) { this.loadImage(load = url, placeHolderResId = placeHolder, errorResId = placeHolder, size = ImageOptions.OverrideSize(width, height)) } @JvmOverloads fun ImageView.loadGrayImage(url: String?, @DrawableRes placeHolder: Int = placeHolderImageView) { this.loadImage(load = url, placeHolderResId = placeHolder, errorResId = placeHolder, isGray = true) } @JvmOverloads fun ImageView.loadBlurImage(url: String?, radius: Int = 25, sampling: Int = 4, @DrawableRes placeHolder: Int = placeHolderImageView) { this.loadImage(load = url, placeHolderResId = placeHolder, errorResId = placeHolder, isBlur = true, blurRadius = radius, blurSampling = sampling) } @JvmOverloads fun ImageView.loadRoundCornerImage(url: String?, radius: Int = 0, type: ImageOptions.CornerType = ImageOptions.CornerType.ALL, @DrawableRes placeHolder: Int = placeHolderImageView) { this.loadImage(load = url, placeHolderResId = placeHolder, errorResId = placeHolder, isRoundedCorners = radius > 0, roundRadius = radius, cornerType = type) } @JvmOverloads fun ImageView.loadCircleImage(url: String?, borderWidth: Int = 0, @ColorInt borderColor: Int = 0, @DrawableRes placeHolder: Int = placeHolderImageView) { this.loadImage(load = url, placeHolderResId = placeHolder, errorResId = placeHolder, isCircle = true, borderWidth = borderWidth, borderColor = borderColor) } @JvmOverloads fun ImageView.loadBorderImage(url: String?, borderWidth: Int = 0, @ColorInt borderColor: Int = 0, @DrawableRes placeHolder: Int = placeHolderImageView) { this.loadImage(load = url, placeHolderResId = placeHolder, errorResId = placeHolder, borderWidth = borderWidth, borderColor = borderColor) } @JvmOverloads fun ImageView.loadImage(load: Any?, with: Any? = this, //占位图 错误图 @DrawableRes placeHolderResId: Int = placeHolderImageView, placeHolderDrawable: Drawable? = null, @DrawableRes errorResId: Int = placeHolderImageView, errorDrawable: Drawable? = null, @DrawableRes fallbackResId: Int = placeHolderImageView, fallbackDrawable: Drawable? = null, //缓存策略等 skipMemoryCache: Boolean = false, diskCacheStrategy: ImageOptions.DiskCache = ImageOptions.DiskCache.AUTOMATIC, //优先级 priority: ImageOptions.LoadPriority = ImageOptions.LoadPriority.NORMAL, //缩略图 thumbnail: Float = 0f, thumbnailUrl: Any? = null, size: ImageOptions.OverrideSize? = null, //gif或者动画 isAnim: Boolean = true, isCrossFade: Boolean = false, isCircle: Boolean = false, isGray: Boolean = false, isFitCenter: Boolean = false, centerCrop: Boolean = false, //输出图像像素格式 format: Bitmap.Config? = null, //边框 一组一起 borderWidth: Int = 0, borderColor: Int = 0, //模糊处理 一组一起使用 isBlur: Boolean = false, blurRadius: Int = 25, blurSampling: Int = 4, //圆角 一组一起使用 isRoundedCorners: Boolean = false, roundRadius: Int = 0, cornerType: ImageOptions.CornerType = ImageOptions.CornerType.ALL, //自定义转换器 vararg transformation: Transformation<Bitmap>, //进度监听,请求回调监听 onProgressListener: OnProgressListener? = null, requestListener: OnImageListener? = null) { GlideImageLoader.loadImage(ImageOptions().also { it.res = load it.imageView = this it.context = with it.placeHolderResId = placeHolderResId it.placeHolderDrawable = placeHolderDrawable it.errorResId = errorResId it.errorDrawable = errorDrawable it.fallbackResId = fallbackResId it.fallbackDrawable = fallbackDrawable it.isCrossFade = isCrossFade it.skipMemoryCache = skipMemoryCache it.isAnim = isAnim it.diskCacheStrategy = diskCacheStrategy it.priority = priority it.thumbnail = thumbnail it.thumbnailUrl = thumbnailUrl it.size = size it.isCircle = isCircle it.isGray = isGray it.centerCrop = centerCrop it.isFitCenter = isFitCenter it.format = format it.borderWidth = borderWidth it.borderColor = borderColor it.isBlur = isBlur it.blurRadius = blurRadius it.blurSampling = blurSampling it.isRoundedCorners = isRoundedCorners it.roundRadius = roundRadius it.cornerType = cornerType it.transformation = transformation it.onProgressListener = onProgressListener it.requestListener = requestListener }) }
-
调用效果,由于不同需求要用到Glide的api组合,扩展函数提供了参数最多的一个
loadImage
函数,调用这个函数要用可选参数的方式。示例如下://url imageView.loadImage(url, placeHolder = R.color.blue) //特效 iv_3.loadBlurImage(url4) iv_4.loadCircleImage(url1) iv_5.loadBorderImage(url1, borderWidth = 10, borderColor = Color.RED) iv_6.loadGrayImage(url1) iv_7.loadRoundCornerImage(url1, radius = 10, type = ImageOptions.CornerType.ALL) iv_8.loadResizeImage(url2, width = 400, height = 800) //进度条 iv_0.loadProgressImage(url1, progressListener = object : OnProgressListener { override fun onProgress(isComplete: Boolean, percentage: Int, bytesRead: Long, totalBytes: Long) { // 跟踪进度 Log.d("TAG", "onProgress: $percentage") } }) //成功失败回调 iv_2.loadImage(url4, loadListener = object : OnImageListener { override fun onSuccess(drawable: Drawable?) { Toast.makeText(application, R.string.load_success, Toast.LENGTH_LONG).show() } override fun onFail(msg: String?) { Toast.makeText(application, R.string.load_failed, Toast.LENGTH_LONG).show() } }) //其他特殊需求 使用可选参数,loadImage(xxx= Xxxx,yyy = Yyy)这样使用 iv_9.loadImage(load = R.drawable.test, with = MainActivity@ this, placeHolderResId = R.color.black, requestListener = object : OnImageListener { override fun onSuccess(drawable: Drawable?) { } override fun onFail(msg: String?) { } }, onProgressListener = object : OnProgressListener { override fun onProgress(isComplete: Boolean, percentage: Int, bytesRead: Long, totalBytes: Long) { } }, transformation = *arrayOf(GrayscaleTransformation(),CircleWithBorderTransformation(borderWidth = 0, borderColor = 0)))
总结
-
最后要方便的使用到项目中那就打包发布jitpack仓库,项目开源地址和文档
forJrking/ImageExt: 基于Glide封装ImageView加载图片资源的扩展函数集 (github.com)
-
由于使用到基于okhttp的进度管理所以使用了 Glide 的@GlideModule配置方法,这样可能会和你项目自定义配置有冲突,目前只能拉代码自己修改,然后依赖Module方式了。如有更好方式联系我改进。
-
Android图片加载库常见的只有几种,其他库可以自行参考实现。Kotlin真香!!!