@@ -19,6 +19,10 @@ import expo.modules.kotlin.views.ExpoView
1919 * maskOpacity 控制漸層 mask 效果的顯示程度:
2020 * - maskOpacity = 0 → 無漸層效果,內容完全可見(全部 alpha=255)
2121 * - maskOpacity = 1 → 完整漸層效果(使用原始 alpha)
22+ *
23+ * 效能優化:
24+ * - 基礎漸層 bitmap (baseMaskBitmap) 只在 colors/locations/direction/size 變化時重建
25+ * - maskOpacity 變化時只使用 ColorMatrix 調整 alpha,不重建 bitmap
2226 */
2327class GradientMaskView (context : Context , appContext : AppContext ) : ExpoView(context, appContext) {
2428
@@ -30,12 +34,19 @@ class GradientMaskView(context: Context, appContext: AppContext) : ExpoView(cont
3034 // maskOpacity: 0 = 無漸層效果, 1 = 完整漸層效果
3135 private var maskOpacity: Float = 1f
3236
33- // Mask bitmap 和相關的 Paint
34- private var maskBitmap: Bitmap ? = null
35- private var maskBitmapInvalidated = true
37+ // 基礎漸層 bitmap(完整漸層效果,maskOpacity=1 時使用的原始漸層)
38+ private var baseMaskBitmap: Bitmap ? = null
39+ // 是否需要重建基礎 bitmap(只有 colors/locations/direction/size 變化時才需要)
40+ private var baseBitmapInvalidated = true
41+
42+ // 繪製用的 Paint
3643 private val paint = Paint (Paint .ANTI_ALIAS_FLAG )
3744 private val porterDuffXferMode = PorterDuffXfermode (PorterDuff .Mode .DST_IN )
3845
46+ // 用於調整 mask alpha 的 ColorMatrix
47+ private val colorMatrix = ColorMatrix ()
48+ private val colorMatrixFilter = ColorMatrixColorFilter (colorMatrix)
49+
3950 init {
4051 // 確保背景是透明的
4152 setBackgroundColor(Color .TRANSPARENT )
@@ -49,42 +60,46 @@ class GradientMaskView(context: Context, appContext: AppContext) : ExpoView(cont
4960
5061 fun setColors (colorArray : List <Int >? ) {
5162 colors = colorArray?.toIntArray()
52- maskBitmapInvalidated = true
63+ baseBitmapInvalidated = true
5364 invalidate()
5465 }
5566
5667 fun setLocations (locationArray : List <Double >? ) {
5768 locations = locationArray?.map { it.toFloat() }?.toFloatArray()
58- maskBitmapInvalidated = true
69+ baseBitmapInvalidated = true
5970 invalidate()
6071 }
6172
6273 fun setDirection (dir : String ) {
6374 direction = dir
64- maskBitmapInvalidated = true
75+ baseBitmapInvalidated = true
6576 invalidate()
6677 }
6778
6879 fun setMaskOpacity (opacity : Double ) {
69- maskOpacity = opacity.toFloat().coerceIn(0f , 1f )
70- maskBitmapInvalidated = true
71- invalidate()
80+ val newOpacity = opacity.toFloat().coerceIn(0f , 1f )
81+ if (newOpacity != maskOpacity) {
82+ maskOpacity = newOpacity
83+ // 只需要 invalidate,不需要重建 bitmap
84+ // dispatchDraw 時會使用 ColorMatrix 來調整 alpha
85+ invalidate()
86+ }
7287 }
7388
7489 // MARK: - Layout
7590
7691 override fun onSizeChanged (w : Int , h : Int , oldw : Int , oldh : Int ) {
7792 super .onSizeChanged(w, h, oldw, oldh)
7893 if (w > 0 && h > 0 ) {
79- updateMaskBitmap ()
80- maskBitmapInvalidated = false
94+ updateBaseMaskBitmap ()
95+ baseBitmapInvalidated = false
8196 }
8297 }
8398
8499 override fun onLayout (changed : Boolean , l : Int , t : Int , r : Int , b : Int ) {
85100 super .onLayout(changed, l, t, r, b)
86101 if (changed) {
87- maskBitmapInvalidated = true
102+ baseBitmapInvalidated = true
88103 }
89104 }
90105
@@ -97,13 +112,13 @@ class GradientMaskView(context: Context, appContext: AppContext) : ExpoView(cont
97112 return
98113 }
99114
100- // 如果 mask bitmap 需要更新,重新創建
101- if (maskBitmapInvalidated ) {
102- updateMaskBitmap ()
103- maskBitmapInvalidated = false
115+ // 如果基礎 bitmap 需要更新,重新創建
116+ if (baseBitmapInvalidated ) {
117+ updateBaseMaskBitmap ()
118+ baseBitmapInvalidated = false
104119 }
105120
106- val bitmap = maskBitmap
121+ val bitmap = baseMaskBitmap
107122 // 如果沒有 mask bitmap 或 maskOpacity=0,直接繪製子元件(無 mask 效果)
108123 if (bitmap == null || maskOpacity <= 0f ) {
109124 super .dispatchDraw(canvas)
@@ -122,19 +137,48 @@ class GradientMaskView(context: Context, appContext: AppContext) : ExpoView(cont
122137 super .dispatchDraw(canvas)
123138
124139 // 應用 mask(使用 DST_IN 模式)
140+ // 使用 ColorMatrix 來調整 alpha,實現 maskOpacity 效果
141+ // 這樣就不需要每次 maskOpacity 變化都重建 bitmap
125142 paint.xfermode = porterDuffXferMode
143+ paint.colorFilter = if (maskOpacity < 1f ) {
144+ // 使用 ColorMatrix 來混合原始 alpha 和完全不透明
145+ // maskOpacity = 0 → 所有像素的 alpha 變為 255(完全可見)
146+ // maskOpacity = 1 → 使用原始 alpha
147+ //
148+ // ColorMatrix 的 alpha 行:[0, 0, 0, scale, translate]
149+ // 結果 alpha = originalAlpha * scale + translate
150+ //
151+ // 我們想要:resultAlpha = 255 + (originalAlpha - 255) * maskOpacity
152+ // = 255 * (1 - maskOpacity) + originalAlpha * maskOpacity
153+ // 所以:scale = maskOpacity, translate = 255 * (1 - maskOpacity)
154+ colorMatrix.set(floatArrayOf(
155+ 1f , 0f , 0f , 0f , 0f , // R
156+ 0f , 1f , 0f , 0f , 0f , // G
157+ 0f , 0f , 1f , 0f , 0f , // B
158+ 0f , 0f , 0f , maskOpacity, 255f * (1f - maskOpacity) // A
159+ ))
160+ ColorMatrixColorFilter (colorMatrix)
161+ } else {
162+ null
163+ }
126164 canvas.drawBitmap(bitmap, 0f , 0f , paint)
127165 paint.xfermode = null
166+ paint.colorFilter = null
128167 } finally {
129168 canvas.restoreToCount(saveCount)
130169 }
131170 }
132171
133- private fun updateMaskBitmap () {
172+ /* *
173+ * 更新基礎漸層 bitmap
174+ * 這個 bitmap 包含原始漸層效果(maskOpacity = 1 時的效果)
175+ * maskOpacity 的調整在 dispatchDraw 時透過 ColorMatrix 實現
176+ */
177+ private fun updateBaseMaskBitmap () {
134178 if (width <= 0 || height <= 0 ) return
135179
136180 // 回收舊的 bitmap
137- maskBitmap ?.recycle()
181+ baseMaskBitmap ?.recycle()
138182
139183 // 創建新的 mask bitmap
140184 val bitmap = Bitmap .createBitmap(width, height, Bitmap .Config .ARGB_8888 )
@@ -148,25 +192,22 @@ class GradientMaskView(context: Context, appContext: AppContext) : ExpoView(cont
148192 currentColors.size != currentLocations.size ||
149193 currentColors.isEmpty()) {
150194 bitmapCanvas.drawColor(Color .WHITE )
151- maskBitmap = bitmap
195+ baseMaskBitmap = bitmap
152196 return
153197 }
154198
155- // 計算 effective colors(根據 maskOpacity 混合 )
156- val effectiveColors = IntArray (currentColors.size) { i ->
199+ // 轉換顏色為白色 + 原始 alpha(mask 只需要 alpha 通道 )
200+ val maskColors = IntArray (currentColors.size) { i ->
157201 val originalColor = currentColors[i]
158202 val originalAlpha = Color .alpha(originalColor)
159- // 當 maskOpacity = 0,alpha = 255(完全不透明,內容完全可見)
160- // 當 maskOpacity = 1,alpha = originalAlpha
161- val blendedAlpha = (255 + (originalAlpha - 255 ) * maskOpacity).toInt()
162- Color .argb(blendedAlpha, 255 , 255 , 255 )
203+ Color .argb(originalAlpha, 255 , 255 , 255 )
163204 }
164205
165206 // 建立 gradient shader
166207 val (startX, startY, endX, endY) = getGradientCoordinates()
167208 val shader = LinearGradient (
168209 startX, startY, endX, endY,
169- effectiveColors ,
210+ maskColors ,
170211 currentLocations,
171212 Shader .TileMode .CLAMP
172213 )
@@ -177,13 +218,13 @@ class GradientMaskView(context: Context, appContext: AppContext) : ExpoView(cont
177218 }
178219 bitmapCanvas.drawRect(0f , 0f , width.toFloat(), height.toFloat(), gradientPaint)
179220
180- maskBitmap = bitmap
221+ baseMaskBitmap = bitmap
181222 }
182223
183224 override fun onDetachedFromWindow () {
184225 super .onDetachedFromWindow()
185- maskBitmap ?.recycle()
186- maskBitmap = null
226+ baseMaskBitmap ?.recycle()
227+ baseMaskBitmap = null
187228 }
188229
189230 private fun getGradientCoordinates (): List <Float > {
0 commit comments