我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Android 玩转Glide4---Transformation篇

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Android 玩转Glide4---Transformation篇

前言

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

概述

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

Glide自带的转换效果
 
Glide.with(this).load(ConstUrl.ImgUrl).into(ivScaleType)

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

为什么会这样呢?

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

 public ViewTarget 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),
         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... transformations) {
    return transform(new MultiTransformation(transformations),  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最全解析


作者:DeMonnnnnn


免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Android 玩转Glide4---Transformation篇

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Android 玩转Glide4---Transformation篇

前言 系列文章专栏: 玩转Glide4 基础使用篇:Android 玩转Glide4—基础使用篇 进阶使用篇:Android 玩转Glide4—进阶使用篇 Transformation篇:Android 玩转Glide4—Transform
2022-06-06

Android 玩转Glide4---进阶使用篇

前言 系列文章专栏: 玩转Glide4 基础使用篇:Android 玩转Glide4—基础使用篇 进阶使用篇:Android 玩转Glide4—进阶使用篇 Transformation篇:Android 玩转Glide4—Transform
2022-06-06

玩转Android之Drawable的使用

Drawable天天用,可你是否对Drawable家族有一个完整的认知?今天我们就来系统的学习一下Drawable的使用。1.概述 用过Drawable的筒子都知道Drawable有很多种,有的时候Drawable是一张图片,有的时候Dra
2022-06-06

一篇文章带你玩转TiDB灾难恢复

高可用是 TiDB 的另一大特点,TiDB/TiKV/PD 这三个组件都能容忍部分实例失效,不影响整个集群的可用性。下面分别说明这三个组件的可用性、单个实例失效后的后果以及如何恢复。 TiDB TiDB 是无状态的,推荐至少部署两个实例,前端通过负载均衡组件对
一篇文章带你玩转TiDB灾难恢复
2017-09-22

如何玩转Android矢量图VectorDrawable

从5.0(API等级21)开始,android开始支持矢量图了。关于什么是矢量图以及矢量图有什么优缺点不在本文的涉及范围之内,具体可以参考矢量图百科。不过这里要提一下它的优点: 保存最少的信息,文件大小比位图要小,并且文件大小与物体的大小无
2022-06-06

【Adb shell】---玩转 Android系统 查询 应用包名 命令

1:列出所有应用的包名 pm list packagesrk3288:/ $ pm list packages package:com.android.cts.priv.ctsshim package:com.android.provide
2022-06-06

Android蓝牙开发系列文章-玩转BLE开发(一)

我们在《Android蓝牙开发系列文章-策划篇》中计划讲解一下蓝牙BLE,现在开始第一篇:Android蓝牙开发系列文章-玩转BLE开发(一)。计划要写的BLE文章至少分四篇,其他三篇分别讲解:BLE Server端编码(用手机模拟外围设备
2022-06-06

Android拼图游戏 玩转从基础到应用手势变化

相信大家在小的时候都玩过拼图游戏,现如今,手机普及,能在手机上玩的游戏越来越多,于是乎,重温小时候,编写这个简易拼图游戏,而且也能进一步加深Android的一些基础知识。 老规矩,先是效果图: 这里我把为了演示效果,把图片打乱的很少,在代
2022-06-06

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录