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!!