react-native-gradient-mask 0.0.1-beta.1 → 0.0.1-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +24 -0
- package/README.md +24 -0
- package/README.zh-TW.md +24 -0
- package/android/src/main/java/expo/modules/gradientmask/GradientMaskView.kt +71 -30
- package/package.json +1 -1
package/README.ja.md
CHANGED
|
@@ -324,6 +324,30 @@ maskOpacity.value = withTiming(newValue, { duration: 300 });
|
|
|
324
324
|
|
|
325
325
|
---
|
|
326
326
|
|
|
327
|
+
## Anini のために開発
|
|
328
|
+
|
|
329
|
+
このライブラリは [**Anini**](https://play.google.com/store/apps/details?id=com.gazai.aichat) のために開発されました — スムーズでネイティブ品質のユーザー体験を提供する AI チャットコンパニオンアプリです。
|
|
330
|
+
|
|
331
|
+
<p align="center">
|
|
332
|
+
<a href="https://play.google.com/store/apps/details?id=com.gazai.aichat">
|
|
333
|
+
<img src="https://img.shields.io/badge/Google_Play-Anini-414141?style=for-the-badge&logo=google-play&logoColor=white" alt="Google Play で Anini をダウンロード" />
|
|
334
|
+
</a>
|
|
335
|
+
</p>
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## スポンサー
|
|
340
|
+
|
|
341
|
+
このプロジェクトは [**GAZAI**](https://gazai.io/EN/services) がスポンサーです — 革新的な AI 駆動アプリケーションを構築しています。
|
|
342
|
+
|
|
343
|
+
<p align="center">
|
|
344
|
+
<a href="https://github.com/sponsors/CS6">
|
|
345
|
+
<img src="https://img.shields.io/badge/スポンサー-GitHub_Sponsors-ea4aaa?style=for-the-badge&logo=github-sponsors&logoColor=white" alt="GitHub でスポンサーになる" />
|
|
346
|
+
</a>
|
|
347
|
+
</p>
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
327
351
|
## ライセンス
|
|
328
352
|
|
|
329
353
|
MIT © [DaYuan Lin (CS6)](https://github.com/CS6)
|
package/README.md
CHANGED
|
@@ -324,6 +324,30 @@ maskOpacity.value = withTiming(newValue, { duration: 300 });
|
|
|
324
324
|
|
|
325
325
|
---
|
|
326
326
|
|
|
327
|
+
## Built for Anini
|
|
328
|
+
|
|
329
|
+
This library was originally developed for [**Anini**](https://play.google.com/store/apps/details?id=com.gazai.aichat) - an AI chat companion app that delivers smooth, native-quality user experiences.
|
|
330
|
+
|
|
331
|
+
<p align="center">
|
|
332
|
+
<a href="https://play.google.com/store/apps/details?id=com.gazai.aichat">
|
|
333
|
+
<img src="https://img.shields.io/badge/Google_Play-Anini-414141?style=for-the-badge&logo=google-play&logoColor=white" alt="Get Anini on Google Play" />
|
|
334
|
+
</a>
|
|
335
|
+
</p>
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## Sponsor
|
|
340
|
+
|
|
341
|
+
This project is sponsored by [**GAZAI**](https://gazai.io/EN/services) - Building innovative AI-powered applications.
|
|
342
|
+
|
|
343
|
+
<p align="center">
|
|
344
|
+
<a href="https://github.com/sponsors/CS6">
|
|
345
|
+
<img src="https://img.shields.io/badge/Sponsor-GitHub_Sponsors-ea4aaa?style=for-the-badge&logo=github-sponsors&logoColor=white" alt="Sponsor on GitHub" />
|
|
346
|
+
</a>
|
|
347
|
+
</p>
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
327
351
|
## License
|
|
328
352
|
|
|
329
353
|
MIT © [DaYuan Lin (CS6)](https://github.com/CS6)
|
package/README.zh-TW.md
CHANGED
|
@@ -324,6 +324,30 @@ maskOpacity.value = withTiming(newValue, { duration: 300 });
|
|
|
324
324
|
|
|
325
325
|
---
|
|
326
326
|
|
|
327
|
+
## 為 Anini 打造
|
|
328
|
+
|
|
329
|
+
這個套件最初是為 [**Anini**](https://play.google.com/store/apps/details?id=com.gazai.aichat) 開發的 — 一款提供流暢、原生品質使用者體驗的 AI 聊天夥伴應用程式。
|
|
330
|
+
|
|
331
|
+
<p align="center">
|
|
332
|
+
<a href="https://play.google.com/store/apps/details?id=com.gazai.aichat">
|
|
333
|
+
<img src="https://img.shields.io/badge/Google_Play-Anini-414141?style=for-the-badge&logo=google-play&logoColor=white" alt="在 Google Play 下載 Anini" />
|
|
334
|
+
</a>
|
|
335
|
+
</p>
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## 贊助
|
|
340
|
+
|
|
341
|
+
本專案由 [**GAZAI**](https://gazai.io/EN/services) 贊助 — 打造創新的 AI 驅動應用程式。
|
|
342
|
+
|
|
343
|
+
<p align="center">
|
|
344
|
+
<a href="https://github.com/sponsors/CS6">
|
|
345
|
+
<img src="https://img.shields.io/badge/贊助-GitHub_Sponsors-ea4aaa?style=for-the-badge&logo=github-sponsors&logoColor=white" alt="在 GitHub 贊助" />
|
|
346
|
+
</a>
|
|
347
|
+
</p>
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
327
351
|
## 授權
|
|
328
352
|
|
|
329
353
|
MIT © [DaYuan Lin (CS6)](https://github.com/CS6)
|
|
@@ -19,6 +19,10 @@ import expo.modules.kotlin.views.ExpoView
|
|
|
19
19
|
* maskOpacity 控制漸層 mask 效果的顯示程度:
|
|
20
20
|
* - maskOpacity = 0 → 無漸層效果,內容完全可見(全部 alpha=255)
|
|
21
21
|
* - maskOpacity = 1 → 完整漸層效果(使用原始 alpha)
|
|
22
|
+
*
|
|
23
|
+
* 效能優化:
|
|
24
|
+
* - 基礎漸層 bitmap (baseMaskBitmap) 只在 colors/locations/direction/size 變化時重建
|
|
25
|
+
* - maskOpacity 變化時只使用 ColorMatrix 調整 alpha,不重建 bitmap
|
|
22
26
|
*/
|
|
23
27
|
class GradientMaskView(context: Context, appContext: AppContext) : ExpoView(context, appContext) {
|
|
24
28
|
|
|
@@ -30,12 +34,19 @@ class GradientMaskView(context: Context, appContext: AppContext) : ExpoView(cont
|
|
|
30
34
|
// maskOpacity: 0 = 無漸層效果, 1 = 完整漸層效果
|
|
31
35
|
private var maskOpacity: Float = 1f
|
|
32
36
|
|
|
33
|
-
//
|
|
34
|
-
private var
|
|
35
|
-
|
|
37
|
+
// 基礎漸層 bitmap(完整漸層效果,maskOpacity=1 時使用的原始漸層)
|
|
38
|
+
private var baseMaskBitmap: Bitmap? = null
|
|
39
|
+
// 是否需要重建基礎 bitmap(只有 colors/locations/direction/size 變化時才需要)
|
|
40
|
+
private var baseBitmapInvalidated = true
|
|
41
|
+
|
|
42
|
+
// 繪製用的 Paint
|
|
36
43
|
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
|
|
37
44
|
private val porterDuffXferMode = PorterDuffXfermode(PorterDuff.Mode.DST_IN)
|
|
38
45
|
|
|
46
|
+
// 用於調整 mask alpha 的 ColorMatrix
|
|
47
|
+
private val colorMatrix = ColorMatrix()
|
|
48
|
+
private val colorMatrixFilter = ColorMatrixColorFilter(colorMatrix)
|
|
49
|
+
|
|
39
50
|
init {
|
|
40
51
|
// 確保背景是透明的
|
|
41
52
|
setBackgroundColor(Color.TRANSPARENT)
|
|
@@ -49,26 +60,30 @@ class GradientMaskView(context: Context, appContext: AppContext) : ExpoView(cont
|
|
|
49
60
|
|
|
50
61
|
fun setColors(colorArray: List<Int>?) {
|
|
51
62
|
colors = colorArray?.toIntArray()
|
|
52
|
-
|
|
63
|
+
baseBitmapInvalidated = true
|
|
53
64
|
invalidate()
|
|
54
65
|
}
|
|
55
66
|
|
|
56
67
|
fun setLocations(locationArray: List<Double>?) {
|
|
57
68
|
locations = locationArray?.map { it.toFloat() }?.toFloatArray()
|
|
58
|
-
|
|
69
|
+
baseBitmapInvalidated = true
|
|
59
70
|
invalidate()
|
|
60
71
|
}
|
|
61
72
|
|
|
62
73
|
fun setDirection(dir: String) {
|
|
63
74
|
direction = dir
|
|
64
|
-
|
|
75
|
+
baseBitmapInvalidated = true
|
|
65
76
|
invalidate()
|
|
66
77
|
}
|
|
67
78
|
|
|
68
79
|
fun setMaskOpacity(opacity: Double) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
+
}
|
|
72
87
|
}
|
|
73
88
|
|
|
74
89
|
// MARK: - Layout
|
|
@@ -76,15 +91,15 @@ class GradientMaskView(context: Context, appContext: AppContext) : ExpoView(cont
|
|
|
76
91
|
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
|
77
92
|
super.onSizeChanged(w, h, oldw, oldh)
|
|
78
93
|
if (w > 0 && h > 0) {
|
|
79
|
-
|
|
80
|
-
|
|
94
|
+
updateBaseMaskBitmap()
|
|
95
|
+
baseBitmapInvalidated = false
|
|
81
96
|
}
|
|
82
97
|
}
|
|
83
98
|
|
|
84
99
|
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
|
|
85
100
|
super.onLayout(changed, l, t, r, b)
|
|
86
101
|
if (changed) {
|
|
87
|
-
|
|
102
|
+
baseBitmapInvalidated = true
|
|
88
103
|
}
|
|
89
104
|
}
|
|
90
105
|
|
|
@@ -97,13 +112,13 @@ class GradientMaskView(context: Context, appContext: AppContext) : ExpoView(cont
|
|
|
97
112
|
return
|
|
98
113
|
}
|
|
99
114
|
|
|
100
|
-
//
|
|
101
|
-
if (
|
|
102
|
-
|
|
103
|
-
|
|
115
|
+
// 如果基礎 bitmap 需要更新,重新創建
|
|
116
|
+
if (baseBitmapInvalidated) {
|
|
117
|
+
updateBaseMaskBitmap()
|
|
118
|
+
baseBitmapInvalidated = false
|
|
104
119
|
}
|
|
105
120
|
|
|
106
|
-
val bitmap =
|
|
121
|
+
val bitmap = baseMaskBitmap
|
|
107
122
|
// 如果沒有 mask bitmap 或 maskOpacity=0,直接繪製子元件(無 mask 效果)
|
|
108
123
|
if (bitmap == null || maskOpacity <= 0f) {
|
|
109
124
|
super.dispatchDraw(canvas)
|
|
@@ -122,19 +137,48 @@ class GradientMaskView(context: Context, appContext: AppContext) : ExpoView(cont
|
|
|
122
137
|
super.dispatchDraw(canvas)
|
|
123
138
|
|
|
124
139
|
// 應用 mask(使用 DST_IN 模式)
|
|
140
|
+
// 使用 ColorMatrix 來調整 alpha,實現 maskOpacity 效果
|
|
141
|
+
// 這樣就不需要每次 maskOpacity 變化都重建 bitmap
|
|
125
142
|
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
|
+
}
|
|
126
164
|
canvas.drawBitmap(bitmap, 0f, 0f, paint)
|
|
127
165
|
paint.xfermode = null
|
|
166
|
+
paint.colorFilter = null
|
|
128
167
|
} finally {
|
|
129
168
|
canvas.restoreToCount(saveCount)
|
|
130
169
|
}
|
|
131
170
|
}
|
|
132
171
|
|
|
133
|
-
|
|
172
|
+
/**
|
|
173
|
+
* 更新基礎漸層 bitmap
|
|
174
|
+
* 這個 bitmap 包含原始漸層效果(maskOpacity = 1 時的效果)
|
|
175
|
+
* maskOpacity 的調整在 dispatchDraw 時透過 ColorMatrix 實現
|
|
176
|
+
*/
|
|
177
|
+
private fun updateBaseMaskBitmap() {
|
|
134
178
|
if (width <= 0 || height <= 0) return
|
|
135
179
|
|
|
136
180
|
// 回收舊的 bitmap
|
|
137
|
-
|
|
181
|
+
baseMaskBitmap?.recycle()
|
|
138
182
|
|
|
139
183
|
// 創建新的 mask bitmap
|
|
140
184
|
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
|
@@ -148,25 +192,22 @@ class GradientMaskView(context: Context, appContext: AppContext) : ExpoView(cont
|
|
|
148
192
|
currentColors.size != currentLocations.size ||
|
|
149
193
|
currentColors.isEmpty()) {
|
|
150
194
|
bitmapCanvas.drawColor(Color.WHITE)
|
|
151
|
-
|
|
195
|
+
baseMaskBitmap = bitmap
|
|
152
196
|
return
|
|
153
197
|
}
|
|
154
198
|
|
|
155
|
-
//
|
|
156
|
-
val
|
|
199
|
+
// 轉換顏色為白色 + 原始 alpha(mask 只需要 alpha 通道)
|
|
200
|
+
val maskColors = IntArray(currentColors.size) { i ->
|
|
157
201
|
val originalColor = currentColors[i]
|
|
158
202
|
val originalAlpha = Color.alpha(originalColor)
|
|
159
|
-
|
|
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)
|
|
163
204
|
}
|
|
164
205
|
|
|
165
206
|
// 建立 gradient shader
|
|
166
207
|
val (startX, startY, endX, endY) = getGradientCoordinates()
|
|
167
208
|
val shader = LinearGradient(
|
|
168
209
|
startX, startY, endX, endY,
|
|
169
|
-
|
|
210
|
+
maskColors,
|
|
170
211
|
currentLocations,
|
|
171
212
|
Shader.TileMode.CLAMP
|
|
172
213
|
)
|
|
@@ -177,13 +218,13 @@ class GradientMaskView(context: Context, appContext: AppContext) : ExpoView(cont
|
|
|
177
218
|
}
|
|
178
219
|
bitmapCanvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), gradientPaint)
|
|
179
220
|
|
|
180
|
-
|
|
221
|
+
baseMaskBitmap = bitmap
|
|
181
222
|
}
|
|
182
223
|
|
|
183
224
|
override fun onDetachedFromWindow() {
|
|
184
225
|
super.onDetachedFromWindow()
|
|
185
|
-
|
|
186
|
-
|
|
226
|
+
baseMaskBitmap?.recycle()
|
|
227
|
+
baseMaskBitmap = null
|
|
187
228
|
}
|
|
188
229
|
|
|
189
230
|
private fun getGradientCoordinates(): List<Float> {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-gradient-mask",
|
|
3
|
-
"version": "0.0.1-beta.
|
|
3
|
+
"version": "0.0.1-beta.2",
|
|
4
4
|
"description": "A native gradient mask component for React Native with Reanimated animation support. Supports iOS, Android, and Web platforms.",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"module": "build/index.js",
|