@onekeyfe/react-native-pager-view 3.0.119 → 3.0.121
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/build.gradle +1 -0
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerHost.kt +371 -1
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerNativeHeaders.kt +505 -0
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerViewManager.kt +109 -20
- package/android/src/main/java/com/reactnativepagerview/NestedScrollableHost.kt +4 -4
- package/android/src/main/java/com/reactnativepagerview/event/NativeHeaderPressEvent.kt +31 -0
- package/lib/module/CollapsiblePagerView.js +3 -3
- package/lib/typescript/src/CollapsiblePagerView.d.ts +4 -4
- package/package.json +3 -2
- package/src/CollapsiblePagerView.tsx +12 -7
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
package com.reactnativepagerview
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.graphics.Color
|
|
5
|
+
import android.graphics.Typeface
|
|
6
|
+
import android.graphics.drawable.GradientDrawable
|
|
7
|
+
import android.text.TextUtils
|
|
8
|
+
import android.util.TypedValue
|
|
9
|
+
import android.view.Gravity
|
|
10
|
+
import android.view.MotionEvent
|
|
11
|
+
import android.view.View
|
|
12
|
+
import android.view.ViewGroup
|
|
13
|
+
import android.widget.FrameLayout
|
|
14
|
+
import android.widget.HorizontalScrollView
|
|
15
|
+
import android.widget.TextView
|
|
16
|
+
import androidx.core.graphics.ColorUtils
|
|
17
|
+
import org.json.JSONArray
|
|
18
|
+
import org.json.JSONObject
|
|
19
|
+
import kotlin.math.ceil
|
|
20
|
+
import kotlin.math.floor
|
|
21
|
+
import kotlin.math.max
|
|
22
|
+
import kotlin.math.min
|
|
23
|
+
import kotlin.math.roundToInt
|
|
24
|
+
|
|
25
|
+
internal data class CollapsiblePagerNativeHeaderItem(
|
|
26
|
+
val key: String,
|
|
27
|
+
val title: String,
|
|
28
|
+
val accessibilityLabel: String,
|
|
29
|
+
val testID: String?,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
private fun parseHeaderItems(value: String?): List<CollapsiblePagerNativeHeaderItem> {
|
|
33
|
+
if (value.isNullOrEmpty()) return emptyList()
|
|
34
|
+
return runCatching {
|
|
35
|
+
val array = JSONArray(value)
|
|
36
|
+
List(array.length()) { index ->
|
|
37
|
+
val item = array.optJSONObject(index) ?: JSONObject()
|
|
38
|
+
val title = item.optString("title")
|
|
39
|
+
CollapsiblePagerNativeHeaderItem(
|
|
40
|
+
key = item.optString("key"),
|
|
41
|
+
title = title,
|
|
42
|
+
accessibilityLabel = item.optString("accessibilityLabel", title),
|
|
43
|
+
testID = item.optString("testID").takeIf(String::isNotEmpty),
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
}.getOrDefault(emptyList())
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private class CollapsiblePagerHorizontalItemsView(
|
|
50
|
+
context: Context,
|
|
51
|
+
private val showsProgressIndicator: Boolean,
|
|
52
|
+
) : HorizontalScrollView(context) {
|
|
53
|
+
private val density = resources.displayMetrics.density
|
|
54
|
+
private val content = FrameLayout(context)
|
|
55
|
+
private val indicator = View(context)
|
|
56
|
+
private val indicatorBackground = GradientDrawable().apply {
|
|
57
|
+
shape = GradientDrawable.RECTANGLE
|
|
58
|
+
}
|
|
59
|
+
private val buttons = ArrayList<TextView>()
|
|
60
|
+
private val itemFrames = ArrayList<IntArray>()
|
|
61
|
+
private var items: List<CollapsiblePagerNativeHeaderItem> = emptyList()
|
|
62
|
+
private var selectedKey = ""
|
|
63
|
+
private var rowHeightPx = dp(if (showsProgressIndicator) 44.0 else 42.0)
|
|
64
|
+
private var contentPaddingPx = dp(20.0)
|
|
65
|
+
private var itemSpacingPx = dp(8.0)
|
|
66
|
+
private var fontSize = if (showsProgressIndicator) 16.0 else 14.0
|
|
67
|
+
private var fontFamily: String? = null
|
|
68
|
+
private var activeTextColor = Color.BLACK
|
|
69
|
+
private var inactiveTextColor = Color.GRAY
|
|
70
|
+
private var selectedBackgroundColor = Color.TRANSPARENT
|
|
71
|
+
private var indicatorColor = Color.BLACK
|
|
72
|
+
private var indicatorHeightPx = dp(2.0)
|
|
73
|
+
private var indicatorBottomPx = 0
|
|
74
|
+
private var progress = 0f
|
|
75
|
+
private var centerSelectionWhenIdle = true
|
|
76
|
+
private var isTouchDragging = false
|
|
77
|
+
|
|
78
|
+
var onItemPress: ((Int, String) -> Unit)? = null
|
|
79
|
+
|
|
80
|
+
init {
|
|
81
|
+
isHorizontalScrollBarEnabled = false
|
|
82
|
+
isVerticalScrollBarEnabled = false
|
|
83
|
+
isFillViewport = true
|
|
84
|
+
overScrollMode = OVER_SCROLL_NEVER
|
|
85
|
+
clipToPadding = false
|
|
86
|
+
indicator.background = indicatorBackground
|
|
87
|
+
content.addView(indicator, FrameLayout.LayoutParams(0, 0))
|
|
88
|
+
addView(
|
|
89
|
+
content,
|
|
90
|
+
LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT),
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private fun dp(value: Double): Int = (value * density).roundToInt()
|
|
95
|
+
|
|
96
|
+
fun updateItemsJSON(value: String?) {
|
|
97
|
+
val next = parseHeaderItems(value)
|
|
98
|
+
if (items == next) return
|
|
99
|
+
items = next
|
|
100
|
+
buttons.forEach(content::removeView)
|
|
101
|
+
buttons.clear()
|
|
102
|
+
itemFrames.clear()
|
|
103
|
+
items.forEachIndexed { index, item ->
|
|
104
|
+
val button = TextView(context).apply {
|
|
105
|
+
text = item.title
|
|
106
|
+
contentDescription = item.accessibilityLabel
|
|
107
|
+
setTag(com.facebook.react.R.id.react_test_id, item.testID)
|
|
108
|
+
gravity = Gravity.CENTER
|
|
109
|
+
isSingleLine = true
|
|
110
|
+
ellipsize = TextUtils.TruncateAt.END
|
|
111
|
+
isClickable = true
|
|
112
|
+
isFocusable = true
|
|
113
|
+
setPadding(dp(8.0), 0, dp(8.0), 0)
|
|
114
|
+
setOnClickListener { onItemPress?.invoke(index, item.key) }
|
|
115
|
+
}
|
|
116
|
+
buttons.add(button)
|
|
117
|
+
content.addView(button)
|
|
118
|
+
}
|
|
119
|
+
visibility = if (items.isEmpty()) GONE else VISIBLE
|
|
120
|
+
centerSelectionWhenIdle = true
|
|
121
|
+
requestLayout()
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
fun updateSelectedKey(value: String) {
|
|
125
|
+
if (selectedKey == value) return
|
|
126
|
+
selectedKey = value
|
|
127
|
+
centerSelectionWhenIdle = true
|
|
128
|
+
updatePresentation()
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
fun updateStyle(
|
|
132
|
+
rowHeight: Double,
|
|
133
|
+
contentPadding: Double,
|
|
134
|
+
itemSpacing: Double,
|
|
135
|
+
fontSize: Double,
|
|
136
|
+
fontFamily: String?,
|
|
137
|
+
indicatorHeight: Double = 0.0,
|
|
138
|
+
indicatorBottom: Double = 0.0,
|
|
139
|
+
) {
|
|
140
|
+
rowHeightPx = max(1, dp(rowHeight))
|
|
141
|
+
contentPaddingPx = max(0, dp(contentPadding))
|
|
142
|
+
itemSpacingPx = max(0, dp(itemSpacing))
|
|
143
|
+
this.fontSize = max(1.0, fontSize)
|
|
144
|
+
this.fontFamily = fontFamily
|
|
145
|
+
if (showsProgressIndicator) {
|
|
146
|
+
indicatorHeightPx = max(0, dp(indicatorHeight))
|
|
147
|
+
indicatorBottomPx = max(0, dp(indicatorBottom))
|
|
148
|
+
indicatorBackground.cornerRadius = indicatorHeightPx / 2f
|
|
149
|
+
}
|
|
150
|
+
applyTypeface()
|
|
151
|
+
requestLayout()
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
fun updateColors(
|
|
155
|
+
backgroundColor: Int,
|
|
156
|
+
activeTextColor: Int,
|
|
157
|
+
inactiveTextColor: Int,
|
|
158
|
+
selectedBackgroundColor: Int,
|
|
159
|
+
indicatorColor: Int,
|
|
160
|
+
) {
|
|
161
|
+
setBackgroundColor(backgroundColor)
|
|
162
|
+
content.setBackgroundColor(backgroundColor)
|
|
163
|
+
this.activeTextColor = activeTextColor
|
|
164
|
+
this.inactiveTextColor = inactiveTextColor
|
|
165
|
+
this.selectedBackgroundColor = selectedBackgroundColor
|
|
166
|
+
this.indicatorColor = indicatorColor
|
|
167
|
+
indicatorBackground.setColor(indicatorColor)
|
|
168
|
+
updatePresentation()
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
fun setProgress(value: Float) {
|
|
172
|
+
if (!showsProgressIndicator || items.isEmpty()) return
|
|
173
|
+
val next = value.coerceIn(0f, (items.size - 1).toFloat())
|
|
174
|
+
if (kotlin.math.abs(progress - next) >= 0.001f) centerSelectionWhenIdle = true
|
|
175
|
+
progress = next
|
|
176
|
+
updatePresentation()
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private fun applyTypeface() {
|
|
180
|
+
val typeface = Typeface.create(fontFamily ?: "sans-serif-medium", Typeface.NORMAL)
|
|
181
|
+
buttons.forEach { button ->
|
|
182
|
+
button.typeface = typeface
|
|
183
|
+
button.setTextSize(TypedValue.COMPLEX_UNIT_SP, fontSize.toFloat())
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private fun selectedBackground(selected: Boolean) = GradientDrawable().apply {
|
|
188
|
+
shape = GradientDrawable.RECTANGLE
|
|
189
|
+
setColor(if (selected) selectedBackgroundColor else Color.TRANSPARENT)
|
|
190
|
+
cornerRadius = dp(10.0).toFloat()
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
|
194
|
+
val width = MeasureSpec.getSize(widthMeasureSpec)
|
|
195
|
+
val height = when (MeasureSpec.getMode(heightMeasureSpec)) {
|
|
196
|
+
MeasureSpec.EXACTLY -> MeasureSpec.getSize(heightMeasureSpec)
|
|
197
|
+
else -> rowHeightPx
|
|
198
|
+
}
|
|
199
|
+
applyTypeface()
|
|
200
|
+
itemFrames.clear()
|
|
201
|
+
var x = contentPaddingPx
|
|
202
|
+
buttons.forEachIndexed { index, button ->
|
|
203
|
+
val textWidth = ceil(button.paint.measureText(button.text.toString()).toDouble()).toInt()
|
|
204
|
+
val buttonWidth = max(dp(44.0), textWidth + dp(if (showsProgressIndicator) 16.0 else 20.0))
|
|
205
|
+
val indicatorWidth = textWidth
|
|
206
|
+
val indicatorX = x + (buttonWidth - indicatorWidth) / 2
|
|
207
|
+
itemFrames.add(intArrayOf(x, buttonWidth, indicatorX, indicatorWidth))
|
|
208
|
+
val itemHeight = if (showsProgressIndicator) height else min(dp(32.0), height)
|
|
209
|
+
val top = if (showsProgressIndicator) 0 else max(0, (height - itemHeight) / 2)
|
|
210
|
+
button.layoutParams = FrameLayout.LayoutParams(buttonWidth, itemHeight).apply {
|
|
211
|
+
leftMargin = x
|
|
212
|
+
topMargin = top
|
|
213
|
+
}
|
|
214
|
+
x += buttonWidth
|
|
215
|
+
if (index < buttons.lastIndex) x += itemSpacingPx
|
|
216
|
+
}
|
|
217
|
+
val contentWidth = max(width, x + contentPaddingPx)
|
|
218
|
+
content.layoutParams = LayoutParams(contentWidth, height)
|
|
219
|
+
content.measure(
|
|
220
|
+
MeasureSpec.makeMeasureSpec(contentWidth, MeasureSpec.EXACTLY),
|
|
221
|
+
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY),
|
|
222
|
+
)
|
|
223
|
+
setMeasuredDimension(width, height)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
|
227
|
+
super.onLayout(changed, left, top, right, bottom)
|
|
228
|
+
updatePresentation()
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
override fun onTouchEvent(event: MotionEvent): Boolean {
|
|
232
|
+
when (event.actionMasked) {
|
|
233
|
+
MotionEvent.ACTION_DOWN -> isTouchDragging = true
|
|
234
|
+
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
|
|
235
|
+
isTouchDragging = false
|
|
236
|
+
post(::updatePresentation)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return super.onTouchEvent(event)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private fun updatePresentation() {
|
|
243
|
+
if (buttons.isEmpty() || itemFrames.size != buttons.size) return
|
|
244
|
+
val selectedIndex: Int
|
|
245
|
+
if (showsProgressIndicator) {
|
|
246
|
+
val clamped = progress.coerceIn(0f, buttons.lastIndex.toFloat())
|
|
247
|
+
val lower = floor(clamped).toInt()
|
|
248
|
+
val upper = min(lower + 1, buttons.lastIndex)
|
|
249
|
+
val fraction = clamped - lower
|
|
250
|
+
val from = itemFrames[lower]
|
|
251
|
+
val to = itemFrames[upper]
|
|
252
|
+
val indicatorX = (from[2] + (to[2] - from[2]) * fraction).roundToInt()
|
|
253
|
+
val indicatorWidth = (from[3] + (to[3] - from[3]) * fraction).roundToInt()
|
|
254
|
+
val indicatorTop = max(0, measuredHeight - indicatorBottomPx - indicatorHeightPx)
|
|
255
|
+
indicator.layout(
|
|
256
|
+
indicatorX,
|
|
257
|
+
indicatorTop,
|
|
258
|
+
indicatorX + indicatorWidth,
|
|
259
|
+
indicatorTop + indicatorHeightPx,
|
|
260
|
+
)
|
|
261
|
+
indicator.visibility = if (indicatorHeightPx > 0) VISIBLE else GONE
|
|
262
|
+
selectedIndex = clamped.roundToInt()
|
|
263
|
+
buttons.forEachIndexed { index, button ->
|
|
264
|
+
val emphasis = 1f - min(1f, kotlin.math.abs(clamped - index))
|
|
265
|
+
button.setTextColor(ColorUtils.blendARGB(inactiveTextColor, activeTextColor, emphasis))
|
|
266
|
+
button.background = null
|
|
267
|
+
button.isSelected = index == selectedIndex
|
|
268
|
+
}
|
|
269
|
+
} else {
|
|
270
|
+
selectedIndex = items.indexOfFirst { item -> item.key == selectedKey }
|
|
271
|
+
indicator.visibility = GONE
|
|
272
|
+
buttons.forEachIndexed { index, button ->
|
|
273
|
+
val selected = index == selectedIndex
|
|
274
|
+
button.setTextColor(if (selected) activeTextColor else inactiveTextColor)
|
|
275
|
+
button.background = selectedBackground(selected)
|
|
276
|
+
button.isSelected = selected
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (centerSelectionWhenIdle && !isTouchDragging && width > 0 && selectedIndex in itemFrames.indices) {
|
|
281
|
+
val frame = itemFrames[selectedIndex]
|
|
282
|
+
val desired = (frame[0] + frame[1] / 2 - width / 2)
|
|
283
|
+
.coerceIn(0, max(0, content.measuredWidth - width))
|
|
284
|
+
scrollTo(desired, 0)
|
|
285
|
+
centerSelectionWhenIdle = false
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
internal class CollapsiblePagerNativeTabBarView(context: Context) : FrameLayout(context) {
|
|
291
|
+
private val row = CollapsiblePagerHorizontalItemsView(context, true)
|
|
292
|
+
private var heightPx = 0
|
|
293
|
+
|
|
294
|
+
var onItemPress: ((Int, String) -> Unit)?
|
|
295
|
+
get() = row.onItemPress
|
|
296
|
+
set(value) {
|
|
297
|
+
row.onItemPress = value
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
init {
|
|
301
|
+
addView(row, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))
|
|
302
|
+
visibility = GONE
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
fun updateItemsJSON(value: String?) {
|
|
306
|
+
row.updateItemsJSON(value)
|
|
307
|
+
visibility = row.visibility
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
fun updateStyle(
|
|
311
|
+
height: Double,
|
|
312
|
+
contentPadding: Double,
|
|
313
|
+
itemSpacing: Double,
|
|
314
|
+
fontSize: Double,
|
|
315
|
+
fontFamily: String?,
|
|
316
|
+
indicatorHeight: Double,
|
|
317
|
+
indicatorBottom: Double,
|
|
318
|
+
) {
|
|
319
|
+
heightPx = max(1, (height * resources.displayMetrics.density).roundToInt())
|
|
320
|
+
row.updateStyle(
|
|
321
|
+
height,
|
|
322
|
+
contentPadding,
|
|
323
|
+
itemSpacing,
|
|
324
|
+
fontSize,
|
|
325
|
+
fontFamily,
|
|
326
|
+
indicatorHeight,
|
|
327
|
+
indicatorBottom,
|
|
328
|
+
)
|
|
329
|
+
requestLayout()
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
fun updateColors(
|
|
333
|
+
backgroundColor: Int,
|
|
334
|
+
activeTextColor: Int,
|
|
335
|
+
inactiveTextColor: Int,
|
|
336
|
+
indicatorColor: Int,
|
|
337
|
+
) = row.updateColors(
|
|
338
|
+
backgroundColor,
|
|
339
|
+
activeTextColor,
|
|
340
|
+
inactiveTextColor,
|
|
341
|
+
Color.TRANSPARENT,
|
|
342
|
+
indicatorColor,
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
fun setProgress(progress: Float) = row.setProgress(progress)
|
|
346
|
+
|
|
347
|
+
fun preferredHeightPx(): Int = heightPx
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
internal class CollapsiblePagerNativeSubHeaderView(context: Context) : ViewGroup(context) {
|
|
351
|
+
private val density = resources.displayMetrics.density
|
|
352
|
+
private val tabs = CollapsiblePagerHorizontalItemsView(context, false)
|
|
353
|
+
private val columns = FrameLayout(context)
|
|
354
|
+
private val leading = TextView(context)
|
|
355
|
+
private val middle = TextView(context)
|
|
356
|
+
private val trailing = TextView(context)
|
|
357
|
+
private var configuredHeightPx = dp(74.0)
|
|
358
|
+
private var tabsHeightPx = dp(42.0)
|
|
359
|
+
private var contentPaddingPx = dp(20.0)
|
|
360
|
+
private var itemSpacing = 8.0
|
|
361
|
+
private var itemFontSize = 14.0
|
|
362
|
+
private var columnFontSize = 12.0
|
|
363
|
+
private var trailingColumnWidthPx = dp(80.0)
|
|
364
|
+
private var columnGapPx = dp(8.0)
|
|
365
|
+
private var backgroundColorValue = Color.TRANSPARENT
|
|
366
|
+
private var activeTextColor = Color.BLACK
|
|
367
|
+
private var inactiveTextColor = Color.GRAY
|
|
368
|
+
private var selectedBackgroundColor = Color.TRANSPARENT
|
|
369
|
+
private var fontFamily: String? = null
|
|
370
|
+
|
|
371
|
+
var onItemPress: ((Int, String) -> Unit)?
|
|
372
|
+
get() = tabs.onItemPress
|
|
373
|
+
set(value) {
|
|
374
|
+
tabs.onItemPress = value
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
init {
|
|
378
|
+
addView(tabs)
|
|
379
|
+
addView(columns)
|
|
380
|
+
columns.addView(leading)
|
|
381
|
+
columns.addView(middle)
|
|
382
|
+
columns.addView(trailing)
|
|
383
|
+
listOf(leading, middle, trailing).forEach { label ->
|
|
384
|
+
label.gravity = Gravity.CENTER_VERTICAL
|
|
385
|
+
label.isSingleLine = true
|
|
386
|
+
label.ellipsize = TextUtils.TruncateAt.END
|
|
387
|
+
}
|
|
388
|
+
visibility = GONE
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
private fun dp(value: Double): Int = (value * density).roundToInt()
|
|
392
|
+
|
|
393
|
+
fun updateConfigJSON(value: String?) {
|
|
394
|
+
val config = runCatching { JSONObject(value ?: "{}") }.getOrDefault(JSONObject())
|
|
395
|
+
val style = config.optJSONObject("style") ?: JSONObject()
|
|
396
|
+
val columnsConfig = config.optJSONObject("columns") ?: JSONObject()
|
|
397
|
+
val itemArray = config.optJSONArray("items") ?: JSONArray()
|
|
398
|
+
tabs.updateItemsJSON(itemArray.toString())
|
|
399
|
+
tabs.updateSelectedKey(config.optString("selectedKey"))
|
|
400
|
+
configuredHeightPx = max(1, dp(style.optDouble("height", 74.0)))
|
|
401
|
+
tabsHeightPx = max(0, dp(style.optDouble("tabsHeight", 42.0)))
|
|
402
|
+
contentPaddingPx = max(0, dp(style.optDouble("contentPaddingHorizontal", 20.0)))
|
|
403
|
+
itemSpacing = max(0.0, style.optDouble("itemSpacing", 8.0))
|
|
404
|
+
itemFontSize = max(1.0, style.optDouble("fontSize", 14.0))
|
|
405
|
+
columnFontSize = max(1.0, style.optDouble("columnFontSize", 12.0))
|
|
406
|
+
trailingColumnWidthPx = max(0, dp(style.optDouble("trailingColumnWidth", 80.0)))
|
|
407
|
+
columnGapPx = max(0, dp(style.optDouble("columnGap", 8.0)))
|
|
408
|
+
leading.text = columnsConfig.optString("leading")
|
|
409
|
+
middle.text = columnsConfig.optString("middle")
|
|
410
|
+
trailing.text = columnsConfig.optString("trailing")
|
|
411
|
+
visibility = if (itemArray.length() == 0) GONE else VISIBLE
|
|
412
|
+
applyStyle()
|
|
413
|
+
requestLayout()
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
fun updateColors(
|
|
417
|
+
backgroundColor: Int,
|
|
418
|
+
activeTextColor: Int,
|
|
419
|
+
inactiveTextColor: Int,
|
|
420
|
+
selectedBackgroundColor: Int,
|
|
421
|
+
fontFamily: String?,
|
|
422
|
+
) {
|
|
423
|
+
backgroundColorValue = backgroundColor
|
|
424
|
+
this.activeTextColor = activeTextColor
|
|
425
|
+
this.inactiveTextColor = inactiveTextColor
|
|
426
|
+
this.selectedBackgroundColor = selectedBackgroundColor
|
|
427
|
+
this.fontFamily = fontFamily
|
|
428
|
+
applyStyle()
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
private fun applyStyle() {
|
|
432
|
+
setBackgroundColor(backgroundColorValue)
|
|
433
|
+
columns.setBackgroundColor(backgroundColorValue)
|
|
434
|
+
tabs.updateStyle(
|
|
435
|
+
tabsHeightPx / density.toDouble(),
|
|
436
|
+
contentPaddingPx / density.toDouble(),
|
|
437
|
+
itemSpacing,
|
|
438
|
+
itemFontSize,
|
|
439
|
+
fontFamily,
|
|
440
|
+
)
|
|
441
|
+
tabs.updateColors(
|
|
442
|
+
backgroundColorValue,
|
|
443
|
+
activeTextColor,
|
|
444
|
+
inactiveTextColor,
|
|
445
|
+
selectedBackgroundColor,
|
|
446
|
+
activeTextColor,
|
|
447
|
+
)
|
|
448
|
+
val typeface = Typeface.create(fontFamily ?: "sans-serif-medium", Typeface.NORMAL)
|
|
449
|
+
listOf(leading, middle, trailing).forEach { label ->
|
|
450
|
+
label.typeface = typeface
|
|
451
|
+
label.setTextSize(TypedValue.COMPLEX_UNIT_SP, columnFontSize.toFloat())
|
|
452
|
+
label.setTextColor(inactiveTextColor)
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
fun preferredHeightPx(): Int = configuredHeightPx
|
|
457
|
+
|
|
458
|
+
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
|
459
|
+
val width = MeasureSpec.getSize(widthMeasureSpec)
|
|
460
|
+
val height = MeasureSpec.getSize(heightMeasureSpec)
|
|
461
|
+
val actualTabsHeight = min(height, tabsHeightPx)
|
|
462
|
+
tabs.measure(
|
|
463
|
+
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
|
464
|
+
MeasureSpec.makeMeasureSpec(actualTabsHeight, MeasureSpec.EXACTLY),
|
|
465
|
+
)
|
|
466
|
+
val columnsHeight = max(0, height - actualTabsHeight)
|
|
467
|
+
columns.measure(
|
|
468
|
+
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
|
469
|
+
MeasureSpec.makeMeasureSpec(columnsHeight, MeasureSpec.EXACTLY),
|
|
470
|
+
)
|
|
471
|
+
setMeasuredDimension(width, height)
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
|
475
|
+
val width = right - left
|
|
476
|
+
val height = bottom - top
|
|
477
|
+
val actualTabsHeight = min(height, tabsHeightPx)
|
|
478
|
+
tabs.layout(0, 0, width, actualTabsHeight)
|
|
479
|
+
columns.layout(0, actualTabsHeight, width, height)
|
|
480
|
+
val columnsHeight = height - actualTabsHeight
|
|
481
|
+
val half = width / 2
|
|
482
|
+
val isRtl = layoutDirection == View.LAYOUT_DIRECTION_RTL
|
|
483
|
+
if (!isRtl) {
|
|
484
|
+
leading.gravity = Gravity.START or Gravity.CENTER_VERTICAL
|
|
485
|
+
middle.gravity = Gravity.END or Gravity.CENTER_VERTICAL
|
|
486
|
+
trailing.gravity = Gravity.END or Gravity.CENTER_VERTICAL
|
|
487
|
+
val trailingX = width - contentPaddingPx - trailingColumnWidthPx
|
|
488
|
+
leading.layout(contentPaddingPx, 0, half, columnsHeight)
|
|
489
|
+
middle.layout(half, 0, max(half, trailingX - columnGapPx), columnsHeight)
|
|
490
|
+
trailing.layout(trailingX, 0, width - contentPaddingPx, columnsHeight)
|
|
491
|
+
} else {
|
|
492
|
+
leading.gravity = Gravity.END or Gravity.CENTER_VERTICAL
|
|
493
|
+
middle.gravity = Gravity.START or Gravity.CENTER_VERTICAL
|
|
494
|
+
trailing.gravity = Gravity.START or Gravity.CENTER_VERTICAL
|
|
495
|
+
leading.layout(half, 0, width - contentPaddingPx, columnsHeight)
|
|
496
|
+
middle.layout(
|
|
497
|
+
contentPaddingPx + trailingColumnWidthPx + columnGapPx,
|
|
498
|
+
0,
|
|
499
|
+
half,
|
|
500
|
+
columnsHeight,
|
|
501
|
+
)
|
|
502
|
+
trailing.layout(contentPaddingPx, 0, contentPaddingPx + trailingColumnWidthPx, columnsHeight)
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|