在 Android 开发中,Glide 的强大不仅在于其高效的加载和缓存能力,更在于其无与伦比的可扩展性,尤其是在图像处理层面。当内置的 fitCenter() 和 circleCrop() 无法满足你的设计需求时,自定义 Transformation 便是你的终极武器。本文将深入探讨如何创建自定义变换,处理不同资源类型,并组合它们以实现复杂的效果。


1. 自定义 Transformation 的核心原理

在 Glide 中,一个 Transformation 负责在图片被显示到 ImageView 之前对其进行修改。它接收一个 Resource<T> 对象(通常是 Bitmap 或 GifDrawable),并返回一个包含修改后数据的新的 Resource<T> 对象。

关键生命周期:

  1. transform: 核心方法,在此执行实际的图像变换逻辑。

  2. updateDiskCacheKey极其重要的方法,用于生成唯一的缓存键。Glide 使用此键来缓存变换后的结果。如果两个变换的逻辑相同,它们的 updateDiskCacheKey 输出也必须相同,否则将导致错误的缓存命中或未命中。

  3. 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 会将其所有子变换的缓存键组合起来,生成一个全新的、唯一的缓存键。这意味着 圆角+模糊 和 模糊+圆角 会被认为是两种完全不同的变换,并分别缓存。这符合预期,因为变换顺序可能导致不同的最终结果。


总结与最佳实践
  1. 性能第一:变换是 CPU 密集型操作,务必使用 BitmapPool 来复用 Bitmap 对象,避免内存抖动。

  2. 缓存是关键:永远正确重写 updateDiskCacheKeyequals, 和 hashCode 方法。这是 Glide 缓存机制正确工作的基石。

  3. 明确需求:问自己是否真的需要对 GIF 每一帧进行变换。通常处理第一帧或使用静态封面是更好的选择。

  4. 善用组合:使用 MultiTransformation 将简单的原子变换组合成复杂的效果,让代码更清晰、更可复用。

  5. 考虑替代方案:对于一些非常复杂的效果(如高级模糊),可以考虑在服务器端处理图片,或者使用专门的 Native 库(如 OpenCV)来处理,再将结果传递给 Glide。

通过掌握自定义 Transformation,你几乎可以应对任何 UI 设计对图片效果的苛刻要求,将 Glide 的图片处理能力提升到全新的高度。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/94166.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/94166.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/94166.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据挖掘 4.8 评估泛化能力

4.8 Estimating Generalization 4.8 评估泛化能力 评估模型的泛化能力如何合理评估模型的泛化能力指导原则 (Guidelines)存在的问题 (Issues)K-fold 交叉验证&#xff08;Cross-Validation)留一交叉验证&#xff08;Leave One Out CV&#xff09;(LOOCV)Stratification 分层训练…

46.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--扩展功能--集成网关--网关集成日志

本篇文章&#xff0c;我们一起在网关中集成日志功能&#xff0c;我们要在网关中记录下游微服务出现的异常信息、请求信息以及响应信息。在微服务架构中&#xff0c;网关作为系统的入口&#xff0c;承担着非常重要的职责。通过在网关层面集成日志功能&#xff0c;我们可以更好地…

使用 FastAPI 的 WebSockets 和 Elasticsearch 来构建实时应用

作者&#xff1a;来自 Elastic Jeffrey Rengifo 学习如何使用 FastAPI WebSockets 和 Elasticsearch 构建实时应用程序。 更多阅读&#xff1a;使用 FastAPI 构建 Elasticsearch API 想要获得 Elastic 认证吗&#xff1f;看看下一次 Elasticsearch Engineer 培训什么时候开始&…

华为云ModelArts+Dify AI:双剑合璧使能AI应用敏捷开发

引言:AI应用开发的敏捷化转型需求 随着大语言模型(LLM)技术的迅猛发展,企业与开发者对AI应用开发的敏捷化转型需求日益凸显,亟需将大模型能力快速转化为实际业务价值。传统AI开发模式中,复杂的模型工程化、流程编排和部署维护工作往往需要专业技术团队支撑,典型痛点包括…

网络实践——Socket编程UDP

文章目录Socket编程UDPUDP接口的使用铺垫socketrecvform && sendtobind字节序转化使用(Tips)实践部分version_1echo_serverversion_2dict_serverversion_3chat_serverSocket编程UDP 在了解了相关的网络基础知识后&#xff0c;我们不会像学系统知识一样&#xff0c;先学…

GD32 波形发生器,三角波,正弦波,方波。AD9833+MCP410生成和MCU自身的DAC生成。波形,频率,振幅可以通过按键和OLED调整输出。

DIY一个简易的信号发生器驱动板&#xff0c;主要是三角波和正弦波&#xff0c;方波。主板有两个通道能输出波形&#xff0c;CH0由AD9833MCP410AD8051放大电路组成&#xff0c;理论可以生成0.1-12.5MHZ的频率信号&#xff0c;单电源振幅范围是1-9V。CH1由MCU外设DAC生成的信号&a…

VS2022的MFC中关联使用控制台并用printf输出调试信息

前言 MFC一般在调试的时候&#xff0c;可以在IDE中方便的看到调试的信息。但是&#xff0c;有时候运行的时候也要看调试的信息怎么办&#xff1f;最好如同在Console&#xff08;控制台&#xff09;程序中输出一般的方便&#xff0c;可以么&#xff1f;可以的。 一、设置 1.1、加…

ZKmall模块商城的推荐数据体系:从多维度采集到高效存储的实践

在电商领域&#xff0c;个性化推荐已成为提升用户体验与转化效率的核心手段。ZKmall 模块商城基于用户行为、商品属性与交易数据&#xff0c;构建了一套完整的推荐算法体系&#xff0c;而数据采集的全面性与存储的高效性是该体系的基础。本文将聚焦推荐算法的 “数据输入端”&a…

Qt + windows+exe+msvc打包教程

目录 1. Qt + windows+exe+msvc打包教程1 1.1. Enigma Virtual Box下载⏬1 1.2. Enigma Virtual Box安装2 1.3. Qt 打包成独立exe教程6 1.3.1. Qt项目创建6 1.3.2. Qt项目编译13 1.3.3. Qt 项目打包 windeployqt命令14 1.3.4. Qt 项目打包 Enigma Virtual Box工具18 Q…

大语言模型应用开发——利用OpenAI函数与LangChain结合从文本构建知识图谱搭建RAG应用全流程

概述 从文本等非结构化数据中提取结构化信息并非新鲜事物&#xff0c;但大语言模型&#xff08;LLMs&#xff09;为该领域带来了重大变革。以往需要机器学习专家团队策划数据集并训练自定义模型&#xff0c;如今只需访问LLM即可实现&#xff0c;显著降低了技术门槛&#xff0c…

Vue3+Spring Boot技术栈,前端提交混合表单数据(普通字段+文件字段),上传文件,后端插入数据,将文件保存到数据库

一、技术栈1、前端 Vue3 Element Plus TypeSprict2、后端 Spring Boot 3.2.12 Mybatis Plus3、模型特点3.1、表格展示列表数据3.2、行点击&#xff0c;弹出对话框3.3、前端使用 FormData 提交混合表单数据&#xff0c;包含普通字段和文件字段3.4、文件对应数据库结构类型为 …

【Qt开发】Qt的背景介绍(四)

目录 1 -> Qt Hello World 程序 1.1 -> 使用“按钮”实现 1.1.1 -> 纯代码方式实现 1.1.2 -> 可视化操作实现 1.2 -> 使用“标签”实现 1.2.1 -> 纯代码方式实现 1.2.2 -> 可视化操作实现 2 -> 项目文件解析 2.1 -> .pro文件解析 2.2 -&g…

Linux驱动开发笔记(六)——pinctrl GPIO

开发板&#xff1a;imx6ull mini 虚拟机&#xff1a;VMware17 ubuntu&#xff1a;ubuntu20.04 视频&#xff1a;第8.1讲 pinctrl和gpio子系统试验-pincrl子系统详解_哔哩哔哩_bilibili 文档&#xff1a;《【正点原子】I.MX6U嵌入式Linux驱动开发指南.pdf》四十五章 这一章…

SpringBoot 快速上手:从环境搭建到 HelloWorld 实战

在 Java 开发领域&#xff0c;Spring 框架占据着举足轻重的地位&#xff0c;但它复杂的配置曾让不少开发者望而却步。SpringBoot 的出现&#xff0c;如同为 Spring 框架装上了 “加速器”&#xff0c;以 “约定大于配置” 的理念简化了开发流程。本文将从环境准备、Maven 配置入…

图、最小生成树与最短路径

目录 并查集 并查集实现 图 概念 图的存储结构 邻接矩阵 邻接表 无向图 有向图 图的遍历 广度优先遍历 深度优先遍历 最小生成树 Kruskal算法&#xff08;克鲁斯卡尔算法&#xff09; Prim算法&#xff08;普利姆算法&#xff09; 最短路径 单源最短路径--Dij…

互联网电商新生态:开源AI智能名片、链动2+1模式与S2B2C商城小程序的融合赋能

摘要&#xff1a;本文聚焦互联网电商领域&#xff0c;探讨在当下直播电商蓬勃发展的背景下&#xff0c;开源AI智能名片、链动21模式以及S2B2C商城小程序如何相互融合&#xff0c;为创业者、企业和淘宝主播IP等电商参与者带来新的发展机遇。通过分析各要素的特点与优势&#xff…

企业车辆|基于SprinBoot+vue的企业车辆管理系统(源码+数据库+文档)

企业车辆管理系统 基于SprinBootvue的企业车辆管理系统 一、前言 二、系统设计 三、系统功能设计 系统功能实现 后台模块实现 管理员模块实现 驾驶员模块实现 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博…

自学嵌入式第二十五天:数据结构-队列、树

一、队列队列是只允许一段进行插入&#xff0c;另一端进行删除操作的线性表&#xff1b;允许插入的一端叫队尾&#xff0c;允许删除的一端叫对头&#xff1b;先进先出&#xff1b;用于解决速度不匹配&#xff08;例如一快一慢&#xff09;&#xff0c;做缓冲用&#xff1b;二、…

MySQL索引原理与优化全解析

1、MySQL索引是什么&#xff1f; 在关系数据库中&#xff0c;索引是一种单独的、物理的对数据库表中一列或多列的值进行排序的一种存储结构&#xff0c;它是某个表中一列或若干列值的集合和相应的指向表中物理标志这些值的数据页的逻辑指针清单。索引的作用相当于图书的目录&a…

模型对话状态管理方法详解

模型对话状态管理方法详解 目录 简介手动管理对话状态构建对话历史追加响应内容 API 支持的自动化对话状态管理使用 previous_response_id 链接话轮 Token 及上下文窗口管理上下文窗口定义与限制Token 计数与工具 安全与合规注意事项结语1. 简介 在多轮对话场景中&#xff0c;合…