深入 Glide 圖像變換:自定義效果、GIF處理與組合變換

在 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. 緩存是關鍵:永遠正確重寫?updateDiskCacheKey,?equals, 和?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;合…