在 Android 开发中,Glide 的强大不仅在于其高效的加载和缓存能力,更在于其无与伦比的可扩展性,尤其是在图像处理层面。当内置的 fitCenter()
和 circleCrop()
无法满足你的设计需求时,自定义 Transformation
便是你的终极武器。本文将深入探讨如何创建自定义变换,处理不同资源类型,并组合它们以实现复杂的效果。
1. 自定义 Transformation 的核心原理
在 Glide 中,一个 Transformation
负责在图片被显示到 ImageView
之前对其进行修改。它接收一个 Resource<T>
对象(通常是 Bitmap
或 GifDrawable
),并返回一个包含修改后数据的新的 Resource<T>
对象。
关键生命周期:
transform
: 核心方法,在此执行实际的图像变换逻辑。updateDiskCacheKey
: 极其重要的方法,用于生成唯一的缓存键。Glide 使用此键来缓存变换后的结果。如果两个变换的逻辑相同,它们的updateDiskCacheKey
输出也必须相同,否则将导致错误的缓存命中或未命中。equals
/hashCode
: 必须正确重写,用于内存缓存和变换对象的复用判断。
2. 实现自定义 BitmapTransformation
对于静态图片,最常用的是继承 BitmapTransformation
抽象类。它已经帮你处理了部分样板代码(如资源管理),你只需专注于 Bitmap
的变换逻辑。
下面我们实现三个经典效果:黑白、圆角、毛玻璃。
a) 黑白(灰度)效果
kotlin
import android.graphics.* import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool import com.bumptech.glide.load.resource.bitmap.BitmapTransformation import java.security.MessageDigestclass GrayscaleTransformation : BitmapTransformation() {override fun transform(pool: BitmapPool, source: Bitmap, outWidth: Int, outHeight: Int): Bitmap {// 1. 从BitmapPool中获取一个可重用的Bitmap,避免频繁创建对象,优化性能。val result = pool.get(source.width, source.height, Bitmap.Config.ARGB_8888)?: Bitmap.createBitmap(source.width, source.height, Bitmap.Config.ARGB_8888)// 2. 使用Canvas和ColorMatrix来应用灰度效果val canvas = Canvas(result)val paint = Paint()val colorMatrix = ColorMatrix()colorMatrix.setSaturation(0f) // 将饱和度设置为0即可得到灰度图paint.colorFilter = ColorMatrixColorFilter(colorMatrix)canvas.drawBitmap(source, 0f, 0f, paint)// 3. 如果result是从pool中get的,可以安全返回。如果是新创建的,也需要返回。return result}// 必须重写此方法,为变换生成唯一的缓存标识符。override fun updateDiskCacheKey(messageDigest: MessageDigest) {messageDigest.update("grayscale_transformation".toByteArray())}// 重写equals和hashCode是Glide内存缓存机制正确工作的保证。override fun equals(other: Any?): Boolean {return other is GrayscaleTransformation}override fun hashCode(): Int {return "grayscale_transformation".hashCode()} }
使用方式:
kotlin
Glide.with(context).load(url).transform(GrayscaleTransformation()).into(imageView)
b) 圆角效果(支持任意角)
内置的 RoundedCorners
变换要求所有角半径相同。自定义可以实现更灵活的效果。
kotlin
class CustomRoundedCornersTransformation(private val topLeft: Float,private val topRight: Float,private val bottomRight: Float,private val bottomLeft: Float ) : BitmapTransformation() {override fun transform(pool: BitmapPool, source: Bitmap, outWidth: Int, outHeight: Int): Bitmap {val width = source.widthval height = source.heightval result = pool.get(width, height, Bitmap.Config.ARGB_8888)?: Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)val canvas = Canvas(result)val paint = Paint(Paint.ANTI_ALIAS_FLAG) // 关键:开启抗锯齿// 设置BitmapShader,将原图作为纹理val shader = BitmapShader(source, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)paint.shader = shader// 绘制圆角路径val path = Path()val radii = floatArrayOf(topLeft, topLeft,topRight, topRight,bottomRight, bottomRight,bottomLeft, bottomLeft)path.addRoundRect(RectF(0f, 0f, width.toFloat(), height.toFloat()), radii, Path.Direction.CCW)canvas.drawPath(path, paint)return result}override fun updateDiskCacheKey(messageDigest: MessageDigest) {messageDigest.update("rounded_${topLeft}_${topRight}_${bottomRight}_${bottomLeft}".toByteArray())}override fun equals(other: Any?): Boolean {if (other !is CustomRoundedCornersTransformation) return falsereturn topLeft == other.topLeft &&topRight == other.topRight &&bottomRight == other.bottomRight &&bottomLeft == other.bottomLeft}override fun hashCode(): Int {var result = topLeft.hashCode()result = 31 * result + topRight.hashCode()result = 31 * result + bottomRight.hashCode()result = 31 * result + bottomLeft.hashCode()return result} }
使用方式:
kotlin
// 仅左上和右上有20像素圆角 Glide.with(context).load(url).transform(CustomRoundedCornersTransformation(20f, 20f, 0f, 0f)).into(imageView)
c) 毛玻璃(模糊)效果
kotlin
import androidx.annotation.IntRange import android.renderscript.*class BlurTransformation(private val context: Context,@IntRange(from = 1, to = 25) private val radius: Int = 10 ) : BitmapTransformation() {// 使用RenderScript进行高效模糊(注意:RenderScript API已deprecated,但很多项目仍在用)// 替代方案可使用Coil库的BlurTransformation或自己实现RenderScript的替代品override fun transform(pool: BitmapPool, source: Bitmap, outWidth: Int, outHeight: Int): Bitmap {val width = source.widthval height = source.heightval result = pool.get(width, height, Bitmap.Config.ARGB_8888)?: Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)val rs = RenderScript.create(context)val input = Allocation.createFromBitmap(rs, source)val output = Allocation.createTyped(rs, input.type)val script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs))script.setRadius(radius.coerceAtMost(25).toFloat())script.setInput(input)script.forEach(output)output.copyTo(result)// 及时回收资源input.destroy()output.destroy()script.destroy()rs.destroy()return result}override fun updateDiskCacheKey(messageDigest: MessageDigest) {messageDigest.update("blur_$radius".toByteArray())}override fun equals(other: Any?): Boolean {if (other !is BlurTransformation) return falsereturn radius == other.radius}override fun hashCode(): Int {return "blur_$radius".hashCode()} }
注意:由于 RenderScript 已被弃用,在新项目中可以考虑使用其他高效的模糊算法库。
3. 处理 GIF:Transformation<GifDrawable>
如果你想对 GIF 动画的每一帧都应用变换(例如让一个 GIF 变成黑白动画),你需要实现 Transformation<GifDrawable>
接口。
kotlin
import com.bumptech.glide.load.resource.gif.GifDrawable import com.bumptech.glide.load.Transformationclass GifGrayscaleTransformation : Transformation<GifDrawable> {override fun transform(context: Context,resource: Resource<GifDrawable>,outWidth: Int,outHeight: Int): Resource<GifDrawable> {val gifDrawable = resource.get()// 核心:获取GIF的每一帧Bitmap并应用变换val firstFrame = gifDrawable.firstFrameval transformedFirstFrame = applyGrayscale(firstFrame) // 复用之前的灰度变换逻辑// 创建一个新的GifDrawable(这里简化了,实际需要处理每一帧)// 注意:这是一个复杂操作,需要深入理解GifDrawable的结构。// 更实际的做法可能是用一个包装器,在draw()时应用ColorFilter。val transformedGifDrawable = GifDrawable(gifDrawable.gifDecoder,gifDrawable.bitmapPool,gifDrawable.frameTransformation, // 这里本应传入一个能处理每一帧的FrameTransformationgifDrawable.targetWidth,gifDrawable.targetHeight,gifDrawable.frameLoader)transformedGifDrawable.setFirstFrame(transformedFirstFrame)return SimpleResource(transformedGifDrawable)}private fun applyGrayscale(source: Bitmap): Bitmap {// ... 实现同上的灰度效果 ...}override fun updateDiskCacheKey(messageDigest: MessageDigest) {messageDigest.update("gif_grayscale".toByteArray())}// ... 同样必须重写equals和hashCode ... }
重要提示:完整地变换一个 GIF 的每一帧是一项非常复杂且性能开销巨大的任务,通常不建议在生产环境中这样做。更常见的需求是对 GIF 的第一帧或封面进行变换,这可以通过先加载静态图来实现。
4. 组合变换:MultiTransformation 的强大应用
现实中的设计需求往往是多种效果的叠加,例如“先圆角,再模糊”。Glide 提供了 MultiTransformation
类来优雅地解决这个问题。
MultiTransformation
会按照你传入的顺序依次应用变换。
kotlin
// 组合变换:先裁剪成圆角,再应用毛玻璃效果 val multiTransformation = MultiTransformation(CustomRoundedCornersTransformation(16f, 16f, 16f, 16f), // 第一步:16dp圆角BlurTransformation(context, 15) // 第二步:15px模糊 )Glide.with(context).load(url).transform(multiTransformation).into(imageView)// 链式调用.transform() 是等价的,且更简洁 Glide.with(context).load(url).transform(CustomRoundedCornersTransformation(16f, 16f, 16f, 16f),BlurTransformation(context, 15)).into(imageView)
缓存机制:MultiTransformation
会将其所有子变换的缓存键组合起来,生成一个全新的、唯一的缓存键。这意味着 圆角+模糊
和 模糊+圆角
会被认为是两种完全不同的变换,并分别缓存。这符合预期,因为变换顺序可能导致不同的最终结果。
总结与最佳实践
性能第一:变换是 CPU 密集型操作,务必使用
BitmapPool
来复用Bitmap
对象,避免内存抖动。缓存是关键:永远正确重写
updateDiskCacheKey
,equals
, 和hashCode
方法。这是 Glide 缓存机制正确工作的基石。明确需求:问自己是否真的需要对 GIF 每一帧进行变换。通常处理第一帧或使用静态封面是更好的选择。
善用组合:使用
MultiTransformation
将简单的原子变换组合成复杂的效果,让代码更清晰、更可复用。考虑替代方案:对于一些非常复杂的效果(如高级模糊),可以考虑在服务器端处理图片,或者使用专门的 Native 库(如 OpenCV)来处理,再将结果传递给 Glide。
通过掌握自定义 Transformation
,你几乎可以应对任何 UI 设计对图片效果的苛刻要求,将 Glide 的图片处理能力提升到全新的高度。