系列文章专栏: 玩转Glide4
基础使用篇:Android 玩转Glide4—基础使用篇
进阶使用篇:Android 玩转Glide4—进阶使用篇
Transformation篇:Android 玩转Glide4—Transformation篇
再基础篇和进阶篇中,我们简单介绍了Glide4的用法,和一些进阶的使用。
本篇Transformation转换篇,将给大家介绍Glide4强大的转换功能。
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),
/*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... 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最全解析