前言

系列文章专栏: 玩转Glide4
基础使用篇:Android 玩转Glide4—基础使用篇
进阶使用篇:Android 玩转Glide4—进阶使用篇
Transformation篇:Android 玩转Glide4—Transformation篇

概述

再基础篇和进阶篇中,我们简单介绍了Glide4的用法,和一些进阶的使用。
本篇Transformation转换篇,将给大家介绍Glide4强大的转换功能。

Glide自带的转换效果

 <ImageView            android:id="@+id/ivScaleType"            android:layout_width="wrap_content"            android:layout_height="wrap_content" />
Glide.with(this).load(ConstUrl.ImgUrl).into(ivScaleType)

如上代码,经常使用Glide的会发现一个问题:
对于一个宽高自适应的ImageView,并且不指定scaleType,即使加载的图片分辨率小于手机屏幕,图的宽度还是会铺满屏幕。
如下所示:

为什么会这样呢?

我们看一下into()方法的源码:

 public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {    Util.assertMainThread();    Preconditions.checkNotNull(view);    RequestOptions requestOptions = this.requestOptions;    if (!requestOptions.isTransformationSet()        && requestOptions.isTransformationAllowed()        && view.getScaleType() != null) {      // Clone in this method so that if we use this RequestBuilder to load into a View and then      // into a different target, we don't retain the transformation applied based on the previous      // View's scale type.      switch (view.getScaleType()) {        case CENTER_CROP:          requestOptions = requestOptions.clone().optionalCenterCrop();          break;        case CENTER_INSIDE:          requestOptions = requestOptions.clone().optionalCenterInside();          break;        case FIT_CENTER:        case FIT_START:        case FIT_END:          requestOptions = requestOptions.clone().optionalFitCenter();          break;        case FIT_XY:          requestOptions = requestOptions.clone().optionalCenterInside();          break;        case CENTER:        case MATRIX:        default:          // Do nothing.      }    }    return into(        glideContext.buildImageViewTarget(view, transcodeClass),        /*targetListener=*/ null,        requestOptions);  }

通过源码我们发现,Glide会获取ImageView的scaleType,进行centerCrop(),fitCenter(),centerInside()三种转换(这三个方法我们也可以RequestOptions直接调用)。
而我们知道在没有明确指定的情况下,ImageView默认的scaleType是FIT_CENTER。

深究的Glide源码,会发现最后执行了fitCenter()方法,如果原图的宽高与ImageView的一致,不做任何改变,否则就是在保持纵横比不变的情况下对图片进行缩放。

 public static Bitmap fitCenter(@NonNull BitmapPool pool, @NonNull Bitmap inBitmap, int width,      int height) {    if (inBitmap.getWidth() == width && inBitmap.getHeight() == height) {           return inBitmap;    }    final float widthPercentage = width / (float) inBitmap.getWidth();    final float heightPercentage = height / (float) inBitmap.getHeight();    final float minPercentage = Math.min(widthPercentage, heightPercentage);    // Round here in case we've decoded exactly the image we want, but take the floor below to    // avoid a line of garbage or blank pixels in images.    int targetWidth = Math.round(minPercentage * inBitmap.getWidth());    int targetHeight = Math.round(minPercentage * inBitmap.getHeight());    if (inBitmap.getWidth() == targetWidth && inBitmap.getHeight() == targetHeight) {        return inBitmap;    }    // Take the floor of the target width/height, not round. If the matrix    // passed into drawBitmap rounds differently, we want to slightly    // overdraw, not underdraw, to avoid artifacts from bitmap reuse.    targetWidth = (int) (minPercentage * inBitmap.getWidth());    targetHeight = (int) (minPercentage * inBitmap.getHeight());    Bitmap.Config config = getNonNullConfig(inBitmap);    Bitmap toReuse = pool.get(targetWidth, targetHeight, config);    // We don't add or remove alpha, so keep the alpha setting of the Bitmap we were given.    TransformationUtils.setAlpha(inBitmap, toReuse);    Matrix matrix = new Matrix();    matrix.setScale(minPercentage, minPercentage);    applyMatrix(inBitmap, toReuse, matrix);    return toReuse;  }

centerInside(),centerCrop()的源码就不贴出来,有兴趣的可以自己查看。

方法 说明
fitCenter() 如果原图的宽高与ImageView的一致,不做任何改变;否则就是在保持纵横比不变的情况下对图片进行居中缩放。默认。
centerInside() 如果原图的宽高小于ImageView的宽高,则不做任何改变;否则执行fitCenter()
centerCrop() 如果原图的宽高小于ImageView的宽高,则不做任何改变;否则就缩放图像,以使图像的宽度与给定的宽度匹配,并且的高度大于图像的给定高度,反之亦然,然后裁剪较大的尺寸以与给定的尺寸匹配

那如何加载图片的原始尺寸呢?

在Glide3中我们可以使用dontTransform()方法,表示Glide在加载图片的过程中不进行图片变换。
但是在Glide4中该方法只会停止transform方法传入的转换,不再影响fitCenter(),centerInside(),centerCrop()。
而且dontTransform()会停止所有的变换操作,显然是不够的。

这种情况下我们只需要借助override()方法强制将图片尺寸指定成原始大小就可以了。

      val optionsScaleType = RequestOptions().override(Target.SIZE_ORIGINAL)    Glide.with(this).load(ConstUrl.ImgUrl).apply(optionsScaleType).into(ivScaleType)

圆形图片

圆形图片是app开发中是最常见需求,以往我们会使用自定义View的圆形图片,在Glide4中,一个方法即可实现。

       //圆形转换        val optionsCircle = RequestOptions().circleCrop()        Glide.with(this).load(ConstUrl.ImgUrl).apply(optionsCircle).into(ivCircle)

Glide Transformations

借助Glide Transformations库,我们可以非常轻松的实现各种基本的图片变换,如裁剪变换、颜色变换、模糊变换等等。
如果你要更加高级的变换,比如GPU渲染等,它也能实现。基本能满足我们所有的日常开发需求。

库引用

repositories {  jcenter()}dependencies {  implementation 'jp.wasabeef:glide-transformations:4.x.x'  // If you want to use the GPU Filters  implementation 'jp.co.cyberagent.android:gpuimage:2.x.x'}

基本转换

由于Glide4自带圆形转换方法circleCrop(),所以CropCircleTransformation就被废弃了。

高斯模糊

可传入模糊度,采样率两个参数。

 public BlurTransformation() {    this(MAX_RADIUS, DEFAULT_DOWN_SAMPLING);  }  public BlurTransformation(int radius) {    this(radius, DEFAULT_DOWN_SAMPLING);  }  public BlurTransformation(int radius, int sampling) {    this.radius = radius;    this.sampling = sampling;  }
        //高斯模糊        val optionsBlur = RequestOptions().transform(BlurTransformation(15, 5))        Glide.with(this).load(ConstUrl.ImgUrl).apply(optionsBlur).into(ivBlur)
圆角矩形

可传入角度,外边距,圆角类型三个三个参数。
圆角矩形的CornerType,枚举了所有的可能性。

 public enum CornerType {    ALL,    TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT,    TOP, BOTTOM, LEFT, RIGHT,    OTHER_TOP_LEFT, OTHER_TOP_RIGHT, OTHER_BOTTOM_LEFT, OTHER_BOTTOM_RIGHT,    DIAGONAL_FROM_TOP_LEFT, DIAGONAL_FROM_TOP_RIGHT  }  public RoundedCornersTransformation(int radius, int margin) {    this(radius, margin, CornerType.ALL);  }  public RoundedCornersTransformation(int radius, int margin, CornerType cornerType) {    this.radius = radius;    this.diameter = this.radius * 2;    this.margin = margin;    this.cornerType = cornerType;  }
   //圆角矩形        val optionsRounded = RequestOptions().transform(RoundedCornersTransformation())        Glide.with(this).load(ConstUrl.ImgUrl).apply(optionsRounded).into(ivRounded)
灰度转换

ORZ…让我想起了前端时间清明节的首页灰度。

         //灰度转换        val optionsGray = RequestOptions().transform(GrayscaleTransformation())        Glide.with(this).load(ConstUrl.ImgUrl).apply(optionsGray).into(ivGray)
裁剪变换
 public enum CropType {    TOP,    CENTER,    BOTTOM  }  private int width;  private int height;  private CropType cropType = CropType.CENTER;  public CropTransformation(int width, int height) {    this(width, height, CropType.CENTER);  }  public CropTransformation(int width, int height, CropType cropType) {    this.width = width;    this.height = height;    this.cropType = cropType;  }
        //裁剪转换        val optionsCrop = RequestOptions().transform(CropTransformation(200, 100, CropTransformation.CropType.TOP))        Glide.with(this).load(ConstUrl.ImgOne).apply(optionsCrop).into(ivCrop)        //正方形裁剪        val optionsSquare = RequestOptions().transform(CropSquareTransformation())        Glide.with(this).load(ConstUrl.ImgOne).apply(optionsSquare).into(ivSquare)
图形变换

这个就比较厉害了。

保留覆盖目标像素的源像素,丢弃其余的源像素和目标像素。

就是指定一个资源文件作为目标形状,加载出来的图片文件形状跟资源文件的形状完全一致。
这个转换理论上可以将图片加载为任何形状。

        //图形变换        val optionsMask = RequestOptions().transform(MaskTransformation(R.drawable.mask_starfish))        Glide.with(this).load(ConstUrl.ImgUrl).apply(optionsMask).into(ivMask)        val optionsMask1 = RequestOptions().transform(MaskTransformation(R.drawable.x))        Glide.with(this).load(ConstUrl.ImgUrl).apply(optionsMask1).into(ivMask1)

GPU转换

GPU渲染效果转换,非常炫酷屌炸天。
我按照源码的顺序都实现了一遍,直接看效果图。

       //高亮效果        val optionsBrightness = RequestOptions().transform(BrightnessFilterTransformation(0.3f))        Glide.with(this).load(ConstUrl.ImgOne).apply(optionsBrightness).into(ivBrightness)        //滤镜效果        val optionsContrast = RequestOptions().transform(ContrastFilterTransformation())        Glide.with(this).load(ConstUrl.ImgOne).apply(optionsContrast).into(ivContrast)        //虚幻效果        val optionsInvert = RequestOptions().transform(InvertFilterTransformation())        Glide.with(this).load(ConstUrl.ImgOne).apply(optionsInvert).into(ivInvert)        //马赛克效果        val optionsKuwahara = RequestOptions().transform(KuwaharaFilterTransformation())        Glide.with(this).load(ConstUrl.ImgOne).apply(optionsKuwahara).into(ivKuwahara)        //像素效果        val optionsPixelation = RequestOptions().transform(PixelationFilterTransformation())        Glide.with(this).load(ConstUrl.ImgOne).apply(optionsPixelation).into(ivPixelation)        //漫画效果        val optionsSepia = RequestOptions().transform(SepiaFilterTransformation())        Glide.with(this).load(ConstUrl.ImgOne).apply(optionsSepia).into(ivSepia)        //铅笔画效果        val optionsSketch = RequestOptions().transform(SketchFilterTransformation())        Glide.with(this).load(ConstUrl.ImgOne).apply(optionsSketch).into(ivSketch)        //漩涡效果        val optionsSwirl = RequestOptions().transform(SwirlFilterTransformation())        Glide.with(this).load(ConstUrl.ImgOne).apply(optionsSwirl).into(ivSwirl)        //油画效果        val optionsToon = RequestOptions().transform(ToonFilterTransformation())        Glide.with(this).load(ConstUrl.ImgOne).apply(optionsToon).into(ivToon)        //暗边效果        val optionsVignette = RequestOptions().transform(VignetteFilterTransformation())        Glide.with(this).load(ConstUrl.ImgOne).apply(optionsVignette).into(ivVignette)

自定义转换

当然这些肯定不可能满足所有的需求,比如我要实现一个聊天头饰效果,在所有的用户头像上加一个如下图的头饰效果。

我们想一下有了图形变换MaskTransformation我们基本可以实现所有的形状变换,MaskTransformation是在目标资源文件上绘制。
而我们这个需求是需要在目标资源文件下绘制,即将目标资源文件覆盖在加载图片上方。
分析需求后我们只需要修改MaskTransformation其中一行代码设置为xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OVER),即可实现将一个将图片覆盖在另一个图片上的需求需求。

PorterDuff.Mode有很多种模式,在Android低层的graphics包里面,有兴趣的同学多了解。

因此修改后的代码几乎跟MaskTransformation一致,如下:

class HeaddressTransformation constructor(private val maskId: Int) : BitmapTransformation() {    private val VERSION = 1    private val ID = "com.demon.glide4img.HeaddressTransformation.$VERSION"    private val sMaskingPaint by lazy {        Paint().apply {            xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OVER)        }    }    override fun hashCode(): Int {        return ID.hashCode() + maskId * 10    }    override fun equals(other: Any?): Boolean {        return other is HeaddressTransformation &&                other.maskId == maskId    }    override fun updateDiskCacheKey(messageDigest: MessageDigest) {        messageDigest.update((ID + maskId).toByteArray(Key.CHARSET))    }    override fun transform(context: Context, pool: BitmapPool, toTransform: Bitmap, outWidth: Int, outHeight: Int): Bitmap {        val width = toTransform.width        val height = toTransform.height        val result: Bitmap = pool.get(width, height, Bitmap.Config.ARGB_8888)        Canvas(result).run {            val mask = Utils.getMaskDrawable(context.applicationContext, maskId)            mask.setBounds(0, 0, width, height)            mask.draw(this)            drawBitmap(toTransform, 0f, 0f, sMaskingPaint)        }        return result    }}

由于头饰是圆形的,我们又需要将加载的图片转换为圆形效果。RequestOptions的transforms方法可以同事加载多个Transformation,进行复合变换。

 public RequestOptions transforms(@NonNull Transformation<Bitmap>... transformations) {    return transform(new MultiTransformation<>(transformations), /*isRequired=*/ true);  }

最后使用如下:

    //圆形头饰效果        val optionsHeaddress = RequestOptions().centerCrop().transforms(CropCircleTransformation(), HeaddressTransformation(R.drawable.pic_kehu_hg))        Glide.with(this).load(ConstUrl.ImgUrl).apply(optionsHeaddress).into(ivHead)

代码

GitHub: https://github.com/DeMonDemo/Glide4Img

参考

郭霖的专栏—Glide最全解析

更多相关文章

  1. Android(安卓)编辑框 和 按钮使用
  2. Android(安卓)Fresco实现图片毛玻璃效果
  3. Android之SlidingDrawer抽屉效果
  4. Android水波纹点击效果
  5. android主流UI布局
  6. android Gallery3D效果
  7. Android(安卓)Material Design 实践(一)
  8. Android(安卓)Textview实现文字颜色渐变效果
  9. Android创建和配置布局动画

随机推荐

  1. 2013 android经典实例及赚钱心得
  2. XMPP协议实现原理介绍
  3. Android周学习Step By Step(5)--常用widget
  4. 深入浅出 - Android系统移植与平台开发(六
  5. Android 三大图片缓存原理、特性对比
  6. Android调用系统相机、自定义相机、处理
  7. Mika Mobile 谈论 Android 游戏开发:一个
  8. 一些优秀的 Android 开发专栏推荐
  9. Android SDK 2.3与Eclipse开发环境搭建
  10. 从零开始--系统深入学习android(实践-让我