Kotlin should write the series like this:
SharedPreferences with Kotlin should be written like this
This is how it should be packaged with Kotlin (2)
preface
Glide is officially recommended by Google as an image loading library, but it is packaged and used for maintenance at the end of the project. The most common is simple encapsulation through strategy mode, which supports switching Glide, Picasso, etc. Let’s review the implementation of strategy mode, analyze its advantages and disadvantages, and then draw on the advantages of Coil to build a Glide package.
Discussion on strategy Mode
-
Write an interface, unified method name, can also have more than one method, can be added according to your needs
/** image loading policy interface */ public interface BaseImageLoaderStrategy { void loadImage(Context context,ImageOptions options); } Copy the code
-
Implement specific loading policies
/*** Specific load strategy, Glide load framework */ 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()); }}Copy the code
-
ImageOptions Build mode encapsulation
/** * Set specific parameters, too many parameters when the code clean */ public class ImageOptions { private String url; // The url of the network private int placeHolder; // The image to display when loading failed private ImageView imgView; / / ImageView instance private ImageOptions(Builder builder) { this.url = builder.url; this.placeHolder = builder.placeHolder; this.imgView = builder.imgView; } public String getUrl(a) { return url; } public int getPlaceHolder(a) { return placeHolder; } public ImageView getImgView(a) { return imgView; } public static class Builder{ private String url; private int placeHolder; private ImageView imgView; public Builder(a) { 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(a){ return new ImageOptions(this); }}}Copy the code
-
Scheduling policy
** * Image loading frame strategy mode * is designed as a singleton mode and exposes a method to set the loading mode and which image loading frame to use. * /public class ImageLoadreUtils { private static ImageLoadreUtils mInstance; private BaseImageLoaderStrategy imageLoaderStrategy; private ImageLoadreUtils(a) { Glide loading mode is used by default imageLoaderStrategy = new GlideImageLoader(); } public static ImageLoadreUtils getInstance(a) { if (mInstance == null) { synchronized (ImageLoadreUtils.class) { if (mInstance == null) { mInstance = new ImageLoadreUtils(); returnmInstance; }}}return mInstance; } /*** sets the image loading frame to use */ public void setImageLoaderStrategy(BaseImageLoaderStrategy imageLoaderStrategy){ this.imageLoaderStrategy = imageLoaderStrategy; } /*** load the image */ public void loadImage(Context context, ImageLoader imageLoader){ imageLoaderStrategy.loadImage(context,imageLoader); } Copy the code
-
Advantages and disadvantages analysis
Advantages: 1. Different picture frame loading strategies can be replaced according to requirements; 2. The same API call of Glide, can be quickly changed when the demand changes; 3. Improve code reading and extensibility
Disadvantages: Policy interface methods need to be changed or added based on requirements, resulting in constant maintenance and updates for policy implementation
What a beautiful Coil
Kotlin, thanks to its extension function, has a new image frame, Coil, to see her in action
// URL
imageView.load("https://www.example.com/image.jpg")
/ / parameters
imageView.load("https://www.example.com/image.jpg") {
crossfade(true)
placeholder(R.drawable.image)
transformations(CircleCropTransformation())
}
Copy the code
The simplicity of the API use is impressive. Is there any way to make Glide like Coil?
Glide encapsulation
-
AppGlideModule adds OkHttp progress listener management, etc
//ProgressManager is responsible for network picture progress management @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(a): Boolean { return false}}Copy the code
-
Encapsulation Glide
Glide encapsulation / * * * / 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) // By type 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 { // Error map.// Cache configuration, priority, thumbnail request.// Animation, Transformationinto(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")}}/*** clear the local cache */ suspend fun clearDiskCache(context: Context) = withContext(Dispatchers.IO) { Glide.get(context).clearDiskCache() } /*** Clear the memory cache */ @JvmStatic fun clearMemory(context: Context) { Glide.get(context).clearMemory(); } /*** cancel image loading */ @JvmStatic fun clearImage(context: Context, imageView: ImageView?). { Glide.get(context).requestManagerRetriever[context].clear(imageView!!) } /*** preload */ @JvmStatic fun preloadImage(context: Any? , url:String?). { glideRequests(context).load(url).preload() } /*** pause loading */ @JvmStatic fun pauseRequests(context: Any?). { glideRequests(context).pauseRequests() } Download / * * * / suspend fun downloadImage(context: Context, imgUrl: String?).: File? = withContext(Dispatchers.IO) { ... }}Copy the code
/** * image load library configuration, encapsulate the original load configuration properties, conversion */ class ImageOptions { /*** Load the original resource */ var res: Any? = null /*** displays the container */ var imageView: ImageView? = null /*** imageView exists context or fragment activity*/ var context: Any? = null get() { returnfield ? : imageView }/*** load placeholder resource ID. Placeholder 0 means no placeholder */ @DrawableRes var placeHolderResId = 0. Omit animations, error diagrams, and other attributesvar centerCrop: Boolean = false /*** Network progress listener */ var onProgressListener: OnProgressListener? = null /*** load listener */ var requestListener: OnImageListener? = null. Omit enumerations like cache policy and priority}Copy the code
ImageView adds extension functions
-
Create a new imageext.kt, add a common loading method and a method that has all the configuration parameters
private const val placeHolderImageView = 0 /** * load local image */ @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, // Placeholder error map @DrawableRes placeHolderResId: Int = placeHolderImageView, placeHolderDrawable: Drawable? = null.@DrawableRes errorResId: Int = placeHolderImageView, errorDrawable: Drawable? = null.@DrawableRes fallbackResId: Int = placeHolderImageView, fallbackDrawable: Drawable? = null.// Cache policy etc skipMemoryCache: Boolean = false, diskCacheStrategy: ImageOptions.DiskCache = ImageOptions.DiskCache.AUTOMATIC, / / priority priority: ImageOptions.LoadPriority = ImageOptions.LoadPriority.NORMAL, / / thumbnails thumbnail: Float = 0f, thumbnailUrl: Any? = null, size: ImageOptions.OverrideSize? = null.// GIF or animation isAnim: Boolean = true, isCrossFade: Boolean = false, isCircle: Boolean = false, isGray: Boolean = false, isFitCenter: Boolean = false, centerCrop: Boolean = false.// Output image pixel format format: Bitmap.Config? = null.// Frames are grouped together borderWidth: Int = 0, borderColor: Int = 0.// Blur a group together isBlur: Boolean = false, blurRadius: Int = 25, blurSampling: Int = 4.// Round corners are used together isRoundedCorners: Boolean = false, roundRadius: Int = 0, cornerType: ImageOptions.CornerType = ImageOptions.CornerType.ALL, // Custom converter vararg transformation: Transformation<Bitmap>, // Schedule listener, request callback listener 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 }) } Copy the code
-
Call effect, due to different requirements to use Glide API combination, extension function provides the most parameters of a loadImage function, call this function with optional parameters. The following is an example:
//url imageView.loadImage(url, placeHolder = R.color.blue) / / effects 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) / / the progress bar iv_0.loadProgressImage(url1, progressListener = object : OnProgressListener { override fun onProgress(isComplete: Boolean, percentage: Int, bytesRead: Long, totalBytes: Long) { // Track progress Log.d("TAG"."onProgress: $percentage")}})// Successful failed callback 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) is optional for other special requirements 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))) Copy the code
conclusion
-
Finally, package and distribute the JitPack repository, project open source addresses and documentation for easy use in your project
ForJrking /ImageExt: Extension function set for loading image resources based on Glide packaging ImageView (github.com)
-
Because the okHTTP-based schedule management is used, Glide @glidemodule configuration method is used. This may conflict with your project custom configuration, so we can only modify the code and rely on Module mode. Contact me if there is a better way to improve.
-
There are only a few common Android image loading libraries, and other libraries can be implemented by reference. Kotlin smells good!!