@bm-fe/react-native-ui-components 1.0.1 → 1.1.3
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/android/design/build.gradle +51 -0
- package/android/design/consumer-rules.pro +1 -0
- package/android/design/proguard-rules.pro +1 -0
- package/android/design/src/main/AndroidManifest.xml +2 -0
- package/android/design/src/main/java/com/bitmart/react/design/DataUriFetcher.kt +42 -0
- package/android/design/src/main/java/com/bitmart/react/design/DesignComponentPackage.kt +35 -0
- package/android/design/src/main/java/com/bitmart/react/design/PrimaryXLargeViewManager.kt +785 -0
- package/android/design/src/main/java/com/bitmart/react/design/TextButtonViewManager.kt +294 -0
- package/ios/DemoProject/NativeDesign/PrimaryButtonViewManager.m +25 -0
- package/ios/DemoProject/NativeDesign/PrimaryButtonViewManager.swift +321 -0
- package/ios/DemoProject/NativeDesign/TextButtonViewManager.m +21 -0
- package/ios/DemoProject/NativeDesign/TextButtonViewManager.swift +184 -0
- package/package.json +8 -1
- package/react-native-ui-components.podspec +23 -0
- package/react-native.config.js +16 -0
- package/src/screens/NativeButtonsScreen.tsx +0 -335
|
@@ -0,0 +1,785 @@
|
|
|
1
|
+
package com.bitmart.react.design
|
|
2
|
+
|
|
3
|
+
import android.content.res.ColorStateList
|
|
4
|
+
import android.graphics.Color
|
|
5
|
+
import android.graphics.PorterDuff
|
|
6
|
+
import android.graphics.drawable.Drawable
|
|
7
|
+
import android.util.Log
|
|
8
|
+
import android.view.ContextThemeWrapper
|
|
9
|
+
import android.view.View
|
|
10
|
+
import android.widget.FrameLayout
|
|
11
|
+
import android.widget.ProgressBar
|
|
12
|
+
import androidx.appcompat.widget.AppCompatButton
|
|
13
|
+
import androidx.core.widget.TextViewCompat
|
|
14
|
+
import coil.ImageLoader
|
|
15
|
+
import coil.decode.SvgDecoder
|
|
16
|
+
import coil.request.ImageRequest
|
|
17
|
+
import com.facebook.react.bridge.Arguments
|
|
18
|
+
import com.facebook.react.bridge.ReactContext
|
|
19
|
+
import com.facebook.react.common.MapBuilder
|
|
20
|
+
import com.facebook.react.uimanager.PixelUtil
|
|
21
|
+
import com.facebook.react.uimanager.SimpleViewManager
|
|
22
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
23
|
+
import com.facebook.react.uimanager.annotations.ReactProp
|
|
24
|
+
import com.facebook.react.uimanager.events.RCTEventEmitter
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* PrimaryXLargeViewManager - 通用的原生按钮 ViewManager
|
|
28
|
+
*
|
|
29
|
+
* 使用原生 Button + 4参数构造函数(minSdk 24 >= API 21)
|
|
30
|
+
* 支持从 RN 侧动态传入样式名称(如 "Primary.XLarge", "Secondary.Medium" 等)
|
|
31
|
+
*
|
|
32
|
+
* 支持的样式:
|
|
33
|
+
* - Primary: XLarge, Large, Medium, Small, XSmall, XXSmall
|
|
34
|
+
* - Secondary: XLarge, Large, Medium, Small, XSmall, XXSmall
|
|
35
|
+
* - Green: XLarge, Large, Medium, Small, XSmall, XXSmall
|
|
36
|
+
* - Red: XLarge, Large, Medium, Small, XSmall, XXSmall
|
|
37
|
+
* - White: XLarge, Large, Medium, Small, XSmall, XXSmall
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* 自定义 FrameLayout:在 onLayout 中强制为 ProgressBar 指定尺寸并居中。
|
|
41
|
+
* 原因:RN/Yoga 只管理本节点尺寸,应用布局时可能覆盖或忽略子 View 的 LayoutParams,
|
|
42
|
+
* 导致 ProgressBar 被量成 0x0。此处不依赖 LayoutParams,用 tag 存进度条尺寸并手动 layout。
|
|
43
|
+
*/
|
|
44
|
+
private class ButtonWithProgressLayout(context: android.content.Context) : FrameLayout(context) {
|
|
45
|
+
|
|
46
|
+
/** 进度条尺寸(px),由 ViewManager 通过 setTag 设置,onLayout 时用于强制 layout 第二子 View */
|
|
47
|
+
var progressBarSizePx: Int
|
|
48
|
+
get() = (getTag() as? Number)?.toInt() ?: 0
|
|
49
|
+
set(value) = setTag(value)
|
|
50
|
+
|
|
51
|
+
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
|
|
52
|
+
val w = r - l
|
|
53
|
+
val h = b - t
|
|
54
|
+
if (childCount < 2) {
|
|
55
|
+
super.onLayout(changed, l, t, r, b)
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
val button = getChildAt(0)
|
|
59
|
+
val progressBar = getChildAt(1)
|
|
60
|
+
val sizePx = progressBarSizePx.coerceAtLeast(0)
|
|
61
|
+
// 第一子 View:按钮 MATCH_PARENT,填满容器(支持 RN width: '100%' / flex: 1)
|
|
62
|
+
button.layout(0, 0, w, h)
|
|
63
|
+
// 第二子 View:ProgressBar,强制 sizePx x sizePx 居中(避免 Yoga/RN 导致 0x0)
|
|
64
|
+
if (sizePx > 0) {
|
|
65
|
+
val pbLeft = (w - sizePx) / 2
|
|
66
|
+
val pbTop = (h - sizePx) / 2
|
|
67
|
+
progressBar.layout(pbLeft, pbTop, pbLeft + sizePx, pbTop + sizePx)
|
|
68
|
+
} else {
|
|
69
|
+
progressBar.layout(0, 0, 0, 0)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
class PrimaryXLargeViewManager : SimpleViewManager<FrameLayout>() {
|
|
75
|
+
|
|
76
|
+
companion object {
|
|
77
|
+
private const val TAG = "PrimaryXLargeViewManager"
|
|
78
|
+
const val REACT_CLASS = "PrimaryXLargeButton"
|
|
79
|
+
const val EVENT_ON_PRESS = "onPress"
|
|
80
|
+
const val EVENT_ON_SIZE_CHANGE = "onSizeChange"
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 样式名称到资源 ID 的映射表
|
|
84
|
+
*/
|
|
85
|
+
private val STYLE_MAP = mapOf(
|
|
86
|
+
// Primary 样式
|
|
87
|
+
"Primary.XLarge" to com.bitmart.design.R.style.Widget_BitMart4_Button_Primary_XLarge,
|
|
88
|
+
"Primary.Large" to com.bitmart.design.R.style.Widget_BitMart4_Button_Primary_Large,
|
|
89
|
+
"Primary.Medium" to com.bitmart.design.R.style.Widget_BitMart4_Button_Primary_Medium,
|
|
90
|
+
"Primary.Small" to com.bitmart.design.R.style.Widget_BitMart4_Button_Primary_Small,
|
|
91
|
+
"Primary.XSmall" to com.bitmart.design.R.style.Widget_BitMart4_Button_Primary_XSmall,
|
|
92
|
+
"Primary.XXSmall" to com.bitmart.design.R.style.Widget_BitMart4_Button_Primary_XXSmall,
|
|
93
|
+
|
|
94
|
+
// Secondary 样式
|
|
95
|
+
"Secondary.XLarge" to com.bitmart.design.R.style.Widget_BitMart4_Button_Secondary_XLarge,
|
|
96
|
+
"Secondary.Large" to com.bitmart.design.R.style.Widget_BitMart4_Button_Secondary_Large,
|
|
97
|
+
"Secondary.Medium" to com.bitmart.design.R.style.Widget_BitMart4_Button_Secondary_Medium,
|
|
98
|
+
"Secondary.Small" to com.bitmart.design.R.style.Widget_BitMart4_Button_Secondary_Small,
|
|
99
|
+
"Secondary.XSmall" to com.bitmart.design.R.style.Widget_BitMart4_Button_Secondary_XSmall,
|
|
100
|
+
"Secondary.XXSmall" to com.bitmart.design.R.style.Widget_BitMart4_Button_Secondary_XXSmall,
|
|
101
|
+
|
|
102
|
+
// Green 样式
|
|
103
|
+
"Green.XLarge" to com.bitmart.design.R.style.Widget_BitMart4_Button_Green_XLarge,
|
|
104
|
+
"Green.Large" to com.bitmart.design.R.style.Widget_BitMart4_Button_Green_Large,
|
|
105
|
+
"Green.Medium" to com.bitmart.design.R.style.Widget_BitMart4_Button_Green_Medium,
|
|
106
|
+
"Green.Small" to com.bitmart.design.R.style.Widget_BitMart4_Button_Green_Small,
|
|
107
|
+
"Green.XSmall" to com.bitmart.design.R.style.Widget_BitMart4_Button_Green_XSmall,
|
|
108
|
+
"Green.XXSmall" to com.bitmart.design.R.style.Widget_BitMart4_Button_Green_XXSmall,
|
|
109
|
+
|
|
110
|
+
// Red 样式
|
|
111
|
+
"Red.XLarge" to com.bitmart.design.R.style.Widget_BitMart4_Button_Red_XLarge,
|
|
112
|
+
"Red.Large" to com.bitmart.design.R.style.Widget_BitMart4_Button_Red_Large,
|
|
113
|
+
"Red.Medium" to com.bitmart.design.R.style.Widget_BitMart4_Button_Red_Medium,
|
|
114
|
+
"Red.Small" to com.bitmart.design.R.style.Widget_BitMart4_Button_Red_Small,
|
|
115
|
+
"Red.XSmall" to com.bitmart.design.R.style.Widget_BitMart4_Button_Red_XSmall,
|
|
116
|
+
"Red.XXSmall" to com.bitmart.design.R.style.Widget_BitMart4_Button_Red_XXSmall,
|
|
117
|
+
|
|
118
|
+
// White 样式
|
|
119
|
+
"White.XLarge" to com.bitmart.design.R.style.Widget_BitMart4_Button_White_XLarge,
|
|
120
|
+
"White.Large" to com.bitmart.design.R.style.Widget_BitMart4_Button_White_Large,
|
|
121
|
+
"White.Medium" to com.bitmart.design.R.style.Widget_BitMart4_Button_White_Medium,
|
|
122
|
+
"White.Small" to com.bitmart.design.R.style.Widget_BitMart4_Button_White_Small,
|
|
123
|
+
"White.XSmall" to com.bitmart.design.R.style.Widget_BitMart4_Button_White_XSmall,
|
|
124
|
+
"White.XXSmall" to com.bitmart.design.R.style.Widget_BitMart4_Button_White_XXSmall
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* 按钮尺寸对应的 ProgressBar 样式(与 fragment_buttons_primary.xml 一致)
|
|
129
|
+
* XLarge/Large/Medium -> Medium; Small -> Small; XSmall -> XSmall; XXSmall -> XXSmall
|
|
130
|
+
*/
|
|
131
|
+
private val PROGRESS_STYLE_MAP = mapOf(
|
|
132
|
+
"Primary.XLarge" to com.bitmart.design.R.style.Widget_BitMart4_ProgressBar_Medium,
|
|
133
|
+
"Primary.Large" to com.bitmart.design.R.style.Widget_BitMart4_ProgressBar_Medium,
|
|
134
|
+
"Primary.Medium" to com.bitmart.design.R.style.Widget_BitMart4_ProgressBar_Medium,
|
|
135
|
+
"Primary.Small" to com.bitmart.design.R.style.Widget_BitMart4_ProgressBar_Small,
|
|
136
|
+
"Primary.XSmall" to com.bitmart.design.R.style.Widget_BitMart4_ProgressBar_XSmall,
|
|
137
|
+
"Primary.XXSmall" to com.bitmart.design.R.style.Widget_BitMart4_ProgressBar_XXSmall,
|
|
138
|
+
"Secondary.XLarge" to com.bitmart.design.R.style.Widget_BitMart4_ProgressBar_Medium,
|
|
139
|
+
"Secondary.Large" to com.bitmart.design.R.style.Widget_BitMart4_ProgressBar_Medium,
|
|
140
|
+
"Secondary.Medium" to com.bitmart.design.R.style.Widget_BitMart4_ProgressBar_Medium,
|
|
141
|
+
"Secondary.Small" to com.bitmart.design.R.style.Widget_BitMart4_ProgressBar_Small,
|
|
142
|
+
"Secondary.XSmall" to com.bitmart.design.R.style.Widget_BitMart4_ProgressBar_XSmall,
|
|
143
|
+
"Secondary.XXSmall" to com.bitmart.design.R.style.Widget_BitMart4_ProgressBar_XXSmall,
|
|
144
|
+
"Green.XLarge" to com.bitmart.design.R.style.Widget_BitMart4_ProgressBar_Medium,
|
|
145
|
+
"Green.Large" to com.bitmart.design.R.style.Widget_BitMart4_ProgressBar_Medium,
|
|
146
|
+
"Green.Medium" to com.bitmart.design.R.style.Widget_BitMart4_ProgressBar_Medium,
|
|
147
|
+
"Green.Small" to com.bitmart.design.R.style.Widget_BitMart4_ProgressBar_Small,
|
|
148
|
+
"Green.XSmall" to com.bitmart.design.R.style.Widget_BitMart4_ProgressBar_XSmall,
|
|
149
|
+
"Green.XXSmall" to com.bitmart.design.R.style.Widget_BitMart4_ProgressBar_XXSmall,
|
|
150
|
+
"Red.XLarge" to com.bitmart.design.R.style.Widget_BitMart4_ProgressBar_Medium,
|
|
151
|
+
"Red.Large" to com.bitmart.design.R.style.Widget_BitMart4_ProgressBar_Medium,
|
|
152
|
+
"Red.Medium" to com.bitmart.design.R.style.Widget_BitMart4_ProgressBar_Medium,
|
|
153
|
+
"Red.Small" to com.bitmart.design.R.style.Widget_BitMart4_ProgressBar_Small,
|
|
154
|
+
"Red.XSmall" to com.bitmart.design.R.style.Widget_BitMart4_ProgressBar_XSmall,
|
|
155
|
+
"Red.XXSmall" to com.bitmart.design.R.style.Widget_BitMart4_ProgressBar_XXSmall,
|
|
156
|
+
"White.XLarge" to com.bitmart.design.R.style.Widget_BitMart4_ProgressBar_Medium,
|
|
157
|
+
"White.Large" to com.bitmart.design.R.style.Widget_BitMart4_ProgressBar_Medium,
|
|
158
|
+
"White.Medium" to com.bitmart.design.R.style.Widget_BitMart4_ProgressBar_Medium,
|
|
159
|
+
"White.Small" to com.bitmart.design.R.style.Widget_BitMart4_ProgressBar_Small,
|
|
160
|
+
"White.XSmall" to com.bitmart.design.R.style.Widget_BitMart4_ProgressBar_XSmall,
|
|
161
|
+
"White.XXSmall" to com.bitmart.design.R.style.Widget_BitMart4_ProgressBar_XXSmall
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
fun getStyleResId(styleName: String?): Int {
|
|
165
|
+
return STYLE_MAP[styleName] ?: com.bitmart.design.R.style.Widget_BitMart4_Button_Primary_XLarge
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
fun getProgressBarStyleResId(styleName: String?): Int {
|
|
169
|
+
return PROGRESS_STYLE_MAP[styleName] ?: com.bitmart.design.R.style.Widget_BitMart4_ProgressBar_Medium
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** 按钮尺寸对应的 ProgressBar 尺寸 dimen(与 design 库 dimens.xml 一致)*/
|
|
173
|
+
private val PROGRESS_SIZE_DIMEN_MAP = mapOf(
|
|
174
|
+
"Primary.XLarge" to com.bitmart.design.R.dimen.bm4_progress_bar_size_medium,
|
|
175
|
+
"Primary.Large" to com.bitmart.design.R.dimen.bm4_progress_bar_size_medium,
|
|
176
|
+
"Primary.Medium" to com.bitmart.design.R.dimen.bm4_progress_bar_size_medium,
|
|
177
|
+
"Primary.Small" to com.bitmart.design.R.dimen.bm4_progress_bar_size_small,
|
|
178
|
+
"Primary.XSmall" to com.bitmart.design.R.dimen.bm4_progress_bar_size_x_small,
|
|
179
|
+
"Primary.XXSmall" to com.bitmart.design.R.dimen.bm4_progress_bar_size_xx_small,
|
|
180
|
+
"Secondary.XLarge" to com.bitmart.design.R.dimen.bm4_progress_bar_size_medium,
|
|
181
|
+
"Secondary.Large" to com.bitmart.design.R.dimen.bm4_progress_bar_size_medium,
|
|
182
|
+
"Secondary.Medium" to com.bitmart.design.R.dimen.bm4_progress_bar_size_medium,
|
|
183
|
+
"Secondary.Small" to com.bitmart.design.R.dimen.bm4_progress_bar_size_small,
|
|
184
|
+
"Secondary.XSmall" to com.bitmart.design.R.dimen.bm4_progress_bar_size_x_small,
|
|
185
|
+
"Secondary.XXSmall" to com.bitmart.design.R.dimen.bm4_progress_bar_size_xx_small,
|
|
186
|
+
"Green.XLarge" to com.bitmart.design.R.dimen.bm4_progress_bar_size_medium,
|
|
187
|
+
"Green.Large" to com.bitmart.design.R.dimen.bm4_progress_bar_size_medium,
|
|
188
|
+
"Green.Medium" to com.bitmart.design.R.dimen.bm4_progress_bar_size_medium,
|
|
189
|
+
"Green.Small" to com.bitmart.design.R.dimen.bm4_progress_bar_size_small,
|
|
190
|
+
"Green.XSmall" to com.bitmart.design.R.dimen.bm4_progress_bar_size_x_small,
|
|
191
|
+
"Green.XXSmall" to com.bitmart.design.R.dimen.bm4_progress_bar_size_xx_small,
|
|
192
|
+
"Red.XLarge" to com.bitmart.design.R.dimen.bm4_progress_bar_size_medium,
|
|
193
|
+
"Red.Large" to com.bitmart.design.R.dimen.bm4_progress_bar_size_medium,
|
|
194
|
+
"Red.Medium" to com.bitmart.design.R.dimen.bm4_progress_bar_size_medium,
|
|
195
|
+
"Red.Small" to com.bitmart.design.R.dimen.bm4_progress_bar_size_small,
|
|
196
|
+
"Red.XSmall" to com.bitmart.design.R.dimen.bm4_progress_bar_size_x_small,
|
|
197
|
+
"Red.XXSmall" to com.bitmart.design.R.dimen.bm4_progress_bar_size_xx_small,
|
|
198
|
+
"White.XLarge" to com.bitmart.design.R.dimen.bm4_progress_bar_size_medium,
|
|
199
|
+
"White.Large" to com.bitmart.design.R.dimen.bm4_progress_bar_size_medium,
|
|
200
|
+
"White.Medium" to com.bitmart.design.R.dimen.bm4_progress_bar_size_medium,
|
|
201
|
+
"White.Small" to com.bitmart.design.R.dimen.bm4_progress_bar_size_small,
|
|
202
|
+
"White.XSmall" to com.bitmart.design.R.dimen.bm4_progress_bar_size_x_small,
|
|
203
|
+
"White.XXSmall" to com.bitmart.design.R.dimen.bm4_progress_bar_size_xx_small
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
fun getProgressBarSizePx(context: android.content.Context, styleName: String?): Int {
|
|
207
|
+
val dimenId = PROGRESS_SIZE_DIMEN_MAP[styleName] ?: com.bitmart.design.R.dimen.bm4_progress_bar_size_medium
|
|
208
|
+
return context.resources.getDimensionPixelSize(dimenId)
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// 保存按钮的样式名称和 ReactContext
|
|
213
|
+
private val buttonStyleMap = mutableMapOf<AppCompatButton, String>()
|
|
214
|
+
private val buttonContextMap = mutableMapOf<AppCompatButton, ReactContext>()
|
|
215
|
+
// 保存按钮的图标信息
|
|
216
|
+
private val buttonIconMap = mutableMapOf<AppCompatButton, IconInfo>()
|
|
217
|
+
// 包装视图 -> 按钮 / 进度条(根视图为 FrameLayout,内含 Button + ProgressBar)
|
|
218
|
+
private val wrapperToButtonMap = mutableMapOf<FrameLayout, AppCompatButton>()
|
|
219
|
+
private val wrapperToProgressBarMap = mutableMapOf<FrameLayout, ProgressBar>()
|
|
220
|
+
private val buttonToWrapperMap = mutableMapOf<AppCompatButton, FrameLayout>()
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* 图标信息数据类
|
|
224
|
+
*/
|
|
225
|
+
private data class IconInfo(
|
|
226
|
+
var svgString: String? = null,
|
|
227
|
+
var iconColor: String = "#ffffff",
|
|
228
|
+
var iconPosition: String = "leading"
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
override fun getName(): String = REACT_CLASS
|
|
232
|
+
|
|
233
|
+
override fun createViewInstance(reactContext: ThemedReactContext): FrameLayout {
|
|
234
|
+
// 默认使用 Primary.XLarge 样式(保持向后兼容)
|
|
235
|
+
val defaultStyleName = "Primary.XLarge"
|
|
236
|
+
val styleResId = getStyleResId(defaultStyleName)
|
|
237
|
+
val progressStyleResId = getProgressBarStyleResId(defaultStyleName)
|
|
238
|
+
|
|
239
|
+
// 使用 ContextThemeWrapper 让 AppCompatButton 自动应用样式
|
|
240
|
+
val themedContext = ContextThemeWrapper(reactContext, styleResId)
|
|
241
|
+
val button = AppCompatButton(themedContext)
|
|
242
|
+
button.isAllCaps = false
|
|
243
|
+
button.gravity = android.view.Gravity.CENTER
|
|
244
|
+
// 保存样式名称和原始 ReactContext
|
|
245
|
+
buttonStyleMap[button] = defaultStyleName
|
|
246
|
+
buttonContextMap[button] = reactContext
|
|
247
|
+
|
|
248
|
+
// 手动应用背景和最小尺寸设置
|
|
249
|
+
applyBackground(button, styleResId)
|
|
250
|
+
|
|
251
|
+
// 进度条:使用 design 库的 ProgressBar 样式(与 fragment_buttons_primary.xml 一致)
|
|
252
|
+
val progressThemedContext = ContextThemeWrapper(reactContext, progressStyleResId)
|
|
253
|
+
val progressBar = ProgressBar(progressThemedContext, null, 0, progressStyleResId).apply {
|
|
254
|
+
visibility = View.GONE
|
|
255
|
+
isIndeterminate = true
|
|
256
|
+
// 尺寸样式可能未继承 indeterminateTint,显式设置白色以保证在彩色按钮上可见
|
|
257
|
+
indeterminateTintList = try {
|
|
258
|
+
ColorStateList.valueOf(reactContext.getColor(com.bitmart.design.R.color.bm4_btn_text))
|
|
259
|
+
} catch (e: Exception) {
|
|
260
|
+
ColorStateList.valueOf(Color.WHITE)
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
val progressSizePx = getProgressBarSizePx(reactContext, defaultStyleName)
|
|
264
|
+
// 使用自定义 Layout:在 onLayout 中强制为 ProgressBar 指定尺寸,避免 Yoga/RN 覆盖子 View LayoutParams 导致 0x0
|
|
265
|
+
// 按钮使用 MATCH_PARENT 填满容器,以便 RN 侧设置 width: '100%' / flex: 1 时能全宽或均分(Footer 底部按钮)
|
|
266
|
+
val wrapper = ButtonWithProgressLayout(reactContext).apply {
|
|
267
|
+
progressBarSizePx = progressSizePx
|
|
268
|
+
addView(button, FrameLayout.LayoutParams(
|
|
269
|
+
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
270
|
+
FrameLayout.LayoutParams.MATCH_PARENT
|
|
271
|
+
).apply { gravity = android.view.Gravity.CENTER })
|
|
272
|
+
addView(progressBar, FrameLayout.LayoutParams(0, 0))
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
wrapperToButtonMap[wrapper] = button
|
|
276
|
+
wrapperToProgressBarMap[wrapper] = progressBar
|
|
277
|
+
buttonToWrapperMap[button] = wrapper
|
|
278
|
+
|
|
279
|
+
return wrapper
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* 从包装视图获取按钮(供各 @ReactProp 使用)
|
|
284
|
+
*/
|
|
285
|
+
private fun getButton(view: View): AppCompatButton? {
|
|
286
|
+
return (view as? FrameLayout)?.let { wrapperToButtonMap[it] }
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* 从包装视图获取进度条
|
|
291
|
+
*/
|
|
292
|
+
private fun getProgressBar(view: View): ProgressBar? {
|
|
293
|
+
return (view as? FrameLayout)?.let { wrapperToProgressBarMap[it] }
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* 根据 styleName 更新进度条样式(尺寸变化时需同步 ProgressBar 尺寸)
|
|
298
|
+
*/
|
|
299
|
+
private fun updateProgressBarStyle(wrapper: FrameLayout, styleName: String) {
|
|
300
|
+
val progressBar = wrapperToProgressBarMap[wrapper] ?: return
|
|
301
|
+
val progressStyleResId = getProgressBarStyleResId(styleName)
|
|
302
|
+
val progressThemedContext = ContextThemeWrapper(wrapper.context, progressStyleResId)
|
|
303
|
+
val newProgressBar = ProgressBar(progressThemedContext, null, 0, progressStyleResId).apply {
|
|
304
|
+
visibility = progressBar.visibility
|
|
305
|
+
isIndeterminate = true
|
|
306
|
+
indeterminateTintList = try {
|
|
307
|
+
ColorStateList.valueOf(wrapper.context.getColor(com.bitmart.design.R.color.bm4_btn_text))
|
|
308
|
+
} catch (e: Exception) {
|
|
309
|
+
ColorStateList.valueOf(Color.WHITE)
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
val progressSizePx = getProgressBarSizePx(wrapper.context, styleName)
|
|
313
|
+
val index = wrapper.indexOfChild(progressBar)
|
|
314
|
+
wrapper.removeView(progressBar)
|
|
315
|
+
wrapper.addView(newProgressBar, index, FrameLayout.LayoutParams(0, 0))
|
|
316
|
+
wrapperToProgressBarMap[wrapper] = newProgressBar
|
|
317
|
+
(wrapper as? ButtonWithProgressLayout)?.progressBarSizePx = progressSizePx
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* 设置按钮文本
|
|
322
|
+
*/
|
|
323
|
+
@ReactProp(name = "text")
|
|
324
|
+
fun setText(view: View, text: String?) {
|
|
325
|
+
val button = getButton(view) ?: return
|
|
326
|
+
val oldText = button.text.toString()
|
|
327
|
+
val newText = text ?: ""
|
|
328
|
+
|
|
329
|
+
button.text = newText
|
|
330
|
+
|
|
331
|
+
// 文本变化后,测量并发送尺寸
|
|
332
|
+
if (oldText != newText) {
|
|
333
|
+
measureAndSendSize(view, button)
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* 设置样式名称(动态切换样式)
|
|
339
|
+
* @param styleName 样式名称,如 "Primary.XLarge", "Secondary.Medium" 等
|
|
340
|
+
*/
|
|
341
|
+
@ReactProp(name = "styleName")
|
|
342
|
+
fun setStyleName(view: View, styleName: String?) {
|
|
343
|
+
val button = getButton(view) ?: return
|
|
344
|
+
val wrapper = view as? FrameLayout ?: return
|
|
345
|
+
if (styleName.isNullOrEmpty()) {
|
|
346
|
+
return
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
val currentStyle = buttonStyleMap[button]
|
|
350
|
+
if (currentStyle == styleName) {
|
|
351
|
+
return
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
val styleResId = getStyleResId(styleName)
|
|
355
|
+
|
|
356
|
+
if (STYLE_MAP.containsKey(styleName)) {
|
|
357
|
+
// 使用新的简化方法:重新应用完整主题
|
|
358
|
+
applyCompleteStyle(button, styleResId, styleName)
|
|
359
|
+
updateProgressBarStyle(wrapper, styleName)
|
|
360
|
+
measureAndSendSize(view, button)
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* 测量按钮尺寸并发送给 RN(使用包装视图的 id 发送事件)
|
|
366
|
+
*/
|
|
367
|
+
private fun measureAndSendSize(view: View, button: AppCompatButton) {
|
|
368
|
+
val wrapper = view as? FrameLayout ?: return
|
|
369
|
+
button.post {
|
|
370
|
+
// 使用 WRAP_CONTENT 测量按钮的内在尺寸
|
|
371
|
+
val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
|
|
372
|
+
val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
|
|
373
|
+
button.measure(widthMeasureSpec, heightMeasureSpec)
|
|
374
|
+
|
|
375
|
+
val measuredWidth = button.measuredWidth
|
|
376
|
+
val measuredHeight = button.measuredHeight
|
|
377
|
+
|
|
378
|
+
// 如果 measure 返回 0,使用文字测量 + padding 作为备选方案
|
|
379
|
+
val finalWidth = if (measuredWidth > 0) {
|
|
380
|
+
measuredWidth
|
|
381
|
+
} else {
|
|
382
|
+
val textWidth = button.paint.measureText(button.text.toString())
|
|
383
|
+
(textWidth + button.paddingStart + button.paddingEnd).toInt()
|
|
384
|
+
}
|
|
385
|
+
val finalHeight = if (measuredHeight > 0) measuredHeight else button.height
|
|
386
|
+
|
|
387
|
+
// 转换为 dp
|
|
388
|
+
val widthDp = PixelUtil.toDIPFromPixel(finalWidth.toFloat())
|
|
389
|
+
val heightDp = PixelUtil.toDIPFromPixel(finalHeight.toFloat())
|
|
390
|
+
|
|
391
|
+
// 发送尺寸变化事件给 RN(使用包装视图 id)
|
|
392
|
+
val reactContext = buttonContextMap[button]
|
|
393
|
+
if (reactContext != null) {
|
|
394
|
+
val event = Arguments.createMap().apply {
|
|
395
|
+
putDouble("width", widthDp.toDouble())
|
|
396
|
+
putDouble("height", heightDp.toDouble())
|
|
397
|
+
}
|
|
398
|
+
reactContext.getJSModule(RCTEventEmitter::class.java)
|
|
399
|
+
.receiveEvent(wrapper.id, EVENT_ON_SIZE_CHANGE, event)
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* 应用背景和最小尺寸设置
|
|
406
|
+
* AppCompatButton 使用 ContextThemeWrapper 后不会自动应用背景,需要手动设置
|
|
407
|
+
*/
|
|
408
|
+
private fun applyBackground(button: AppCompatButton, styleResId: Int) {
|
|
409
|
+
try {
|
|
410
|
+
val context = button.context
|
|
411
|
+
val typedArray = context.obtainStyledAttributes(
|
|
412
|
+
styleResId,
|
|
413
|
+
intArrayOf(android.R.attr.background)
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
// 应用背景(AppCompatButton 需要手动设置)
|
|
417
|
+
typedArray.getDrawable(0)?.let {
|
|
418
|
+
button.background = it
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
typedArray.recycle()
|
|
422
|
+
|
|
423
|
+
// 强制设置 minWidth 和 minHeight 为 0,让按钮宽度自适应文字
|
|
424
|
+
button.minimumWidth = 0
|
|
425
|
+
button.minimumHeight = 0
|
|
426
|
+
} catch (e: Exception) {
|
|
427
|
+
Log.e(TAG, "Error applying background", e)
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* 🆕 简化的完整样式应用方法 - 自动应用所有样式,无需手动解析
|
|
433
|
+
* 通过创建临时按钮获取完整样式,然后复制到目标按钮
|
|
434
|
+
*/
|
|
435
|
+
private fun applyCompleteStyle(button: AppCompatButton, styleResId: Int, styleName: String) {
|
|
436
|
+
try {
|
|
437
|
+
// 保存当前按钮状态
|
|
438
|
+
val currentText = button.text
|
|
439
|
+
val currentEnabled = button.isEnabled
|
|
440
|
+
val originalContext = buttonContextMap[button] ?: return
|
|
441
|
+
|
|
442
|
+
// 创建一个临时按钮来获取完整的样式属性
|
|
443
|
+
val themedContext = ContextThemeWrapper(originalContext, styleResId)
|
|
444
|
+
val tempButton = AppCompatButton(themedContext)
|
|
445
|
+
|
|
446
|
+
// 从临时按钮复制所有样式属性到当前按钮
|
|
447
|
+
copyButtonStyle(tempButton, button)
|
|
448
|
+
|
|
449
|
+
// 复用现有的背景应用方法(确保背景正确生效,并自动设置最小尺寸)
|
|
450
|
+
applyBackground(button, styleResId)
|
|
451
|
+
|
|
452
|
+
// 手动设置其他必要属性
|
|
453
|
+
button.isAllCaps = false
|
|
454
|
+
|
|
455
|
+
// 恢复按钮状态
|
|
456
|
+
button.text = currentText
|
|
457
|
+
button.isEnabled = currentEnabled
|
|
458
|
+
|
|
459
|
+
// 强制刷新视图以确保样式变化生效
|
|
460
|
+
button.invalidate()
|
|
461
|
+
button.requestLayout()
|
|
462
|
+
|
|
463
|
+
// 更新样式映射
|
|
464
|
+
buttonStyleMap[button] = styleName
|
|
465
|
+
|
|
466
|
+
} catch (e: Exception) {
|
|
467
|
+
// 如果新方法失败,回退到原来的方法
|
|
468
|
+
// Log.w(TAG, "Failed to apply complete style, falling back to manual approach", e)
|
|
469
|
+
// applyStyleToButton(button, styleResId)
|
|
470
|
+
// buttonStyleMap[button] = styleName
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* 🆕 从源按钮复制完整样式到目标按钮
|
|
476
|
+
*/
|
|
477
|
+
private fun copyButtonStyle(sourceButton: AppCompatButton, targetButton: AppCompatButton) {
|
|
478
|
+
// 复制文本样式
|
|
479
|
+
targetButton.setTextColor(sourceButton.textColors)
|
|
480
|
+
targetButton.textSize = sourceButton.textSize / targetButton.resources.displayMetrics.scaledDensity
|
|
481
|
+
targetButton.typeface = sourceButton.typeface
|
|
482
|
+
|
|
483
|
+
// 复制 padding
|
|
484
|
+
targetButton.setPadding(
|
|
485
|
+
sourceButton.paddingStart,
|
|
486
|
+
sourceButton.paddingTop,
|
|
487
|
+
sourceButton.paddingEnd,
|
|
488
|
+
sourceButton.paddingBottom
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
// 复制 drawable padding
|
|
492
|
+
targetButton.compoundDrawablePadding = sourceButton.compoundDrawablePadding
|
|
493
|
+
|
|
494
|
+
// 复制 compound drawables 和 tint
|
|
495
|
+
val drawables = sourceButton.compoundDrawables
|
|
496
|
+
targetButton.setCompoundDrawables(drawables[0], drawables[1], drawables[2], drawables[3])
|
|
497
|
+
if (sourceButton.compoundDrawableTintList != null) {
|
|
498
|
+
targetButton.compoundDrawableTintList = sourceButton.compoundDrawableTintList
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// 注意:背景通过单独的 applyBackground 方法处理,确保正确应用
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* ⚠️ 原有的手动解析样式方法(作为备用方案)
|
|
506
|
+
*
|
|
507
|
+
* 注意:这个方法从按钮样式的继承层次中提取属性:
|
|
508
|
+
* 1. Widget.BitMart4.Button.Primary.XLarge (具体尺寸)
|
|
509
|
+
* 2. Widget.BitMart4.Button.Primary (类型样式)
|
|
510
|
+
* 3. Widget.BitMart4.Button (基础样式)
|
|
511
|
+
*
|
|
512
|
+
* 当简化方法失败时使用,需要手动解析每个样式属性
|
|
513
|
+
*/
|
|
514
|
+
private fun applyStyleToButton(button: AppCompatButton, styleResId: Int) {
|
|
515
|
+
try {
|
|
516
|
+
val context = button.context
|
|
517
|
+
|
|
518
|
+
// 获取所有需要的属性
|
|
519
|
+
val typedArray = context.obtainStyledAttributes(
|
|
520
|
+
styleResId,
|
|
521
|
+
intArrayOf(
|
|
522
|
+
android.R.attr.background, // 0
|
|
523
|
+
android.R.attr.textColor, // 1
|
|
524
|
+
android.R.attr.paddingStart, // 2
|
|
525
|
+
android.R.attr.paddingEnd, // 3
|
|
526
|
+
android.R.attr.textAppearance, // 4
|
|
527
|
+
android.R.attr.drawablePadding, // 5
|
|
528
|
+
android.R.attr.drawableTint // 6 - AppCompatButton 支持
|
|
529
|
+
)
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
// 应用背景(AppCompatButton 需要单独设置)
|
|
533
|
+
typedArray.getDrawable(0)?.let {
|
|
534
|
+
button.background = it
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// 获取 textAppearance(每个尺寸都有自己的 textAppearance)
|
|
538
|
+
val textAppearanceResId = typedArray.getResourceId(4, -1)
|
|
539
|
+
|
|
540
|
+
// 获取样式中定义的 textColor(在 Primary/Secondary/等父样式中)
|
|
541
|
+
val textColorFromStyle = typedArray.getColorStateList(1)
|
|
542
|
+
|
|
543
|
+
// 应用 textAppearance(包含 fontFamily, textSize, fontWeight)
|
|
544
|
+
if (textAppearanceResId != -1) {
|
|
545
|
+
button.setTextAppearance(textAppearanceResId)
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// 显式设置 textAllCaps = false(BitMart 按钮不使用大写)
|
|
549
|
+
button.isAllCaps = false
|
|
550
|
+
|
|
551
|
+
// 在 textAppearance 之后,强制应用样式中定义的 textColor
|
|
552
|
+
// 这样可以覆盖 textAppearance 中可能包含的默认颜色
|
|
553
|
+
if (textColorFromStyle != null) {
|
|
554
|
+
button.setTextColor(textColorFromStyle)
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// 应用 padding
|
|
558
|
+
val paddingStart = typedArray.getDimensionPixelSize(2, button.paddingStart)
|
|
559
|
+
val paddingEnd = typedArray.getDimensionPixelSize(3, button.paddingEnd)
|
|
560
|
+
button.setPadding(paddingStart, button.paddingTop, paddingEnd, button.paddingBottom)
|
|
561
|
+
|
|
562
|
+
// 应用 drawablePadding(图标和文字之间的间距)
|
|
563
|
+
val drawablePadding = typedArray.getDimensionPixelSize(5, 0)
|
|
564
|
+
if (drawablePadding > 0) {
|
|
565
|
+
button.compoundDrawablePadding = drawablePadding
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// 应用 drawableTint(AppCompatButton 支持)
|
|
569
|
+
val drawableTint = typedArray.getColorStateList(6)
|
|
570
|
+
if (drawableTint != null) {
|
|
571
|
+
TextViewCompat.setCompoundDrawableTintList(button, drawableTint)
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
typedArray.recycle()
|
|
575
|
+
|
|
576
|
+
// 强制设置 minWidth 和 minHeight 为 0,确保按钮宽度能根据文字内容自适应
|
|
577
|
+
button.minimumWidth = 0
|
|
578
|
+
button.minimumHeight = 0
|
|
579
|
+
} catch (e: Exception) {
|
|
580
|
+
Log.e(TAG, "Error applying style", e)
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* 设置启用状态
|
|
586
|
+
*/
|
|
587
|
+
@ReactProp(name = "enabled", defaultBoolean = true)
|
|
588
|
+
fun setEnabled(view: View, enabled: Boolean) {
|
|
589
|
+
getButton(view)?.let {
|
|
590
|
+
it.isEnabled = enabled
|
|
591
|
+
it.refreshDrawableState()
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* 是否显示进度条(加载状态)。为 true 时:按钮背景移到 wrapper 上(与 ButtonContainer.migrateAttrsFrom 一致),
|
|
597
|
+
* 按钮 INVISIBLE,ProgressBar 叠在按钮形状上;为 false 时恢复。
|
|
598
|
+
* 参考 fragment_buttons_primary.xml 中 ButtonContainer + ProgressBar。
|
|
599
|
+
*/
|
|
600
|
+
@ReactProp(name = "loading", defaultBoolean = false)
|
|
601
|
+
fun setLoading(view: View, loading: Boolean) {
|
|
602
|
+
val wrapper = view as? FrameLayout
|
|
603
|
+
val button = getButton(view)
|
|
604
|
+
val progressBar = getProgressBar(view)
|
|
605
|
+
if (button == null || progressBar == null) return
|
|
606
|
+
if (loading) {
|
|
607
|
+
// 与 ButtonContainer 一致:把按钮的 background 移到 wrapper,这样 loading 时仍显示按钮形状,ProgressBar 叠在上面
|
|
608
|
+
val bg = button.background
|
|
609
|
+
if (bg != null && wrapper != null) {
|
|
610
|
+
wrapper.background = bg
|
|
611
|
+
button.background = null
|
|
612
|
+
}
|
|
613
|
+
button.visibility = View.INVISIBLE
|
|
614
|
+
progressBar.visibility = View.VISIBLE
|
|
615
|
+
} else {
|
|
616
|
+
// 恢复:把 background 从 wrapper 移回按钮(仅当 wrapper 当前持有 background 时,避免初始 setLoading(false) 清空按钮)
|
|
617
|
+
if (wrapper != null && wrapper.background != null) {
|
|
618
|
+
button.background = wrapper.background
|
|
619
|
+
wrapper.background = null
|
|
620
|
+
}
|
|
621
|
+
button.visibility = View.VISIBLE
|
|
622
|
+
progressBar.visibility = View.GONE
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* 设置SVG图标字符串
|
|
628
|
+
*/
|
|
629
|
+
@ReactProp(name = "iconSvgString")
|
|
630
|
+
fun setIconSvgString(view: View, svgString: String?) {
|
|
631
|
+
val button = getButton(view) ?: return
|
|
632
|
+
val iconInfo = buttonIconMap.getOrPut(button) { IconInfo() }
|
|
633
|
+
iconInfo.svgString = svgString
|
|
634
|
+
loadAndApplySvgIcon(button)
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* 设置图标颜色
|
|
639
|
+
*/
|
|
640
|
+
@ReactProp(name = "iconColor")
|
|
641
|
+
fun setIconColor(view: View, color: String?) {
|
|
642
|
+
val button = getButton(view) ?: return
|
|
643
|
+
val iconInfo = buttonIconMap.getOrPut(button) { IconInfo() }
|
|
644
|
+
iconInfo.iconColor = color ?: "#ffffff"
|
|
645
|
+
loadAndApplySvgIcon(button)
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* 设置图标位置
|
|
650
|
+
*/
|
|
651
|
+
@ReactProp(name = "iconPosition")
|
|
652
|
+
fun setIconPosition(view: View, position: String?) {
|
|
653
|
+
val button = getButton(view) ?: return
|
|
654
|
+
val iconInfo = buttonIconMap.getOrPut(button) { IconInfo() }
|
|
655
|
+
iconInfo.iconPosition = position ?: "leading"
|
|
656
|
+
loadAndApplySvgIcon(button)
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* 使用Coil加载并应用SVG图标
|
|
661
|
+
*/
|
|
662
|
+
private fun loadAndApplySvgIcon(button: AppCompatButton) {
|
|
663
|
+
val iconInfo = buttonIconMap[button] ?: return
|
|
664
|
+
val svgString = iconInfo.svgString
|
|
665
|
+
|
|
666
|
+
if (svgString.isNullOrEmpty() || iconInfo.iconPosition == "none") {
|
|
667
|
+
button.setCompoundDrawables(null, null, null, null)
|
|
668
|
+
buttonToWrapperMap[button]?.let { measureAndSendSize(it, button) }
|
|
669
|
+
return
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
try {
|
|
673
|
+
val context = button.context
|
|
674
|
+
val styleName = buttonStyleMap[button] ?: "Primary.XLarge"
|
|
675
|
+
val iconSizePx = getIconSizeForStyle(styleName)
|
|
676
|
+
|
|
677
|
+
// 创建支持 SVG 的 ImageLoader(含 data:image/svg+xml;utf8 的 Fetcher)
|
|
678
|
+
val imageLoader = ImageLoader.Builder(context)
|
|
679
|
+
.components {
|
|
680
|
+
add(DataUriFetcher.Factory())
|
|
681
|
+
add(SvgDecoder.Factory())
|
|
682
|
+
}
|
|
683
|
+
.build()
|
|
684
|
+
|
|
685
|
+
// 使用 SvgStringData 直接传 SVG 字符串,避免 Uri 解析截断 fill="#ffffff" 等
|
|
686
|
+
val request = ImageRequest.Builder(context)
|
|
687
|
+
.data(SvgStringData(svgString))
|
|
688
|
+
.size(iconSizePx, iconSizePx)
|
|
689
|
+
.target { drawable ->
|
|
690
|
+
try {
|
|
691
|
+
val colorStr = iconInfo.iconColor
|
|
692
|
+
if (!colorStr.isNullOrEmpty()) {
|
|
693
|
+
val color = Color.parseColor(colorStr)
|
|
694
|
+
drawable.mutate().setColorFilter(color, PorterDuff.Mode.SRC_IN)
|
|
695
|
+
}
|
|
696
|
+
} catch (e: Exception) {
|
|
697
|
+
Log.e(TAG, "Failed to parse color: ${iconInfo.iconColor}", e)
|
|
698
|
+
}
|
|
699
|
+
drawable.setBounds(0, 0, iconSizePx, iconSizePx)
|
|
700
|
+
applyIconToButton(button, drawable, iconInfo.iconPosition)
|
|
701
|
+
buttonToWrapperMap[button]?.let { measureAndSendSize(it, button) }
|
|
702
|
+
}
|
|
703
|
+
.listener(
|
|
704
|
+
onError = { _, result ->
|
|
705
|
+
Log.e(TAG, "Coil load error: ${result.throwable.message}", result.throwable)
|
|
706
|
+
}
|
|
707
|
+
)
|
|
708
|
+
.build()
|
|
709
|
+
|
|
710
|
+
// 执行请求
|
|
711
|
+
imageLoader.enqueue(request)
|
|
712
|
+
|
|
713
|
+
} catch (e: Exception) {
|
|
714
|
+
Log.e(TAG, "Error loading SVG icon", e)
|
|
715
|
+
button.setCompoundDrawables(null, null, null, null)
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* 应用图标到按钮
|
|
721
|
+
*/
|
|
722
|
+
private fun applyIconToButton(button: AppCompatButton, drawable: Drawable, position: String) {
|
|
723
|
+
when (position) {
|
|
724
|
+
"leading" -> button.setCompoundDrawables(drawable, null, null, null)
|
|
725
|
+
"trailing" -> button.setCompoundDrawables(null, null, drawable, null)
|
|
726
|
+
else -> button.setCompoundDrawables(drawable, null, null, null)
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* 根据样式名称获取图标大小(单位:px)
|
|
732
|
+
*/
|
|
733
|
+
private fun getIconSizeForStyle(styleName: String): Int {
|
|
734
|
+
return when {
|
|
735
|
+
styleName.contains("XLarge") || styleName.contains("Large") -> dpToPx(20f)
|
|
736
|
+
styleName.contains("Medium") || styleName.contains("Small") -> dpToPx(16f)
|
|
737
|
+
styleName.contains("XSmall") || styleName.contains("XXSmall") -> dpToPx(14f)
|
|
738
|
+
else -> dpToPx(20f)
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
/**
|
|
743
|
+
* dp 转 px
|
|
744
|
+
*/
|
|
745
|
+
private fun dpToPx(dp: Float): Int {
|
|
746
|
+
val density = android.content.res.Resources.getSystem().displayMetrics.density
|
|
747
|
+
return (dp * density + 0.5f).toInt()
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
/**
|
|
751
|
+
* 导出事件映射
|
|
752
|
+
*/
|
|
753
|
+
override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any>? {
|
|
754
|
+
return MapBuilder.builder<String, Any>()
|
|
755
|
+
.put(EVENT_ON_PRESS, MapBuilder.of("registrationName", EVENT_ON_PRESS))
|
|
756
|
+
.put(EVENT_ON_SIZE_CHANGE, MapBuilder.of("registrationName", EVENT_ON_SIZE_CHANGE))
|
|
757
|
+
.build()
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
/**
|
|
761
|
+
* 添加点击事件监听(点击在按钮上,事件使用包装视图 id)
|
|
762
|
+
*/
|
|
763
|
+
override fun addEventEmitters(reactContext: ThemedReactContext, view: FrameLayout) {
|
|
764
|
+
val button = getButton(view) ?: return
|
|
765
|
+
button.setOnClickListener {
|
|
766
|
+
reactContext.getJSModule(RCTEventEmitter::class.java)
|
|
767
|
+
.receiveEvent(view.id, EVENT_ON_PRESS, null)
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* 清理资源
|
|
773
|
+
*/
|
|
774
|
+
override fun onDropViewInstance(view: FrameLayout) {
|
|
775
|
+
super.onDropViewInstance(view)
|
|
776
|
+
val button = wrapperToButtonMap.remove(view)
|
|
777
|
+
wrapperToProgressBarMap.remove(view)
|
|
778
|
+
if (button != null) {
|
|
779
|
+
buttonToWrapperMap.remove(button)
|
|
780
|
+
buttonStyleMap.remove(button)
|
|
781
|
+
buttonContextMap.remove(button)
|
|
782
|
+
buttonIconMap.remove(button)
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
}
|