ImageLoader

Introduction: 基于 Glide 的二次封装,方便使用,而且增加了多种滤镜,加载本地多边形(适配至 Glide 4.0 的版本)
More: Author   ReportBugs   OfficialWebsite   
Tags:
图片滤镜-

这是本人基于原作者对于 ImagLoader 进行 gradle 4.0 的适配,特修改 readme.md 以适应源码的变更。本人水平有限,在适配过程中,有处理不妥之处,请指出。感谢原作者的封转思想,十分收益。

  • 有人可能会问为什么选择 Glide 进行二次封装?
  • 那么又有人问了 Glide 直接能使用了,已经很方便了,为啥还要封装?
    • 入口统一,所有图片加载都在这一个地方管理,一目了然,即使有什么改动我也只需要改这一个类就可以了。
    • 虽然现在的第三方库已经非常好用,但是如果我们看到第三方库就拿来用的话,很可能在第三方库无法满足业务需求或者停止维护的时候,发现替换库,工作量可见一斑。这就是不封装在切库时面临的窘境!
    • 外部表现一致,内部灵活处理原则
    • 更多内容参考:如何正确使用开源项目?

初识 Glide

Glide 配置

1、 在 build.gradle 中添加依赖:

dependencies {
  compile 'com.github.bumptech.glide:glide:4.6.1'
  compile 'com.android.support:support-v4:19.1.0'
}

Or Maven:

<dependency>
  <groupId>com.github.bumptech.glide</groupId>
  <artifactId>glide</artifactId>
  <version>4.6.1</version>
</dependency>
<dependency>
  <groupId>com.google.android</groupId>
  <artifactId>support-v4</artifactId>
  <version>r7</version>
</dependency>

2、混淆

-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
  **[] $VALUES;
  public *;
}

# for DexGuard only
-keepresourcexmlelements manifest/application/meta-data@value=GlideModule

3、权限 如果是联网获取图片或者本地存储需要添加以下权限:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

Glide 基本使用

Glide 使用一个流接口(Fluent Interface)。用 Glide 完成一个完整的图片加载功能请求,需要向其构造器中至少传入 3 个参数,分别是:

  • with(Context context)- Context 是许多 Android API 需要调用的, Glide 也不例外。这里 Glide 非常方便,你可以任意传递一个 Activity 或者 Fragment 对象,它都可以自动提取出上下文。
  • load(String imageUrl) - 这里传入的是你要加载的图片的 URL,大多数情况下这个 String 类型的变量会链接到一个网络图片。
  • into(ImageView targetImageView) - 将你所希望解析的图片传递给所要显示的 ImageView。

example:

ImageView targetImageView = (ImageView) findViewById(R.id.imageView);
String internetUrl = "http://i.imgur.com/DvpvklR.png";

Glide
    .with(context)
    .load(internetUrl)
    .into(targetImageView);

Compatibility

  • Android SDK: Glide requires a minimum API level of 10.
  • OkHttp 2.x: there are optional dependencies available called okhttp-integration, see Integration Libraries wiki page.
  • OkHttp 3.x: there are optional dependencies available called okhttp3-integration, see Integration Libraries wiki page.
  • Volley: there are optional dependencies available called volley-integration, see Integration Libraries wiki page.
  • Round Pictures: CircleImageView/CircularImageView/RoundedImageView are known to have issues with TransitionDrawable (.crossFade() with .thumbnail() or .placeholder()) and animated GIFs, use a BitmapTransformation (.circleCrop() will be available in v4) or .dontAnimate() to fix the issue.
  • Huge Images (maps, comic strips): Glide can load huge images by downsampling them, but does not support zooming and panning ImageViews as they require special resource optimizations (such as tiling) to work without OutOfMemoryErrors.

常用 API

  • thumbnail(float sizeMultiplier). 请求给定系数的缩略图。如果缩略图比全尺寸图先加载完,就显示缩略图,否则就不显示。系数 sizeMultiplier 必须在(0,1)之间,可以递归调用该方法。
  • (DiskCacheStrategy strategy).设置缓存策略。> -DiskCacheStrategy.SOURCE:缓存原始数据,DiskCacheStrategy.RESULT:缓存变换(如缩放、裁剪等)后的资源数据,DiskCacheStrategy.NONE:什么都不缓存,DiskCacheStrategy.ALL:缓存 SOURC 和 RESULT。默认采用> -> -DiskCacheStrategy.RESULT 策略,对于 download only 操作要使用> -DiskCacheStrategy.SOURCE。
  • priority(Priority priority). 指定加载的优先级,优先级越高越优先加载,但不保证所有图片都按序加载。枚举 Priority.IMMEDIATE,Priority.HIGH,Priority.NORMAL,Priority.LOW。默认为 Priority.NORMAL。
  • dontAnimate() 移除所有的动画。
  • animate(int animationId). 在异步加载资源完成时会执行该动画。
  • animate(ViewPropertyAnimation.Animator animator). 在异步加载资源完成时会执行该动画。
  • animate(Animation animation). 在异步加载资源完成时会执行该动画。
  • placeholder(int resourceId). 设置资源加载过程中的占位 Drawable。
  • error(int resourceId). 设置 load 失败时显示的 Drawable。
  • skipMemoryCache(boolean skip). 设置是否跳过内存缓存,但不保证一定不被缓存(比如请求已经在加载资源且没设置跳过内存缓存,这个资源就会被缓存在内存中)。
  • override(int width, int height). 重新设置 Target 的宽高值(单位为 pixel)。
  • into(Y target).设置资源将被加载到的 Target。
  • into(ImageView view). 设置资源将被加载到的 ImageView。取消该 ImageView 之前所有的加载并释放资源。
  • into(int width, int height). 后台线程加载时要加载资源的宽高值(单位为 pixel)。
  • preload(int width, int height). 预加载 resource 到缓存中(单位为 pixel)。
  • asBitmap(). 无论资源是不是 gif 动画,都作为 Bitmap 对待。如果是 gif 动画会停在第一帧。
  • asGif(). 把资源作为 GifDrawable 对待。如果资源不是 gif 动画将会失败,会回调.error()。

更多 Glide 详细介绍可以看Glide 官网以及Glide 教程系列文章

如何封装

明白了为什么封装以及基本原理,接下来我们就要开工,大干一场。

先看一下本人封装后的基本使用样式:

ImageLoader.with(this)
    .url("http://img.yxbao.com/news/image/201703/13/7bda462477.gif")
    .placeHolder(R.mipmap.ic_launcher,false)
    .rectRoundCorner(30, R.color.colorPrimary)
    .blur(40)
    .into(iv_round);

更多属性我们后再详细讲解使用,主要先来看看具体的封装。

先看一下 uml:

这里写图片描述

使用者只需要关心 ImageLoader 就好了,就算里面封装的库更换、更新也没关系,因为对外的接口是不变的。实际操作中是由实现了 ILoader 的具体类去操作的,这里我们只封装了 GlideLoader,其实所有操作都是由 ImageLoader 下发指令,由 GlideLoader 具体去实现的。这里如果想封装别的第三方库,只需要实现 ILoader 自己去完成里面的方法。

初始化

    public static int CACHE_IMAGE_SIZE = 250;

    public static void init(final Context context) {
        init(context, CACHE_IMAGE_SIZE);
    }

    public static void init(final Context context, int cacheSizeInM) {
        init(context, cacheSizeInM, MemoryCategory.NORMAL);
    }

    public static void init(final Context context, int cacheSizeInM, MemoryCategory memoryCategory) {
        init(context, cacheSizeInM, memoryCategory, true);
    }

    /**
     * @param context        上下文
     * @param cacheSizeInM   Glide 默认磁盘缓存最大容量 250MB
     * @param memoryCategory 调整内存缓存的大小 LOW(0.5f) / NORMAL(1f) / HIGH(1.5f);
     * @param isInternalCD   true 磁盘缓存到应用的内部目录 / false 磁盘缓存到外部存
     */
    public static void init(final Context context, int cacheSizeInM, MemoryCategory memoryCategory, boolean isInternalCD) {
        ImageLoader.context = context;
        GlobalConfig.init(context, cacheSizeInM, memoryCategory, isInternalCD);
    }

从这里可以看出我们提供了四个构造器,这里注释详细说明了所有参数的用法及意义。

除了初始化,我们还需要在 Application 中重写以下方法: (已经废弃,Glide4.0 之后,在 Glide 自身的生命周期中会主动调用释放 onTrimMemory(level) 和 onLowMemory(),不建议外部调用)

    @Override
    public void onTrimMemory(int level) {
        super.onTrimMemory(level);
        // 程序在内存清理的时候执行
        ImageLoader.trimMemory(level);
    }

    @Override
    public void onLowMemory() {
        super.onLowMemory();
        // 低内存的时候执行
        ImageLoader.clearAllMemoryCaches();
    }

上面这两个方法会在下面 ImageLoader 中介绍到。

你所关心的类--ImageLoader

ImageLoader 是封装好所有的方法供用户使用的,让我们看看都有什么方法:

  • ImageLoader.init(Context context) //初始化
  • ImageLoader.trimMemory(int level);
  • ImageLoader.clearAllMemoryCaches();
  • ImageLoader.getActualLoader(); //获取当前的 loader
  • ImageLoader.with(Context context) //加载图片
  • ImageLoader.saveImageIntoGallery(String url) // 保存图片到相册
  • ImageLoader.pauseRequests() //取消请求
  • ImageLoader.resumeRequests() //回复的请求(当列表在滑动的时候,调用 pauseRequests()取消请求,滑动停止时,调用 resumeRequests()恢复请求 等等)
  • ImageLoader.clearDiskCache()//清除磁盘缓存(必须在后台线程中调用)
  • ImageLoader.clearMomoryCache(View view) //清除指定 view 的缓存
  • ImageLoader.clearMomory() // 清除内存缓存(必须在 UI 线程中调用)

图片的各种设置信息--SingleConfig

我们所设置图片的所有属性都写在这个类里面。下面我们详细的看一下:

  • url(String url) //支持 filepath、图片链接、contenProvider、资源 id 四种
  • thumbnail(float thumbnail)//缩略图
  • rectRoundCorner(int rectRoundRadius, int overlayColorWhenGif) //形状为圆角矩形时的圆角半径
  • asSquare() //形状为正方形
  • colorFilter(int color) //颜色滤镜
  • diskCacheStrategy(DiskCacheStrategy diskCacheStrategy)
    1. DiskCacheStrategy.ALL 使用 DATA 和 RESOURCE 缓存远程数据,仅使用 RESOURCE 来缓存本地数据。
    2. DiskCacheStrategy.NONE 不使用磁盘缓存
    3. DiskCacheStrategy.DATA 在资源解码前就将原始数据写入磁盘缓存
    4. DiskCacheStrategy.RESOURCE 在资源解码后将数据写入磁盘缓存,即经过缩放等转换后的图片资源。
    5. DiskCacheStrategy.AUTOMATIC 根据原始图片数据和资源编码策略来自动选择磁盘缓存策略。(默认)
  • asCircle(int overlayColorWhenGif)//加载圆形图片
  • placeHolder(int placeHolderResId) //占位图
  • override(int oWidth, int oHeight) //加载图片时设置分辨率 a
  • scale(int scaleMode) // CENTER_CROP 等比例缩放图片,直到图片的狂高都大于等于 ImageView 的宽度,然后截取中间的显示 ; FIT_CENTER 等比例缩放图片,宽或者是高等于 ImageView 的宽或者是高 默认:FIT_CENTER
  • animate(int animationId ) 引入动画
  • animate( Animation animation) 引入动画
  • animate(ViewPropertyAnimation.Animator animato) 引入动画
  • asBitmap(BitmapListener bitmapListener)// 使用 bitmap 不显示到 imageview
  • into(View targetView) //展示到 imageview
  • colorFilter(int filteColor) //颜色滤镜
  • blur(int blurRadius) //高斯模糊
  • brightnessFilter(float level) //调节图片亮度
  • grayscaleFilter() //黑白效果
  • swirlFilter() //漩涡效果
  • toonFilter() //油画效果
  • sepiaFilter() //水墨画效果
  • contrastFilter(float constrasrLevel) //锐化效果
  • invertFilter() //胶片效果
  • pixelationFilter(float pixelationLevel) //马赛克效果
  • sketchFilter() // //素描效果
  • vignetteFilter() //晕映效果

github 项目地址

中转站--GlideLoader

GlideLoader 实现 ILoader 接口。在使用的时候我们虽然不用关心这个类,但是了解一下主要做了什么功能还是不错的。

/**
 * Created by doudou on 2017/4/10. * 参考:
 * https://mrfu.me/2016/02/28/Glide_Sries_Roundup/
 * modify by jacklyy
 */
public class GlideLoader implements ILoader {

    /**
     * @param context        上下文
     * @param cacheSizeInM   Glide 默认磁盘缓存最大容量 250MB
     * @param memoryCategory 调整内存缓存的大小 LOW(0.5f) / NORMAL(1f) / HIGH(1.5f);
     * @param isInternalCD   true 磁盘缓存到应用的内部目录 / false 磁盘缓存到外部存
     */
    @Override
    public void init(Context context, int cacheSizeInM, MemoryCategory memoryCategory, boolean isInternalCD) {
        Glide.get(context).setMemoryCategory(memoryCategory); //如果在应用当中想要调整内存缓存的大小,开发者可以通过如下方式:
        GlideBuilder builder = new GlideBuilder();
        if (isInternalCD) {
            builder.setDiskCache(new InternalCacheDiskCacheFactory(context, cacheSizeInM * 1024 * 1024));
        } else {
            builder.setDiskCache(new ExternalPreferredCacheDiskCacheFactory(context, cacheSizeInM * 1024 * 1024));
        }

    }

    @Override
    public void request(final SingleConfig config) {
        RequestOptions requestOptions = getRequestOptions(config);//得到初始的 RequestOptions

        RequestBuilder requestBuilder = getRequestBuilder(config); //得到一个正确类型的 RequestBuilder(bitmap or 其他加载)

        if (requestBuilder == null) {
            return;
        }

        requestBuilder.apply(requestOptions);//应用 RequestOptions


        //设置缩略图
        if (config.getThumbnail() != 0) { //设置缩略比例
            requestBuilder.thumbnail(config.getThumbnail());
        }

        //设置图片加载动画
        setAnimator(config, requestBuilder);

        if (config.isAsBitmap()) {//如果是获取 bitmap,则回调
            SimpleTarget target = new SimpleTarget<Bitmap>(config.getWidth(), config.getHeight()) {
                @Override
                public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {
                    if (config.getBitmapListener() != null) {
                        config.getBitmapListener().onSuccess(resource);
                    }
                }

            };
            requestBuilder.into(target);
        } else {//如果是加载图片,(无论是否为 Gif)
            if (config.getTarget() instanceof ImageView) {
                requestBuilder.into((ImageView) config.getTarget());
            }
        }

    }

    private RequestOptions getRequestOptions(SingleConfig config) {
        RequestOptions options = new RequestOptions();
        //设置磁盘缓存
        if (config.getDiskCacheStrategy() != null) {
            options.diskCacheStrategy(config.getDiskCacheStrategy());
        }else{
            options.diskCacheStrategy(DiskCacheStrategy.AUTOMATIC);//默认为自动选择
        }
        if (ImageUtil.shouldSetPlaceHolder(config)) {
            options = options.placeholder(config.getPlaceHolderResId());
        }

        int scaleMode = config.getScaleMode();

        switch (scaleMode) {
            case ScaleMode.CENTER_CROP:
                options.centerCrop();
                break;
            case ScaleMode.FIT_CENTER:
                options.fitCenter();
                break;
            default:
                options.fitCenter();
                break;
        }

        //设置图片加载的分辨 sp
        if (config.getoWidth() != 0 && config.getoHeight() != 0) {
            options.override(config.getoWidth(), config.getoHeight());
        }


        //设置图片加载优先级
        setPriority(config, options);

        if (config.getErrorResId() > 0) {
            options.error(config.getErrorResId());
        }
        setShapeModeAndBlur(config, options);//设置 RequestOptions 关于 多重变换
        return options;
    }


    /**
     * 设置加载优先级
     *
     * @param config
     * @param options
     */
    private void setPriority(SingleConfig config, RequestOptions options) {
        switch (config.getPriority()) {
            case PriorityMode.PRIORITY_LOW:
                options.priority(Priority.LOW);
                break;
            case PriorityMode.PRIORITY_NORMAL:
                options.priority(Priority.NORMAL);
                break;
            case PriorityMode.PRIORITY_HIGH:
                options.priority(Priority.HIGH);
                break;
            case PriorityMode.PRIORITY_IMMEDIATE:
                options.priority(Priority.IMMEDIATE);
                break;
            default:
                options.priority(Priority.IMMEDIATE);
                break;
        }
    }

    /**
     * 设置加载进入动画
     *
     * @param config
     * @param request
     */
    private void setAnimator(SingleConfig config, RequestBuilder request) {
        if (config.getAnimationType() == AnimationMode.ANIMATIONID) {
            GenericTransitionOptions genericTransitionOptions = GenericTransitionOptions.with(config.getAnimationId());
            request.transition(genericTransitionOptions);
        } else if (config.getAnimationType() == AnimationMode.ANIMATOR) {
            GenericTransitionOptions genericTransitionOptions = GenericTransitionOptions.with(config.getAnimator());
            request.transition(genericTransitionOptions);
        } else if (config.getAnimationType() == AnimationMode.ANIMATION) {
            GenericTransitionOptions genericTransitionOptions = GenericTransitionOptions.with(new ViewAnimationFactory(config.getAnimation()));
            request.transition(genericTransitionOptions);
        } else {//设置默认的交叉淡入动画
            request.transition(DrawableTransitionOptions.withCrossFade());
        }
    }

    @Nullable
    private RequestBuilder getRequestBuilder(SingleConfig config) {

        RequestManager requestManager = Glide.with(config.getContext());
        RequestBuilder request = null;
        if (config.isAsBitmap()) {
            request = requestManager.asBitmap();

        } else if (config.isGif()) {
            request = requestManager.asGif();
        } else {
            request = requestManager.asDrawable();
        }
        if (!TextUtils.isEmpty(config.getUrl())) {
            request.load(ImageUtil.appendUrl(config.getUrl()));
            Log.e("TAG", "getUrl : " + config.getUrl());
        } else if (!TextUtils.isEmpty(config.getFilePath())) {
            request.load(ImageUtil.appendUrl(config.getFilePath()));
            Log.e("TAG", "getFilePath : " + config.getFilePath());
        } else if (!TextUtils.isEmpty(config.getContentProvider())) {
            request.load(Uri.parse(config.getContentProvider()));
            Log.e("TAG", "getContentProvider : " + config.getContentProvider());
        } else if (config.getResId() > 0) {
            request.load(config.getResId());
            Log.e("TAG", "getResId : " + config.getResId());
        } else if (config.getFile() != null) {
            request.load(config.getFile());
            Log.e("TAG", "getFile : " + config.getFile());
        } else if (!TextUtils.isEmpty(config.getAssertspath())) {
            request.load(config.getAssertspath());
            Log.e("TAG", "getAssertspath : " + config.getAssertspath());
        } else if (!TextUtils.isEmpty(config.getRawPath())) {
            request.load(config.getRawPath());
            Log.e("TAG", "getRawPath : " + config.getRawPath());
        }
        return request;
    }

    /**
     * 设置图片滤镜和形状
     *
     * @param config
     * @param options
     */
    private void setShapeModeAndBlur(SingleConfig config, RequestOptions options) {

        int count = 0;

        Transformation[] transformation = new Transformation[statisticsCount(config)];

        if (config.isNeedBlur()) {
            transformation[count] = new BlurTransformation(config.getBlurRadius());
            count++;
        }

        if (config.isNeedBrightness()) {
            transformation[count] = new BrightnessFilterTransformation(config.getBrightnessLeve()); //亮度
            count++;
        }

        if (config.isNeedGrayscale()) {
            transformation[count] = new GrayscaleTransformation(); //黑白效果
            count++;
        }

        if (config.isNeedFilteColor()) {
            transformation[count] = new ColorFilterTransformation(config.getFilteColor());
            count++;
        }

        if (config.isNeedSwirl()) {
            transformation[count] = new SwirlFilterTransformation(0.5f, 1.0f, new PointF(0.5f, 0.5f)); //漩涡
            count++;
        }

        if (config.isNeedToon()) {
            transformation[count] = new ToonFilterTransformation(); //油画
            count++;
        }

        if (config.isNeedSepia()) {
            transformation[count] = new SepiaFilterTransformation(); //墨画
            count++;
        }

        if (config.isNeedContrast()) {
            transformation[count] = new ContrastFilterTransformation(config.getContrastLevel()); //锐化
            count++;
        }

        if (config.isNeedInvert()) {
            transformation[count] = new InvertFilterTransformation(); //胶片
            count++;
        }

        if (config.isNeedPixelation()) {
            transformation[count] = new PixelationFilterTransformation(config.getPixelationLevel()); //马赛克
            count++;
        }

        if (config.isNeedSketch()) {
            transformation[count] = new SketchFilterTransformation(); //素描
            count++;
        }

        if (config.isNeedVignette()) {
            transformation[count] = new VignetteFilterTransformation(new PointF(0.5f, 0.5f),
                    new float[]{0.0f, 0.0f, 0.0f}, 0f, 0.75f);//晕映
            count++;
        }

        switch (config.getShapeMode()) {
            case ShapeMode.RECT:

                break;
            case ShapeMode.RECT_ROUND:
                transformation[count] = new RoundedCornersTransformation
                        (config.getRectRoundRadius(), 0, RoundedCornersTransformation.CornerType.ALL);
                count++;
                break;
            case ShapeMode.OVAL://@deprecated Use {@link RequestOptions#circleCrop()}.
//                transformation[count] = new CropCircleTransformation();
//                count++;
                options = options.circleCrop();
                break;

            case ShapeMode.SQUARE:
                transformation[count] = new CropSquareTransformation();
                count++;
                break;
        }

        if (transformation.length != 0) {
            options.transforms(transformation);
        }
    }
    ...
 }

看一下效果哦: 这里写图片描述

到这里我们的封装就结束了,就可以愉快的使用了,欢迎大家提出意见与建议。

Glide 二次封装库源码

后面会更新到 jcenter,并会出一篇具体如何使用本库的文章,还请大家持续关注哦。

Apps
About Me
GitHub: Trinea
Facebook: Dev Tools