react-native-gradient-mask 0.1.0-beta.1
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/.eslintrc.js +5 -0
- package/PUBLISHING.md +171 -0
- package/README.md +360 -0
- package/android/build.gradle +43 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/expo/modules/gradientmask/GradientMaskModule.kt +33 -0
- package/android/src/main/java/expo/modules/gradientmask/GradientMaskView.kt +198 -0
- package/build/AnimatedGradientMaskView.d.ts +36 -0
- package/build/AnimatedGradientMaskView.d.ts.map +1 -0
- package/build/AnimatedGradientMaskView.js +37 -0
- package/build/AnimatedGradientMaskView.js.map +1 -0
- package/build/AnimatedGradientMaskView.web.d.ts +17 -0
- package/build/AnimatedGradientMaskView.web.d.ts.map +1 -0
- package/build/AnimatedGradientMaskView.web.js +100 -0
- package/build/AnimatedGradientMaskView.web.js.map +1 -0
- package/build/GradientMask.types.d.ts +38 -0
- package/build/GradientMask.types.d.ts.map +1 -0
- package/build/GradientMask.types.js +2 -0
- package/build/GradientMask.types.js.map +1 -0
- package/build/GradientMaskModule.d.ts +3 -0
- package/build/GradientMaskModule.d.ts.map +1 -0
- package/build/GradientMaskModule.js +4 -0
- package/build/GradientMaskModule.js.map +1 -0
- package/build/GradientMaskModule.web.d.ts +3 -0
- package/build/GradientMaskModule.web.d.ts.map +1 -0
- package/build/GradientMaskModule.web.js +3 -0
- package/build/GradientMaskModule.web.js.map +1 -0
- package/build/GradientMaskView.d.ts +4 -0
- package/build/GradientMaskView.d.ts.map +1 -0
- package/build/GradientMaskView.js +7 -0
- package/build/GradientMaskView.js.map +1 -0
- package/build/GradientMaskView.web.d.ts +8 -0
- package/build/GradientMaskView.web.d.ts.map +1 -0
- package/build/GradientMaskView.web.js +99 -0
- package/build/GradientMaskView.web.js.map +1 -0
- package/build/index.d.ts +6 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +7 -0
- package/build/index.js.map +1 -0
- package/expo-module.config.json +9 -0
- package/ios/GradientMask.podspec +29 -0
- package/ios/GradientMaskModule.swift +29 -0
- package/ios/GradientMaskView.swift +133 -0
- package/package.json +83 -0
- package/rn-gradient-mask-design.md +657 -0
- package/src/AnimatedGradientMaskView.tsx +60 -0
- package/src/AnimatedGradientMaskView.web.tsx +149 -0
- package/src/GradientMask.types.ts +43 -0
- package/src/GradientMaskModule.ts +4 -0
- package/src/GradientMaskModule.web.ts +2 -0
- package/src/GradientMaskView.tsx +11 -0
- package/src/GradientMaskView.web.tsx +129 -0
- package/src/index.ts +7 -0
- package/tsconfig.json +9 -0
package/.eslintrc.js
ADDED
package/PUBLISHING.md
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# 發佈流程指南
|
|
2
|
+
|
|
3
|
+
本文件說明如何進行 NPM 套件的測試發佈與正式發佈。
|
|
4
|
+
|
|
5
|
+
## 分支策略
|
|
6
|
+
|
|
7
|
+
| 分支 | 用途 | NPM 發佈 |
|
|
8
|
+
|------|------|----------|
|
|
9
|
+
| `main` | 正式穩定版本 | ✅ 正式發佈 |
|
|
10
|
+
| `feat/*` | 功能開發 | ❌ 不發佈 |
|
|
11
|
+
| `beta` | 測試版本 | ✅ Beta 發佈 |
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 測試發佈流程(非正式)
|
|
16
|
+
|
|
17
|
+
### 方法一:使用 npm pack 本地測試
|
|
18
|
+
|
|
19
|
+
這是最安全的方式,不會發佈到 npm registry。
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# 1. 確保程式碼已建置
|
|
23
|
+
npm run build
|
|
24
|
+
|
|
25
|
+
# 2. 產生 .tgz 壓縮包(模擬發佈)
|
|
26
|
+
npm pack
|
|
27
|
+
|
|
28
|
+
# 3. 檢視產生的檔案內容
|
|
29
|
+
tar -tzf react-native-gradient-mask-0.1.0.tgz
|
|
30
|
+
|
|
31
|
+
# 4. 在其他專案中測試安裝
|
|
32
|
+
cd /path/to/your/test-project
|
|
33
|
+
npm install /path/to/react-native-gradient-mask-0.1.0.tgz
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### 方法二:使用 npm link 本地連結
|
|
37
|
+
|
|
38
|
+
適合開發階段快速測試。
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# 在套件目錄
|
|
42
|
+
npm run build
|
|
43
|
+
npm link
|
|
44
|
+
|
|
45
|
+
# 在測試專案目錄
|
|
46
|
+
npm link react-native-gradient-mask
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 方法三:發佈 Beta 版本到 npm
|
|
50
|
+
|
|
51
|
+
當需要讓其他人測試時使用。
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# 1. 建置
|
|
55
|
+
npm run build
|
|
56
|
+
|
|
57
|
+
# 2. 更新版本號為 beta
|
|
58
|
+
npm version prerelease --preid=beta
|
|
59
|
+
# 例如:0.1.0 → 0.1.1-beta.0
|
|
60
|
+
|
|
61
|
+
# 3. 發佈到 npm(使用 beta tag)
|
|
62
|
+
npm publish --tag beta
|
|
63
|
+
|
|
64
|
+
# 4. 其他人安裝 beta 版
|
|
65
|
+
npm install react-native-gradient-mask@beta
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 方法四:使用 GitHub 作為 npm 來源
|
|
69
|
+
|
|
70
|
+
直接從 GitHub 分支安裝,適合內部測試。
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
# 從特定分支安裝
|
|
74
|
+
npm install CS6/react-native-gradient-mask#feat/add-mask
|
|
75
|
+
|
|
76
|
+
# 或使用完整 URL
|
|
77
|
+
npm install git+https://github.com/CS6/react-native-gradient-mask.git#feat/add-mask
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## 正式發佈流程
|
|
83
|
+
|
|
84
|
+
### 準備工作
|
|
85
|
+
|
|
86
|
+
1. **確保在 main 分支**
|
|
87
|
+
```bash
|
|
88
|
+
git checkout main
|
|
89
|
+
git pull origin main
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
2. **合併功能分支**
|
|
93
|
+
```bash
|
|
94
|
+
git merge feat/add-mask
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
3. **確認程式碼品質**
|
|
98
|
+
```bash
|
|
99
|
+
npm run lint
|
|
100
|
+
npm run test
|
|
101
|
+
npm run build
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
4. **確認 .npmignore 排除不需要的檔案**
|
|
105
|
+
```bash
|
|
106
|
+
npm pack --dry-run
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### 發佈步驟
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
# 1. 更新版本號
|
|
113
|
+
npm version patch # 0.1.0 → 0.1.1(修復)
|
|
114
|
+
npm version minor # 0.1.0 → 0.2.0(新功能)
|
|
115
|
+
npm version major # 0.1.0 → 1.0.0(重大變更)
|
|
116
|
+
|
|
117
|
+
# 2. 推送到 GitHub(包含 tag)
|
|
118
|
+
git push origin main --tags
|
|
119
|
+
|
|
120
|
+
# 3. 發佈到 npm
|
|
121
|
+
npm publish
|
|
122
|
+
|
|
123
|
+
# 4. 在 GitHub 建立 Release
|
|
124
|
+
gh release create v0.1.1 --generate-notes
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## 發佈前檢查清單
|
|
130
|
+
|
|
131
|
+
- [ ] 程式碼已建置 (`npm run build`)
|
|
132
|
+
- [ ] 測試通過 (`npm run test`)
|
|
133
|
+
- [ ] Lint 檢查通過 (`npm run lint`)
|
|
134
|
+
- [ ] README.md 已更新
|
|
135
|
+
- [ ] package.json 版本號正確
|
|
136
|
+
- [ ] CHANGELOG.md 已更新(如有)
|
|
137
|
+
- [ ] 所有相依套件版本正確
|
|
138
|
+
- [ ] .npmignore 排除不必要檔案
|
|
139
|
+
- [ ] 在測試專案中驗證安裝
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## 常用指令速查
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
# 檢視將要發佈的檔案
|
|
147
|
+
npm pack --dry-run
|
|
148
|
+
|
|
149
|
+
# 檢視目前版本
|
|
150
|
+
npm version
|
|
151
|
+
|
|
152
|
+
# 檢視 npm 上的版本
|
|
153
|
+
npm view react-native-gradient-mask versions
|
|
154
|
+
|
|
155
|
+
# 撤銷發佈(24小時內)
|
|
156
|
+
npm unpublish react-native-gradient-mask@0.1.0
|
|
157
|
+
|
|
158
|
+
# 廢棄版本(不撤銷,但標記為不建議使用)
|
|
159
|
+
npm deprecate react-native-gradient-mask@0.1.0 "此版本有問題,請升級"
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## 版本號規則(Semantic Versioning)
|
|
165
|
+
|
|
166
|
+
| 變更類型 | 版本號變更 | 範例 |
|
|
167
|
+
|----------|-----------|------|
|
|
168
|
+
| 修復 Bug | PATCH | 0.1.0 → 0.1.1 |
|
|
169
|
+
| 新增功能(向下相容) | MINOR | 0.1.0 → 0.2.0 |
|
|
170
|
+
| 重大變更(不向下相容) | MAJOR | 0.1.0 → 1.0.0 |
|
|
171
|
+
| 測試版 | PRERELEASE | 0.1.0 → 0.1.1-beta.0 |
|
package/README.md
ADDED
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
# react-native-gradient-mask
|
|
2
|
+
|
|
3
|
+
React Native 原生漸層遮罩元件,支援 Reanimated 動畫。適用於建立淡入淡出效果、列表遮罩等視覺效果。
|
|
4
|
+
|
|
5
|
+
## 特色
|
|
6
|
+
|
|
7
|
+
- **跨平台支援**:iOS、Android、Web 三平台完整支援
|
|
8
|
+
- **原生效能**:iOS 使用 `CAGradientLayer`,Android 使用 `Bitmap` + `PorterDuff`,Web 使用 CSS `mask-image`
|
|
9
|
+
- **支援 Reanimated**:透過 `AnimatedGradientMaskView` 實現流暢的遮罩動畫
|
|
10
|
+
- **彈性設定**:自訂顏色、位置、方向與遮罩強度
|
|
11
|
+
- **TypeScript 支援**:完整的型別定義
|
|
12
|
+
|
|
13
|
+
## 安裝
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install react-native-gradient-mask
|
|
17
|
+
# 或
|
|
18
|
+
yarn add react-native-gradient-mask
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### 前置需求
|
|
22
|
+
|
|
23
|
+
- Expo SDK 50+
|
|
24
|
+
- React Native 0.73+
|
|
25
|
+
- react-native-reanimated >= 3.0.0(如需使用 AnimatedGradientMaskView)
|
|
26
|
+
|
|
27
|
+
### iOS 設定
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
cd ios && pod install
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Android 設定
|
|
34
|
+
|
|
35
|
+
無需額外設定,自動連結。
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## API 參考
|
|
40
|
+
|
|
41
|
+
### 匯出項目
|
|
42
|
+
|
|
43
|
+
| 名稱 | 類型 | 說明 |
|
|
44
|
+
|------|------|------|
|
|
45
|
+
| `GradientMaskView` | Component | 基礎漸層遮罩元件 |
|
|
46
|
+
| `AnimatedGradientMaskView` | Component | 支援 Reanimated 動畫的漸層遮罩元件 |
|
|
47
|
+
| `GradientMaskViewProps` | Type | GradientMaskView 的 Props 型別定義 |
|
|
48
|
+
| `AnimatedGradientMaskViewProps` | Type | AnimatedGradientMaskView 的 Props 型別定義 |
|
|
49
|
+
|
|
50
|
+
### GradientMaskView Props
|
|
51
|
+
|
|
52
|
+
| 屬性 | 型別 | 必填 | 預設值 | 說明 |
|
|
53
|
+
|------|------|:----:|--------|------|
|
|
54
|
+
| `colors` | `(number \| null)[]` | ✅ | - | 漸層顏色陣列,需使用 `processColor()` 處理 |
|
|
55
|
+
| `locations` | `number[]` | ✅ | - | 每個顏色的位置 (0-1) |
|
|
56
|
+
| `direction` | `'top' \| 'bottom' \| 'left' \| 'right'` | | `'top'` | 漸層方向 |
|
|
57
|
+
| `maskOpacity` | `number` | | `1` | 遮罩效果強度 (0-1) |
|
|
58
|
+
| `style` | `StyleProp<ViewStyle>` | | - | 元件樣式 |
|
|
59
|
+
| `children` | `React.ReactNode` | | - | 子元件 |
|
|
60
|
+
|
|
61
|
+
### AnimatedGradientMaskView Props
|
|
62
|
+
|
|
63
|
+
| 屬性 | 型別 | 必填 | 預設值 | 說明 |
|
|
64
|
+
|------|------|:----:|--------|------|
|
|
65
|
+
| `colors` | `(number \| null)[]` | ✅ | - | 同 GradientMaskView |
|
|
66
|
+
| `locations` | `number[]` | ✅ | - | 同 GradientMaskView |
|
|
67
|
+
| `direction` | `'top' \| 'bottom' \| 'left' \| 'right'` | | `'top'` | 同 GradientMaskView |
|
|
68
|
+
| `maskOpacity` | `SharedValue<number>` | ✅ | - | Reanimated SharedValue,用於動畫控制 |
|
|
69
|
+
| `style` | `StyleProp<ViewStyle>` | | - | 元件樣式 |
|
|
70
|
+
| `children` | `React.ReactNode` | | - | 子元件 |
|
|
71
|
+
|
|
72
|
+
### 方向說明
|
|
73
|
+
|
|
74
|
+
| 值 | 效果 |
|
|
75
|
+
|------|------|
|
|
76
|
+
| `'top'` | 頂部透明,底部不透明(預設) |
|
|
77
|
+
| `'bottom'` | 底部透明,頂部不透明 |
|
|
78
|
+
| `'left'` | 左側透明,右側不透明 |
|
|
79
|
+
| `'right'` | 右側透明,左側不透明 |
|
|
80
|
+
|
|
81
|
+
### 平台支援
|
|
82
|
+
|
|
83
|
+
| 平台 | 支援狀態 | 說明 |
|
|
84
|
+
|------|:--------:|------|
|
|
85
|
+
| iOS | ✅ | 使用 CAGradientLayer 原生實作 |
|
|
86
|
+
| Android | ✅ | 使用 Bitmap + LinearGradient 實作 |
|
|
87
|
+
| Web | ✅ | 使用 CSS mask-image + linear-gradient 實作 |
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## 使用範例
|
|
92
|
+
|
|
93
|
+
### 基本用法
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
import { processColor } from 'react-native';
|
|
97
|
+
import { GradientMaskView } from 'react-native-gradient-mask';
|
|
98
|
+
|
|
99
|
+
// 定義漸層顏色(從透明到不透明)
|
|
100
|
+
const colors = [
|
|
101
|
+
'rgba(0, 0, 0, 0)', // 完全透明
|
|
102
|
+
'rgba(0, 0, 0, 0.5)', // 半透明
|
|
103
|
+
'rgba(0, 0, 0, 1)', // 完全不透明
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
// 使用 processColor 處理顏色
|
|
107
|
+
const processedColors = colors.map(c => processColor(c));
|
|
108
|
+
|
|
109
|
+
// 定義每個顏色的位置
|
|
110
|
+
const locations = [0, 0.3, 1];
|
|
111
|
+
|
|
112
|
+
function MyComponent() {
|
|
113
|
+
return (
|
|
114
|
+
<GradientMaskView
|
|
115
|
+
colors={processedColors}
|
|
116
|
+
locations={locations}
|
|
117
|
+
direction="top"
|
|
118
|
+
maskOpacity={1}
|
|
119
|
+
style={{ flex: 1 }}
|
|
120
|
+
>
|
|
121
|
+
{/* 你的內容 */}
|
|
122
|
+
<ScrollView>
|
|
123
|
+
<Text>這裡的內容會套用漸層遮罩效果</Text>
|
|
124
|
+
</ScrollView>
|
|
125
|
+
</GradientMaskView>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### 搭配 Reanimated 動畫
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
import { processColor } from 'react-native';
|
|
134
|
+
import { AnimatedGradientMaskView } from 'react-native-gradient-mask';
|
|
135
|
+
import {
|
|
136
|
+
useSharedValue,
|
|
137
|
+
withTiming,
|
|
138
|
+
Easing,
|
|
139
|
+
} from 'react-native-reanimated';
|
|
140
|
+
|
|
141
|
+
function AnimatedMaskExample() {
|
|
142
|
+
// 建立動畫 SharedValue
|
|
143
|
+
const maskOpacity = useSharedValue(0);
|
|
144
|
+
|
|
145
|
+
const colors = [
|
|
146
|
+
'rgba(0, 0, 0, 0)',
|
|
147
|
+
'rgba(0, 0, 0, 1)',
|
|
148
|
+
].map(c => processColor(c));
|
|
149
|
+
|
|
150
|
+
const locations = [0, 1];
|
|
151
|
+
|
|
152
|
+
// 顯示遮罩效果
|
|
153
|
+
const showMask = () => {
|
|
154
|
+
maskOpacity.value = withTiming(1, {
|
|
155
|
+
duration: 600,
|
|
156
|
+
easing: Easing.in(Easing.quad),
|
|
157
|
+
});
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// 隱藏遮罩效果
|
|
161
|
+
const hideMask = () => {
|
|
162
|
+
maskOpacity.value = withTiming(0, {
|
|
163
|
+
duration: 400,
|
|
164
|
+
easing: Easing.out(Easing.quad),
|
|
165
|
+
});
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
return (
|
|
169
|
+
<AnimatedGradientMaskView
|
|
170
|
+
colors={colors}
|
|
171
|
+
locations={locations}
|
|
172
|
+
direction="top"
|
|
173
|
+
maskOpacity={maskOpacity}
|
|
174
|
+
style={{ flex: 1 }}
|
|
175
|
+
>
|
|
176
|
+
<YourContent />
|
|
177
|
+
</AnimatedGradientMaskView>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### 實際應用:聊天列表淡出效果
|
|
183
|
+
|
|
184
|
+
```tsx
|
|
185
|
+
import { useMemo, useCallback, useRef } from 'react';
|
|
186
|
+
import { processColor, NativeSyntheticEvent, NativeScrollEvent } from 'react-native';
|
|
187
|
+
import { FlashList } from '@shopify/flash-list';
|
|
188
|
+
import { AnimatedGradientMaskView } from 'react-native-gradient-mask';
|
|
189
|
+
import {
|
|
190
|
+
useSharedValue,
|
|
191
|
+
withTiming,
|
|
192
|
+
cancelAnimation,
|
|
193
|
+
Easing,
|
|
194
|
+
} from 'react-native-reanimated';
|
|
195
|
+
|
|
196
|
+
function ChatListWithMask({ messages }) {
|
|
197
|
+
const maskOpacity = useSharedValue(0);
|
|
198
|
+
const isAtBottomRef = useRef(false);
|
|
199
|
+
|
|
200
|
+
// 定義漸層顏色
|
|
201
|
+
const maskColors = useMemo(() => {
|
|
202
|
+
return [
|
|
203
|
+
'rgba(0,0,0,0)',
|
|
204
|
+
'rgba(0,0,0,0)',
|
|
205
|
+
'rgba(0,0,0,0.2)',
|
|
206
|
+
'rgba(0,0,0,0.6)',
|
|
207
|
+
'rgba(0,0,0,0.9)',
|
|
208
|
+
'rgba(0,0,0,1)',
|
|
209
|
+
].map(c => processColor(c));
|
|
210
|
+
}, []);
|
|
211
|
+
|
|
212
|
+
const maskLocations = useMemo(() => [0, 0.42, 0.45, 0.48, 0.5, 1], []);
|
|
213
|
+
|
|
214
|
+
// 偵測滾動位置,動態控制遮罩
|
|
215
|
+
const handleScroll = useCallback((e: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
216
|
+
const { contentOffset, layoutMeasurement, contentSize } = e.nativeEvent;
|
|
217
|
+
const distanceFromBottom = contentSize.height - contentOffset.y - layoutMeasurement.height;
|
|
218
|
+
|
|
219
|
+
const isAtBottom = distanceFromBottom <= 30;
|
|
220
|
+
|
|
221
|
+
if (isAtBottom !== isAtBottomRef.current) {
|
|
222
|
+
isAtBottomRef.current = isAtBottom;
|
|
223
|
+
cancelAnimation(maskOpacity);
|
|
224
|
+
|
|
225
|
+
if (isAtBottom) {
|
|
226
|
+
// 滾動到底部時顯示遮罩
|
|
227
|
+
maskOpacity.value = withTiming(1, {
|
|
228
|
+
duration: 600,
|
|
229
|
+
easing: Easing.in(Easing.quad),
|
|
230
|
+
});
|
|
231
|
+
} else {
|
|
232
|
+
// 離開底部時隱藏遮罩
|
|
233
|
+
maskOpacity.value = withTiming(0, {
|
|
234
|
+
duration: 400,
|
|
235
|
+
easing: Easing.out(Easing.quad),
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}, []);
|
|
240
|
+
|
|
241
|
+
return (
|
|
242
|
+
<AnimatedGradientMaskView
|
|
243
|
+
colors={maskColors}
|
|
244
|
+
locations={maskLocations}
|
|
245
|
+
direction="top"
|
|
246
|
+
maskOpacity={maskOpacity}
|
|
247
|
+
style={{ flex: 1 }}
|
|
248
|
+
>
|
|
249
|
+
<FlashList
|
|
250
|
+
data={messages}
|
|
251
|
+
renderItem={({ item }) => <MessageItem item={item} />}
|
|
252
|
+
onScroll={handleScroll}
|
|
253
|
+
scrollEventThrottle={16}
|
|
254
|
+
/>
|
|
255
|
+
</AnimatedGradientMaskView>
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### 不同方向的遮罩
|
|
261
|
+
|
|
262
|
+
```tsx
|
|
263
|
+
// 頂部淡出(預設)
|
|
264
|
+
<GradientMaskView direction="top" {...props}>
|
|
265
|
+
{children}
|
|
266
|
+
</GradientMaskView>
|
|
267
|
+
|
|
268
|
+
// 底部淡出
|
|
269
|
+
<GradientMaskView direction="bottom" {...props}>
|
|
270
|
+
{children}
|
|
271
|
+
</GradientMaskView>
|
|
272
|
+
|
|
273
|
+
// 左側淡出
|
|
274
|
+
<GradientMaskView direction="left" {...props}>
|
|
275
|
+
{children}
|
|
276
|
+
</GradientMaskView>
|
|
277
|
+
|
|
278
|
+
// 右側淡出
|
|
279
|
+
<GradientMaskView direction="right" {...props}>
|
|
280
|
+
{children}
|
|
281
|
+
</GradientMaskView>
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### 控制遮罩強度
|
|
285
|
+
|
|
286
|
+
```tsx
|
|
287
|
+
// maskOpacity = 0:無遮罩效果,內容完全可見
|
|
288
|
+
<GradientMaskView maskOpacity={0} {...props}>
|
|
289
|
+
{children}
|
|
290
|
+
</GradientMaskView>
|
|
291
|
+
|
|
292
|
+
// maskOpacity = 0.5:半透明遮罩效果
|
|
293
|
+
<GradientMaskView maskOpacity={0.5} {...props}>
|
|
294
|
+
{children}
|
|
295
|
+
</GradientMaskView>
|
|
296
|
+
|
|
297
|
+
// maskOpacity = 1:完整遮罩效果(預設)
|
|
298
|
+
<GradientMaskView maskOpacity={1} {...props}>
|
|
299
|
+
{children}
|
|
300
|
+
</GradientMaskView>
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
## 進階技巧
|
|
306
|
+
|
|
307
|
+
### 顏色處理
|
|
308
|
+
|
|
309
|
+
顏色必須使用 React Native 的 `processColor()` 函式處理:
|
|
310
|
+
|
|
311
|
+
```tsx
|
|
312
|
+
import { processColor } from 'react-native';
|
|
313
|
+
|
|
314
|
+
// ✅ 正確做法
|
|
315
|
+
const colors = [
|
|
316
|
+
processColor('rgba(0, 0, 0, 0)'),
|
|
317
|
+
processColor('rgba(0, 0, 0, 1)'),
|
|
318
|
+
];
|
|
319
|
+
|
|
320
|
+
// ❌ 錯誤做法(不會正常運作)
|
|
321
|
+
const colors = [
|
|
322
|
+
'rgba(0, 0, 0, 0)',
|
|
323
|
+
'rgba(0, 0, 0, 1)',
|
|
324
|
+
];
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### 使用 useMemo 優化效能
|
|
328
|
+
|
|
329
|
+
```tsx
|
|
330
|
+
const maskColors = useMemo(() => {
|
|
331
|
+
return [
|
|
332
|
+
'rgba(0,0,0,0)',
|
|
333
|
+
'rgba(0,0,0,1)',
|
|
334
|
+
].map(c => processColor(c));
|
|
335
|
+
}, []); // 只計算一次
|
|
336
|
+
|
|
337
|
+
const maskLocations = useMemo(() => [0, 1], []);
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### 避免閃爍
|
|
341
|
+
|
|
342
|
+
使用 `AnimatedGradientMaskView` 搭配 `cancelAnimation` 可以避免快速切換時的閃爍問題:
|
|
343
|
+
|
|
344
|
+
```tsx
|
|
345
|
+
import { cancelAnimation } from 'react-native-reanimated';
|
|
346
|
+
|
|
347
|
+
// 在改變動畫前先取消之前的動畫
|
|
348
|
+
cancelAnimation(maskOpacity);
|
|
349
|
+
maskOpacity.value = withTiming(newValue, { duration: 300 });
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
---
|
|
353
|
+
|
|
354
|
+
## 授權
|
|
355
|
+
|
|
356
|
+
MIT License
|
|
357
|
+
|
|
358
|
+
## 作者
|
|
359
|
+
|
|
360
|
+
DaYuan Lin (CS6)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
apply plugin: 'com.android.library'
|
|
2
|
+
|
|
3
|
+
group = 'expo.modules.gradientmask'
|
|
4
|
+
version = '0.1.0'
|
|
5
|
+
|
|
6
|
+
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
|
7
|
+
apply from: expoModulesCorePlugin
|
|
8
|
+
applyKotlinExpoModulesCorePlugin()
|
|
9
|
+
useCoreDependencies()
|
|
10
|
+
useExpoPublishing()
|
|
11
|
+
|
|
12
|
+
// If you want to use the managed Android SDK versions from expo-modules-core, set this to true.
|
|
13
|
+
// The Android SDK versions will be bumped from time to time in SDK releases and may introduce breaking changes in your module code.
|
|
14
|
+
// Most of the time, you may like to manage the Android SDK versions yourself.
|
|
15
|
+
def useManagedAndroidSdkVersions = false
|
|
16
|
+
if (useManagedAndroidSdkVersions) {
|
|
17
|
+
useDefaultAndroidSdkVersions()
|
|
18
|
+
} else {
|
|
19
|
+
buildscript {
|
|
20
|
+
// Simple helper that allows the root project to override versions declared by this library.
|
|
21
|
+
ext.safeExtGet = { prop, fallback ->
|
|
22
|
+
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
project.android {
|
|
26
|
+
compileSdkVersion safeExtGet("compileSdkVersion", 36)
|
|
27
|
+
defaultConfig {
|
|
28
|
+
minSdkVersion safeExtGet("minSdkVersion", 24)
|
|
29
|
+
targetSdkVersion safeExtGet("targetSdkVersion", 36)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
android {
|
|
35
|
+
namespace "expo.modules.gradientmask"
|
|
36
|
+
defaultConfig {
|
|
37
|
+
versionCode 1
|
|
38
|
+
versionName "0.1.0"
|
|
39
|
+
}
|
|
40
|
+
lintOptions {
|
|
41
|
+
abortOnError false
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
package expo.modules.gradientmask
|
|
2
|
+
|
|
3
|
+
import expo.modules.kotlin.modules.Module
|
|
4
|
+
import expo.modules.kotlin.modules.ModuleDefinition
|
|
5
|
+
|
|
6
|
+
class GradientMaskModule : Module() {
|
|
7
|
+
override fun definition() = ModuleDefinition {
|
|
8
|
+
Name("GradientMask")
|
|
9
|
+
|
|
10
|
+
// View definition
|
|
11
|
+
View(GradientMaskView::class) {
|
|
12
|
+
// colors: List of processed colors (from processColor in JS)
|
|
13
|
+
Prop("colors") { view: GradientMaskView, colors: List<Int>? ->
|
|
14
|
+
view.setColors(colors)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// locations: Array of floats (0-1) for gradient stops
|
|
18
|
+
Prop("locations") { view: GradientMaskView, locations: List<Double>? ->
|
|
19
|
+
view.setLocations(locations)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// direction: "top" | "bottom" | "left" | "right"
|
|
23
|
+
Prop("direction") { view: GradientMaskView, direction: String? ->
|
|
24
|
+
view.setDirection(direction ?: "top")
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// maskOpacity: 0 = no mask effect, 1 = full gradient mask
|
|
28
|
+
Prop("maskOpacity") { view: GradientMaskView, opacity: Double? ->
|
|
29
|
+
view.setMaskOpacity(opacity ?: 1.0)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|