@onekeyfe/react-native-pager-view 6.7.0-2 → 6.7.0-21
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.md +14 -0
- package/android/src/androidTest/AndroidManifest.xml +7 -0
- package/android/src/androidTest/java/com/reactnativepagerview/CollapsiblePagerTabReservationTest.kt +306 -0
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerHost.kt +130 -30
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerNativeHeaders.kt +72 -7
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerViewManager.kt +27 -0
- package/android/src/main/java/com/reactnativepagerview/NativeScrollerScrollAwayInsets.kt +19 -0
- package/android/src/main/java/com/reactnativepagerview/NativeScrollerView.kt +50 -6
- package/android/src/main/java/com/reactnativepagerview/NestedScrollableHost.kt +8 -2
- package/android/src/test/java/com/reactnativepagerview/NativeScrollerScrollAwayInsetsTest.kt +40 -0
- package/docs/SPEC.md +257 -10
- package/ios/RNCCollapsiblePagerViewComponentView.mm +614 -42
- package/ios/RNCNativeScrollerComponentView.h +2 -0
- package/ios/RNCNativeScrollerComponentView.mm +42 -0
- package/ios/tests/RNCCollapsiblePagerFirstFrameTests.mm +403 -1
- package/ios/tests/RNCCollapsiblePagerHitTestTests.mm +338 -0
- package/ios/tests/RNCCollapsiblePagerPressCancellationTests.mm +74 -9
- package/ios/tests/RNCCollapsiblePagerRefreshControlTests.mm +50 -0
- package/lib/module/CollapsiblePagerView.js +32 -7
- package/lib/module/CollapsiblePagerView.web.js +439 -39
- package/lib/module/CollapsiblePagerViewNativeComponent.ts +13 -0
- package/lib/module/NativeScroller.web.js +16 -3
- package/lib/module/WebCollapsiblePagerPage.js +182 -0
- package/lib/module/index.js +2 -0
- package/lib/module/index.native.js +9 -0
- package/lib/module/index.web.js +2 -0
- package/lib/typescript/src/CollapsiblePagerView.d.ts +10 -3
- package/lib/typescript/src/CollapsiblePagerView.web.d.ts +66 -3
- package/lib/typescript/src/CollapsiblePagerViewNativeComponent.d.ts +5 -0
- package/lib/typescript/src/WebCollapsiblePagerPage.d.ts +42 -0
- package/lib/typescript/src/index.d.ts +4 -0
- package/lib/typescript/src/index.native.d.ts +13 -0
- package/lib/typescript/src/index.web.d.ts +4 -0
- package/package.json +6 -4
- package/react-native-pager-view.podspec +1 -0
- package/src/CollapsiblePagerView.tsx +99 -8
- package/src/CollapsiblePagerView.web.tsx +657 -51
- package/src/CollapsiblePagerViewNativeComponent.ts +13 -0
- package/src/NativeScroller.web.tsx +24 -2
- package/src/WebCollapsiblePagerPage.tsx +258 -0
- package/src/index.native.tsx +30 -0
- package/src/index.tsx +4 -0
- package/src/index.web.tsx +4 -0
package/README.md
CHANGED
|
@@ -66,6 +66,20 @@ replacing its delegate. Android coordinates nested scroll with the active
|
|
|
66
66
|
Web scroll child can opt in by exposing a DOM element with
|
|
67
67
|
`data-collapsible-pager-scroll`. CSS scroll timelines drive header/sticky
|
|
68
68
|
movement, so React is not updated on each vertical scroll frame.
|
|
69
|
+
`pagerRef.current?.scrollHeaderBy(delta)` immediately moves the active page's
|
|
70
|
+
collapsible header by a finite logical distance, clamped to its measured range.
|
|
71
|
+
`scrollHeaderToOffset(offset)` targets an absolute collapse position, so a
|
|
72
|
+
keyboard's own scroll adjustment cannot add a second relative movement.
|
|
73
|
+
It is useful for revealing a focused field above a keyboard.
|
|
74
|
+
|
|
75
|
+
On Web, `WebCollapsiblePagerView` is an explicit root export of the existing Web
|
|
76
|
+
pager. It measures Header and sticky content when their heights are omitted.
|
|
77
|
+
`isActive` describes route visibility; the pager preserves a pending scroll
|
|
78
|
+
target across a short or temporarily hidden page and retries when real content
|
|
79
|
+
geometry becomes ready. `useWebCollapsiblePagerPage()` exposes the selected
|
|
80
|
+
page's scroll owner, visible metrics, content registration and pending target
|
|
81
|
+
to descendants. These Web-only APIs require a DOM when mounted. Native callers
|
|
82
|
+
continue to use `CollapsiblePagerView`.
|
|
69
83
|
|
|
70
84
|
On iOS and Android, `nativeTabBar` renders the tab bar natively and
|
|
71
85
|
`onNativeTabPress` reports presses. By default, adjacent tab presses animate and
|
package/android/src/androidTest/java/com/reactnativepagerview/CollapsiblePagerTabReservationTest.kt
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
package com.reactnativepagerview
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.content.Intent
|
|
5
|
+
import android.content.pm.ApplicationInfo
|
|
6
|
+
import android.graphics.Bitmap
|
|
7
|
+
import android.graphics.Canvas
|
|
8
|
+
import android.graphics.Color
|
|
9
|
+
import android.test.InstrumentationTestCase
|
|
10
|
+
import android.view.MotionEvent
|
|
11
|
+
import android.view.View
|
|
12
|
+
import android.view.ViewGroup
|
|
13
|
+
import android.widget.HorizontalScrollView
|
|
14
|
+
import android.widget.TextView
|
|
15
|
+
import androidx.recyclerview.widget.RecyclerView
|
|
16
|
+
import androidx.viewpager2.widget.ViewPager2
|
|
17
|
+
import com.facebook.react.uimanager.PointerEvents
|
|
18
|
+
import com.facebook.react.views.view.ReactViewGroup
|
|
19
|
+
import java.util.concurrent.atomic.AtomicInteger
|
|
20
|
+
import kotlin.math.roundToInt
|
|
21
|
+
|
|
22
|
+
class CollapsiblePagerHitTestActivity : Activity()
|
|
23
|
+
|
|
24
|
+
class CollapsiblePagerTabReservationTest : InstrumentationTestCase() {
|
|
25
|
+
fun testSegmentedNativeTabsTrackFractionalProgressAndReset() {
|
|
26
|
+
val context = instrumentation.targetContext
|
|
27
|
+
assertTrue(context.applicationInfo.flags and ApplicationInfo.FLAG_SUPPORTS_RTL != 0)
|
|
28
|
+
val activity = instrumentation.startActivitySync(
|
|
29
|
+
Intent(context, CollapsiblePagerHitTestActivity::class.java)
|
|
30
|
+
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
|
|
31
|
+
) as CollapsiblePagerHitTestActivity
|
|
32
|
+
val density = context.resources.displayMetrics.density
|
|
33
|
+
fun dp(value: Int) = (value * density).roundToInt()
|
|
34
|
+
lateinit var tab: CollapsiblePagerNativeTabBarView
|
|
35
|
+
lateinit var row: HorizontalScrollView
|
|
36
|
+
lateinit var content: ViewGroup
|
|
37
|
+
lateinit var track: View
|
|
38
|
+
lateinit var thumb: View
|
|
39
|
+
lateinit var buttons: List<TextView>
|
|
40
|
+
fun layout(width: Int = 320, height: Int = 56) {
|
|
41
|
+
tab.measure(View.MeasureSpec.makeMeasureSpec(dp(width), View.MeasureSpec.EXACTLY),
|
|
42
|
+
View.MeasureSpec.makeMeasureSpec(dp(height), View.MeasureSpec.EXACTLY))
|
|
43
|
+
tab.layout(0, 0, dp(width), dp(height))
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
instrumentation.runOnMainSync {
|
|
47
|
+
tab = CollapsiblePagerNativeTabBarView(context)
|
|
48
|
+
activity.setContentView(tab)
|
|
49
|
+
val presses = mutableListOf<String>()
|
|
50
|
+
tab.onItemPress = { _, key -> presses.add(key) }
|
|
51
|
+
tab.updateItemsJSON("""[{"key":"recent","title":"Recents"},""" +
|
|
52
|
+
"""{"key":"account","title":"Accounts"},""" +
|
|
53
|
+
"""{"key":"book","title":"Address Book"}]""")
|
|
54
|
+
tab.updateStyle(56.0, "segmented", 20.0, 12.0, 0.0, 14.0, null, 2.0, 0.0)
|
|
55
|
+
tab.updateColors(Color.LTGRAY, Color.BLACK, Color.GRAY, Color.WHITE)
|
|
56
|
+
layout()
|
|
57
|
+
row = tab.getChildAt(0) as HorizontalScrollView
|
|
58
|
+
content = row.getChildAt(0) as ViewGroup
|
|
59
|
+
track = content.getChildAt(0)
|
|
60
|
+
thumb = content.getChildAt(1)
|
|
61
|
+
buttons = (0 until content.childCount).map(content::getChildAt).filterIsInstance<TextView>()
|
|
62
|
+
assertEquals(3, buttons.size)
|
|
63
|
+
assertEquals(dp(20), track.left)
|
|
64
|
+
assertTrue(kotlin.math.abs(dp(32) - track.height) <= 1)
|
|
65
|
+
assertEquals(0, row.scrollX)
|
|
66
|
+
assertTrue(kotlin.math.abs(buttons[0].width - buttons[1].width) <= 1)
|
|
67
|
+
tab.setProgress(0f)
|
|
68
|
+
val firstX = thumb.left
|
|
69
|
+
tab.setProgress(1f)
|
|
70
|
+
val secondX = thumb.left
|
|
71
|
+
tab.setProgress(0.25f)
|
|
72
|
+
assertTrue(kotlin.math.abs(thumb.left - (firstX + (secondX - firstX) * 0.25f)) <= 1)
|
|
73
|
+
tab.setProgress(0.5f)
|
|
74
|
+
assertTrue(kotlin.math.abs(thumb.left - (firstX + secondX) / 2f) <= 1)
|
|
75
|
+
tab.setProgress(0f)
|
|
76
|
+
assertEquals(firstX, thumb.left)
|
|
77
|
+
buttons[1].performClick()
|
|
78
|
+
assertEquals(listOf("account"), presses)
|
|
79
|
+
|
|
80
|
+
tab.layoutDirection = View.LAYOUT_DIRECTION_RTL
|
|
81
|
+
}
|
|
82
|
+
instrumentation.waitForIdleSync()
|
|
83
|
+
instrumentation.runOnMainSync {
|
|
84
|
+
layout()
|
|
85
|
+
assertEquals(View.LAYOUT_DIRECTION_RTL, row.layoutDirection)
|
|
86
|
+
val rtlFirst = buttons[0].layoutParams as android.widget.FrameLayout.LayoutParams
|
|
87
|
+
val rtlSecond = buttons[1].layoutParams as android.widget.FrameLayout.LayoutParams
|
|
88
|
+
assertTrue(rtlFirst.leftMargin > rtlSecond.leftMargin)
|
|
89
|
+
val rtlGeometry = "rowDir=${row.layoutDirection} contentDir=${content.layoutDirection} " +
|
|
90
|
+
buttons.mapIndexed { index, button ->
|
|
91
|
+
val params = button.layoutParams as android.widget.FrameLayout.LayoutParams
|
|
92
|
+
"$index:${button.left}-${button.right} margin=${params.leftMargin}/${params.rightMargin} gravity=${params.gravity}"
|
|
93
|
+
}.joinToString(" ")
|
|
94
|
+
assertTrue(rtlGeometry, buttons[0].left >= buttons[1].right)
|
|
95
|
+
assertEquals(dp(20), track.left)
|
|
96
|
+
tab.setProgress(0f)
|
|
97
|
+
val rtlFirstX = thumb.left
|
|
98
|
+
tab.setProgress(1f)
|
|
99
|
+
val rtlSecondX = thumb.left
|
|
100
|
+
assertTrue(rtlFirstX > rtlSecondX)
|
|
101
|
+
tab.setProgress(0.5f)
|
|
102
|
+
assertTrue(kotlin.math.abs(thumb.left - (rtlFirstX + rtlSecondX) / 2f) <= 1)
|
|
103
|
+
tab.layoutDirection = View.LAYOUT_DIRECTION_LTR
|
|
104
|
+
}
|
|
105
|
+
instrumentation.waitForIdleSync()
|
|
106
|
+
instrumentation.runOnMainSync {
|
|
107
|
+
layout()
|
|
108
|
+
assertTrue(buttons[0].right <= buttons[1].left)
|
|
109
|
+
tab.updateItemsJSON("""[{"key":"recent","title":"Recents (8)"},""" +
|
|
110
|
+
"""{"key":"account","title":"Accounts"}]""")
|
|
111
|
+
layout()
|
|
112
|
+
val twoButtons = (0 until content.childCount).map(content::getChildAt).filterIsInstance<TextView>()
|
|
113
|
+
assertEquals(2, twoButtons.size)
|
|
114
|
+
assertTrue(kotlin.math.abs(twoButtons[0].width - track.width / 2) <= 1)
|
|
115
|
+
layout(width = 30)
|
|
116
|
+
assertEquals(dp(15), track.left)
|
|
117
|
+
assertEquals(0, track.width)
|
|
118
|
+
tab.updateStyle(56.0, "segmented", 20.0, 40.0, 0.0, 14.0, null, 2.0, 0.0)
|
|
119
|
+
layout(width = 30)
|
|
120
|
+
assertTrue(kotlin.math.abs(dp(28) - track.top) <= 1)
|
|
121
|
+
assertEquals(0, track.height)
|
|
122
|
+
assertEquals(View.GONE, thumb.visibility)
|
|
123
|
+
tab.updateItemsJSON("[]")
|
|
124
|
+
assertEquals(View.GONE, tab.visibility)
|
|
125
|
+
tab.updateItemsJSON("""[{"key":"recent","title":"Recents"}]""")
|
|
126
|
+
tab.updateStyle(44.0, "underline", 20.0, 0.0, 8.0, 16.0, null, 2.0, 0.0)
|
|
127
|
+
layout(height = 44)
|
|
128
|
+
assertEquals(View.GONE, track.visibility)
|
|
129
|
+
assertEquals(dp(2), thumb.height)
|
|
130
|
+
}
|
|
131
|
+
} finally {
|
|
132
|
+
instrumentation.runOnMainSync { activity.finish() }
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
fun testTabViewportToolbarHitAndResetInBothDirections() {
|
|
137
|
+
val context = instrumentation.targetContext
|
|
138
|
+
assertTrue(context.applicationInfo.flags and ApplicationInfo.FLAG_SUPPORTS_RTL != 0)
|
|
139
|
+
val activity = instrumentation.startActivitySync(
|
|
140
|
+
Intent(context, CollapsiblePagerHitTestActivity::class.java)
|
|
141
|
+
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
|
|
142
|
+
) as CollapsiblePagerHitTestActivity
|
|
143
|
+
val toolbarPresses = AtomicInteger()
|
|
144
|
+
val tabPresses = AtomicInteger()
|
|
145
|
+
try {
|
|
146
|
+
instrumentation.runOnMainSync {
|
|
147
|
+
val density = context.resources.displayMetrics.density
|
|
148
|
+
fun dp(value: Int) = (value * density).roundToInt()
|
|
149
|
+
val width = dp(320)
|
|
150
|
+
val height = dp(500)
|
|
151
|
+
val reserved = dp(64)
|
|
152
|
+
val tabHeight = dp(44)
|
|
153
|
+
val host = CollapsiblePagerHost(context)
|
|
154
|
+
activity.setContentView(host)
|
|
155
|
+
assertTrue(host.isAttachedToWindow)
|
|
156
|
+
val sticky = ReactViewGroup(context).apply { pointerEvents = PointerEvents.BOX_NONE }
|
|
157
|
+
val fade = View(context).apply { setBackgroundColor(Color.RED) }
|
|
158
|
+
val toolbar = View(context).apply {
|
|
159
|
+
setBackgroundColor(Color.BLUE)
|
|
160
|
+
isClickable = true
|
|
161
|
+
}
|
|
162
|
+
sticky.addView(fade)
|
|
163
|
+
sticky.addView(toolbar)
|
|
164
|
+
host.addReactChild(View(context), 0)
|
|
165
|
+
host.addReactChild(sticky, 1)
|
|
166
|
+
host.headerHeightPx = 0
|
|
167
|
+
host.stickyHeaderHeightPx = tabHeight
|
|
168
|
+
host.nativeTabBarBackgroundColor = Color.GREEN
|
|
169
|
+
host.applyNativeHeaderStyle()
|
|
170
|
+
host.updateNativeTabBarItems(
|
|
171
|
+
"""[{"key":"a","title":"Overview"},{"key":"b","title":"Ethereum"},""" +
|
|
172
|
+
"""{"key":"c","title":"BNB Chain"},{"key":"d","title":"Polygon"}]""",
|
|
173
|
+
)
|
|
174
|
+
val tab = (0 until host.childCount).map(host::getChildAt)
|
|
175
|
+
.first { it.javaClass.simpleName == "CollapsiblePagerNativeTabBarView" } as ViewGroup
|
|
176
|
+
val row = tab.getChildAt(0) as HorizontalScrollView
|
|
177
|
+
toolbar.setOnClickListener { toolbarPresses.incrementAndGet() }
|
|
178
|
+
host.onNativeTabPress = { _, _ -> tabPresses.incrementAndGet() }
|
|
179
|
+
|
|
180
|
+
fun layout() {
|
|
181
|
+
host.measure(View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
|
|
182
|
+
View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY))
|
|
183
|
+
host.layout(0, 0, width, height)
|
|
184
|
+
}
|
|
185
|
+
fun tap(x: Int) {
|
|
186
|
+
val time = android.os.SystemClock.uptimeMillis()
|
|
187
|
+
val down = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN,
|
|
188
|
+
x.toFloat(), (tabHeight / 2).toFloat(), 0)
|
|
189
|
+
val up = MotionEvent.obtain(time, time + 20, MotionEvent.ACTION_UP,
|
|
190
|
+
x.toFloat(), (tabHeight / 2).toFloat(), 0)
|
|
191
|
+
host.dispatchTouchEvent(down)
|
|
192
|
+
host.dispatchTouchEvent(up)
|
|
193
|
+
down.recycle()
|
|
194
|
+
up.recycle()
|
|
195
|
+
}
|
|
196
|
+
fun colorAt(x: Int): Int {
|
|
197
|
+
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
|
198
|
+
host.draw(Canvas(bitmap))
|
|
199
|
+
val color = bitmap.getPixel(x, tabHeight / 2)
|
|
200
|
+
bitmap.recycle()
|
|
201
|
+
return color
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
host.nativeTabBarTrailingReservedWidth = 64.0
|
|
205
|
+
layout()
|
|
206
|
+
fade.layout(width - reserved - dp(20), 0, width - reserved, tabHeight)
|
|
207
|
+
toolbar.layout(width - reserved, 0, width, tabHeight)
|
|
208
|
+
assertEquals(width - reserved, tab.width)
|
|
209
|
+
assertEquals(0, tab.left)
|
|
210
|
+
assertEquals(width, sticky.width)
|
|
211
|
+
assertEquals(Color.RED, colorAt(width - reserved - dp(10)))
|
|
212
|
+
tap(width - dp(10))
|
|
213
|
+
tap(dp(28))
|
|
214
|
+
host.updateNativeTabProgress(3, 0f)
|
|
215
|
+
val content = row.getChildAt(0) as ViewGroup
|
|
216
|
+
val lastTab = (0 until content.childCount).map(content::getChildAt)
|
|
217
|
+
.filterIsInstance<TextView>().last()
|
|
218
|
+
assertTrue(lastTab.left >= row.scrollX - dp(1))
|
|
219
|
+
assertTrue(lastTab.right <= row.scrollX + row.width + dp(1))
|
|
220
|
+
|
|
221
|
+
host.setPagerLayoutDirection(View.LAYOUT_DIRECTION_RTL)
|
|
222
|
+
host.nativeTabBarTrailingReservedWidth = 64.0
|
|
223
|
+
layout()
|
|
224
|
+
assertEquals(View.LAYOUT_DIRECTION_RTL, tab.layoutDirection)
|
|
225
|
+
toolbar.layout(0, 0, reserved, tabHeight)
|
|
226
|
+
fade.layout(reserved, 0, reserved + dp(20), tabHeight)
|
|
227
|
+
assertEquals(reserved, tab.left)
|
|
228
|
+
assertEquals(width, tab.right)
|
|
229
|
+
assertEquals(Color.RED, colorAt(reserved + dp(10)))
|
|
230
|
+
tap(dp(10))
|
|
231
|
+
|
|
232
|
+
host.nativeTabBarTrailingReservedWidth = 500.0
|
|
233
|
+
layout()
|
|
234
|
+
assertEquals(0, tab.width)
|
|
235
|
+
host.nativeTabBarTrailingReservedWidth = 0.0
|
|
236
|
+
layout()
|
|
237
|
+
assertEquals(0, tab.left)
|
|
238
|
+
assertEquals(width, tab.width)
|
|
239
|
+
assertTrue(host.indexOfChild(sticky) < host.indexOfChild(tab))
|
|
240
|
+
}
|
|
241
|
+
instrumentation.waitForIdleSync()
|
|
242
|
+
assertEquals(2, toolbarPresses.get())
|
|
243
|
+
assertEquals(1, tabPresses.get())
|
|
244
|
+
} finally {
|
|
245
|
+
instrumentation.runOnMainSync { activity.finish() }
|
|
246
|
+
instrumentation.waitForIdleSync()
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
fun testUnmountWithTransitioningPageHoldersSkipsEmptyRecyclerLayout() {
|
|
251
|
+
val context = instrumentation.targetContext
|
|
252
|
+
val activity = instrumentation.startActivitySync(
|
|
253
|
+
Intent(context, CollapsiblePagerHitTestActivity::class.java)
|
|
254
|
+
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
|
|
255
|
+
) as CollapsiblePagerHitTestActivity
|
|
256
|
+
try {
|
|
257
|
+
instrumentation.runOnMainSync {
|
|
258
|
+
val density = context.resources.displayMetrics.density
|
|
259
|
+
fun dp(value: Int) = (value * density).roundToInt()
|
|
260
|
+
val width = dp(320)
|
|
261
|
+
val height = dp(500)
|
|
262
|
+
val host = CollapsiblePagerHost(context)
|
|
263
|
+
activity.setContentView(host)
|
|
264
|
+
val header = View(context)
|
|
265
|
+
val sticky = View(context)
|
|
266
|
+
val page0 = View(context)
|
|
267
|
+
val page1 = View(context)
|
|
268
|
+
host.addReactChild(header, 0)
|
|
269
|
+
host.addReactChild(sticky, 1)
|
|
270
|
+
host.addReactChild(page0, 2)
|
|
271
|
+
host.addReactChild(page1, 3)
|
|
272
|
+
host.measure(
|
|
273
|
+
View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
|
|
274
|
+
View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY),
|
|
275
|
+
)
|
|
276
|
+
host.layout(0, 0, width, height)
|
|
277
|
+
val pager = (0 until host.childCount).map(host::getChildAt).filterIsInstance<ViewPager2>().first()
|
|
278
|
+
val recycler = pager.getChildAt(0) as RecyclerView
|
|
279
|
+
assertNull(recycler.itemAnimator)
|
|
280
|
+
assertTrue(host.attachedPageCount() > 0)
|
|
281
|
+
|
|
282
|
+
val transitioningHolders = (0 until recycler.childCount).map(recycler::getChildAt)
|
|
283
|
+
transitioningHolders.forEach { recycler.startViewTransition(it) }
|
|
284
|
+
|
|
285
|
+
host.removeReactChild(page1)
|
|
286
|
+
host.removeReactChild(page0)
|
|
287
|
+
host.removeReactChild(sticky)
|
|
288
|
+
host.removeReactChild(header)
|
|
289
|
+
|
|
290
|
+
host.viewTreeObserver.dispatchOnPreDraw()
|
|
291
|
+
host.measure(
|
|
292
|
+
View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
|
|
293
|
+
View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY),
|
|
294
|
+
)
|
|
295
|
+
host.layout(0, 0, width, height)
|
|
296
|
+
assertTrue(host.attachedPageCount() > 0)
|
|
297
|
+
|
|
298
|
+
transitioningHolders.forEach { recycler.endViewTransition(it) }
|
|
299
|
+
}
|
|
300
|
+
instrumentation.waitForIdleSync()
|
|
301
|
+
} finally {
|
|
302
|
+
instrumentation.runOnMainSync { activity.finish() }
|
|
303
|
+
instrumentation.waitForIdleSync()
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
@@ -142,7 +142,7 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
142
142
|
private var transitionSourceRecyclerAnchor: RecyclerReadingAnchor? = null
|
|
143
143
|
private val commandBodyKeys = HashSet<String>()
|
|
144
144
|
private var explicitTopCommand: Pair<String, PageRestoreRequest>? = null
|
|
145
|
-
private class PagerTopRequest(view: View, val key: String, val pageIndex: Int, val generation: Int, val
|
|
145
|
+
private class PagerTopRequest(view: View, val key: String, val pageIndex: Int, val generation: Int, val logicalOffset: Int, val token: Int) {
|
|
146
146
|
val view = WeakReference(view)
|
|
147
147
|
}
|
|
148
148
|
private var pagerTopRequest: PagerTopRequest? = null
|
|
@@ -293,7 +293,23 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
293
293
|
var onNativeSubHeaderPress: ((Int, String) -> Unit)? = null
|
|
294
294
|
|
|
295
295
|
var nativeTabBarHeight = 44.0
|
|
296
|
+
var nativeTabBarVariant = "underline"
|
|
297
|
+
var nativeTabBarTrailingReservedWidth = 0.0
|
|
298
|
+
set(value) {
|
|
299
|
+
field = if (value.isFinite() && value >= 0.0) value else 0.0
|
|
300
|
+
bringNativeHeadersToFront()
|
|
301
|
+
requestLayout()
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
private fun nativeTabReservationPx(width: Int): Int {
|
|
305
|
+
val reserved = nativeTabBarTrailingReservedWidth
|
|
306
|
+
val density = resources.displayMetrics.density
|
|
307
|
+
return if (reserved.isFinite() && reserved > 0.0 && width > 0 && density > 0f) {
|
|
308
|
+
(reserved.coerceAtMost(width / density.toDouble()) * density).roundToInt()
|
|
309
|
+
} else 0
|
|
310
|
+
}
|
|
296
311
|
var nativeTabBarContentPaddingHorizontal = 20.0
|
|
312
|
+
var nativeTabBarContentPaddingVertical = 0.0
|
|
297
313
|
var nativeTabBarItemSpacing = 8.0
|
|
298
314
|
var nativeTabBarFontSize = 16.0
|
|
299
315
|
var nativeTabBarFontFamily: String? = null
|
|
@@ -311,6 +327,7 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
311
327
|
pager.id = View.generateViewId()
|
|
312
328
|
pager.adapter = adapter
|
|
313
329
|
pager.isSaveEnabled = false
|
|
330
|
+
(pager.getChildAt(0) as? RecyclerView)?.itemAnimator = null
|
|
314
331
|
super.addView(
|
|
315
332
|
pager,
|
|
316
333
|
LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT),
|
|
@@ -416,7 +433,9 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
416
433
|
fun applyNativeHeaderStyle() {
|
|
417
434
|
nativeTabBarView.updateStyle(
|
|
418
435
|
nativeTabBarHeight,
|
|
436
|
+
nativeTabBarVariant,
|
|
419
437
|
nativeTabBarContentPaddingHorizontal,
|
|
438
|
+
nativeTabBarContentPaddingVertical,
|
|
420
439
|
nativeTabBarItemSpacing,
|
|
421
440
|
nativeTabBarFontSize,
|
|
422
441
|
nativeTabBarFontFamily,
|
|
@@ -442,6 +461,7 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
442
461
|
private fun bringNativeHeadersToFront() {
|
|
443
462
|
nativeTabBarView.bringToFront()
|
|
444
463
|
nativeSubHeaderView.bringToFront()
|
|
464
|
+
if (nativeTabBarTrailingReservedWidth > 0.0) stickyHeaderView?.bringToFront()
|
|
445
465
|
}
|
|
446
466
|
|
|
447
467
|
fun addReactChild(child: View, index: Int) {
|
|
@@ -571,9 +591,29 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
571
591
|
cancelPagerTop()
|
|
572
592
|
if (adapter.itemCount == 0 || !nativeSmoothHeaderScrollEnabled || headerHeightPx <= 0) return
|
|
573
593
|
if (transitionHeaderOffsets == null) {
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
594
|
+
val sourceKey = currentPageKey()
|
|
595
|
+
val sourceView = primaryScrollableForPage(selectedPage)
|
|
596
|
+
val topInset = headerHeightPx + stickyHeaderHeightPx
|
|
597
|
+
val sourceRestored = sourceKey !in pendingPageRestores && when (sourceView) {
|
|
598
|
+
is RecyclerView -> restoredRecyclerKeys[sourceView] == sourceKey &&
|
|
599
|
+
originalRecyclerPadding[sourceView]?.appliedTopInset == topInset
|
|
600
|
+
is ScrollView -> restoredScrollerKeys[sourceView] == sourceKey &&
|
|
601
|
+
originalScrollerPadding[sourceView]?.appliedTopInset == topInset
|
|
602
|
+
is NativeScrollerViewport -> ownedScrollerKeys[sourceView] == sourceKey
|
|
603
|
+
else -> false
|
|
604
|
+
}
|
|
605
|
+
transitionSourceDisplayBody = if (sourceRestored && sourceView != null) {
|
|
606
|
+
verticalScrollOffset(sourceView)
|
|
607
|
+
} else {
|
|
608
|
+
displayBodyOverrides[sourceKey] ?: pageOffsets[sourceKey] ?: 0
|
|
609
|
+
}
|
|
610
|
+
transitionSourceRecyclerAnchor = if (sourceRestored && sourceView is RecyclerView) {
|
|
611
|
+
captureRecyclerAnchor(sourceView)
|
|
612
|
+
} else {
|
|
613
|
+
(displayRecyclerAnchors[sourceKey] ?: recyclerReadingAnchors[sourceKey])
|
|
614
|
+
?.takeIf { sourceView !is RecyclerView || (it.view.get() === sourceView && it.adapter === sourceView.adapter) }
|
|
615
|
+
}
|
|
616
|
+
pageHeaderOffsets[sourceKey] = headerOffsetPx
|
|
577
617
|
detachPrimaryScrollObserver()
|
|
578
618
|
transitionSourcePage = selectedPage
|
|
579
619
|
transitionHeaderOffsets = pageHeaderOffsets.toMap()
|
|
@@ -689,7 +729,16 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
689
729
|
|
|
690
730
|
private fun layoutPagerIfRequested() {
|
|
691
731
|
val recycler = pager.getChildAt(0) as? RecyclerView ?: return
|
|
692
|
-
if (
|
|
732
|
+
if (
|
|
733
|
+
!shouldDrainCollapsiblePagerLayout(
|
|
734
|
+
recycler.isLayoutRequested,
|
|
735
|
+
recycler.isLayoutSuppressed,
|
|
736
|
+
recycler.isComputingLayout,
|
|
737
|
+
recycler.width,
|
|
738
|
+
recycler.height,
|
|
739
|
+
adapter.itemCount,
|
|
740
|
+
)
|
|
741
|
+
) return
|
|
693
742
|
// OneKey patch: a non-animated page command queues RecyclerView layout, but RN
|
|
694
743
|
// can stop that request after navigation. Drain it before showing the page.
|
|
695
744
|
recycler.measure(
|
|
@@ -712,6 +761,13 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
712
761
|
layout(left, top, right, bottom)
|
|
713
762
|
}
|
|
714
763
|
|
|
764
|
+
private fun isPagerRecyclerLayoutSuppressed(): Boolean =
|
|
765
|
+
shouldSuppressCollapsiblePagerRecyclerLayout(
|
|
766
|
+
(pager.getChildAt(0) as? RecyclerView)?.isLayoutSuppressed == true,
|
|
767
|
+
adapter.itemCount,
|
|
768
|
+
attachedPageCount(),
|
|
769
|
+
)
|
|
770
|
+
|
|
715
771
|
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
|
716
772
|
val width = MeasureSpec.getSize(widthMeasureSpec)
|
|
717
773
|
val height = MeasureSpec.getSize(heightMeasureSpec)
|
|
@@ -722,10 +778,12 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
722
778
|
return
|
|
723
779
|
}
|
|
724
780
|
val pagerHeight = height + headerHeightPx
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
781
|
+
if (!isPagerRecyclerLayoutSuppressed()) {
|
|
782
|
+
pager.measure(
|
|
783
|
+
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
|
784
|
+
MeasureSpec.makeMeasureSpec(pagerHeight, MeasureSpec.EXACTLY),
|
|
785
|
+
)
|
|
786
|
+
}
|
|
729
787
|
headerView?.measure(
|
|
730
788
|
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
|
731
789
|
MeasureSpec.makeMeasureSpec(headerHeightPx, MeasureSpec.EXACTLY),
|
|
@@ -737,8 +795,9 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
737
795
|
val nativeTabHeight = if (nativeTabBarView.visibility == View.VISIBLE) {
|
|
738
796
|
nativeTabBarView.preferredHeightPx()
|
|
739
797
|
} else 0
|
|
798
|
+
val tabReservedWidth = nativeTabReservationPx(width)
|
|
740
799
|
nativeTabBarView.measure(
|
|
741
|
-
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
|
800
|
+
MeasureSpec.makeMeasureSpec(width - tabReservedWidth, MeasureSpec.EXACTLY),
|
|
742
801
|
MeasureSpec.makeMeasureSpec(nativeTabHeight, MeasureSpec.EXACTLY),
|
|
743
802
|
)
|
|
744
803
|
val nativeSubHeaderHeight = if (nativeSubHeaderView.visibility == View.VISIBLE) {
|
|
@@ -757,7 +816,9 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
757
816
|
// RN ancestors allow overflow; keep translated header content inside this viewport.
|
|
758
817
|
clipBounds = Rect(0, 0, width, height)
|
|
759
818
|
if (width <= 0 || height <= 0) return
|
|
760
|
-
|
|
819
|
+
if (!isPagerRecyclerLayoutSuppressed()) {
|
|
820
|
+
pager.layout(0, 0, width, height + headerHeightPx)
|
|
821
|
+
}
|
|
761
822
|
headerView?.layout(0, 0, width, headerHeightPx)
|
|
762
823
|
stickyHeaderView?.layout(
|
|
763
824
|
0,
|
|
@@ -766,7 +827,12 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
766
827
|
headerHeightPx + stickyHeaderHeightPx,
|
|
767
828
|
)
|
|
768
829
|
val nativeTabHeight = nativeTabBarView.measuredHeight
|
|
769
|
-
|
|
830
|
+
val tabReservedWidth = nativeTabReservationPx(width)
|
|
831
|
+
val tabLeft = if (nativeTabBarView.layoutDirection == View.LAYOUT_DIRECTION_RTL) {
|
|
832
|
+
tabReservedWidth
|
|
833
|
+
} else 0
|
|
834
|
+
nativeTabBarView.layout(tabLeft, headerHeightPx, tabLeft + width - tabReservedWidth,
|
|
835
|
+
headerHeightPx + nativeTabHeight)
|
|
770
836
|
nativeSubHeaderView.layout(
|
|
771
837
|
0,
|
|
772
838
|
headerHeightPx + nativeTabHeight,
|
|
@@ -828,7 +894,7 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
828
894
|
}
|
|
829
895
|
if (!reached()) {
|
|
830
896
|
if (!isAttachedToWindow || !view.isAttachedToWindow || width <= 0 || height <= 0 ||
|
|
831
|
-
view.width <= 0 || view.height <= 0 || isInLayout || view.isInLayout || view.isComputingLayout ||
|
|
897
|
+
view.width <= 0 || view.height <= 0 || isInLayout || view.isInLayout || view.isLayoutSuppressed || view.isComputingLayout ||
|
|
832
898
|
pageKey(index) != key || primaryScrollableForPage(index) !== view || !isCurrentPageRestore(key, request)) return false
|
|
833
899
|
val previousSynthetic = syntheticPageRestore
|
|
834
900
|
syntheticPageRestore = true
|
|
@@ -987,13 +1053,33 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
987
1053
|
if (!settled) return
|
|
988
1054
|
pageOffsets[request.key] = 0
|
|
989
1055
|
if (view is RecyclerView) saveRecyclerReadingAnchor(request.key, view)
|
|
990
|
-
setHeaderOffset(
|
|
991
|
-
offsetDiag("pager-top-complete-
|
|
1056
|
+
setHeaderOffset(request.logicalOffset.coerceIn(0, headerHeightPx))
|
|
1057
|
+
offsetDiag("pager-top-complete-offset=${request.logicalOffset}-token=${request.token}", view, request.key)
|
|
992
1058
|
cancelPagerTop()
|
|
993
1059
|
}
|
|
994
1060
|
|
|
995
1061
|
internal fun scrollToTop(target: Int, animated: Boolean) {
|
|
996
|
-
if (target !in 0..1
|
|
1062
|
+
if (target !in 0..1) return
|
|
1063
|
+
scrollHeaderToOffset(if (target == 1) headerHeightPx else 0, animated)
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
internal fun scrollHeaderBy(delta: Double) {
|
|
1067
|
+
if (!delta.isFinite()) return
|
|
1068
|
+
val density = resources.displayMetrics.density.toDouble()
|
|
1069
|
+
val maxDip = headerHeightPx / density
|
|
1070
|
+
val deltaPx = (delta.coerceIn(-maxDip, maxDip) * density).roundToInt()
|
|
1071
|
+
scrollHeaderToOffset((headerOffsetPx.toLong() + deltaPx).coerceIn(0, headerHeightPx.toLong()).toInt(), false)
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
internal fun scrollHeaderToOffset(offset: Double) {
|
|
1075
|
+
if (!offset.isFinite()) return
|
|
1076
|
+
val density = resources.displayMetrics.density.toDouble()
|
|
1077
|
+
val targetPx = (offset.coerceIn(0.0, headerHeightPx / density) * density).roundToInt()
|
|
1078
|
+
scrollHeaderToOffset(targetPx, false)
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
private fun scrollHeaderToOffset(logicalOffset: Int, animated: Boolean) {
|
|
1082
|
+
if (pager.scrollState != ViewPager2.SCROLL_STATE_IDLE || transitionHeaderOffsets != null ||
|
|
997
1083
|
syntheticPageRestore || selectedPage !in 0 until adapter.itemCount || !isAttachedToWindow) return
|
|
998
1084
|
val view = observedRecyclerView ?: observedOwnedScroller ?: observedNativeScroller ?: return
|
|
999
1085
|
val key = currentPageKey()
|
|
@@ -1020,11 +1106,12 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
1020
1106
|
if (view is RecyclerView) deferredRecyclerGeometry.remove(view)
|
|
1021
1107
|
if (view is ScrollView) deferredScrollerGeometry.remove(view)
|
|
1022
1108
|
pageOffsets[key] = 0
|
|
1023
|
-
val
|
|
1109
|
+
val targetOffset = logicalOffset.coerceIn(0, headerHeightPx)
|
|
1110
|
+
val request = PagerTopRequest(view, key, selectedPage, attachmentGeneration, targetOffset, ++pagerTopToken)
|
|
1024
1111
|
pagerTopRequest = request
|
|
1025
1112
|
commandBodyKeys.add(key)
|
|
1026
|
-
setHeaderOffset(
|
|
1027
|
-
offsetDiag("pager-top-begin-
|
|
1113
|
+
setHeaderOffset(targetOffset)
|
|
1114
|
+
offsetDiag("pager-top-begin-offset=$targetOffset-animated=$animated-token=${request.token}", view, key)
|
|
1028
1115
|
dispatchingPagerTop = true
|
|
1029
1116
|
try {
|
|
1030
1117
|
when (view) {
|
|
@@ -1033,7 +1120,7 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
1033
1120
|
if (animated && !recyclerIsAtBodyTop(view)) view.smoothScrollToPosition(0)
|
|
1034
1121
|
else if (manager != null && manager.orientation == RecyclerView.VERTICAL) {
|
|
1035
1122
|
manager.scrollToPositionWithOffset(0, 0)
|
|
1036
|
-
if (view.width > 0 && view.height > 0 && !isInLayout && !view.isInLayout && !view.isComputingLayout) {
|
|
1123
|
+
if (view.width > 0 && view.height > 0 && !isInLayout && !view.isInLayout && !view.isLayoutSuppressed && !view.isComputingLayout) {
|
|
1037
1124
|
view.measure(MeasureSpec.makeMeasureSpec(view.width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(view.height, MeasureSpec.EXACTLY))
|
|
1038
1125
|
view.layout(view.left, view.top, view.right, view.bottom)
|
|
1039
1126
|
}
|
|
@@ -1617,6 +1704,7 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
1617
1704
|
)
|
|
1618
1705
|
}
|
|
1619
1706
|
updateRecyclerPaddingOwnership(recycler, original)
|
|
1707
|
+
val key = pageKey(pageIndex)
|
|
1620
1708
|
val topInset = headerHeightPx + stickyHeaderHeightPx
|
|
1621
1709
|
recycler.clipToPadding = false
|
|
1622
1710
|
val top = original.top + topInset
|
|
@@ -1628,17 +1716,28 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
1628
1716
|
// OneKey patch: LinearLayoutManager otherwise anchors children at their old
|
|
1629
1717
|
// screen coordinates when a page changes the sticky header height.
|
|
1630
1718
|
val manager = recycler.layoutManager as? LinearLayoutManager
|
|
1631
|
-
val
|
|
1632
|
-
|
|
1633
|
-
|
|
1719
|
+
val savedAnchor = if (nativeSmoothHeaderScrollEnabled && original.appliedTopInset == 0) {
|
|
1720
|
+
if (headerOffsetPx < headerHeightPx) {
|
|
1721
|
+
recycler.adapter?.let { RecyclerReadingAnchor(recycler, it, 0, 0) }
|
|
1722
|
+
} else {
|
|
1723
|
+
(if (transitionPageOffsets != null && pageIndex == transitionSourcePage) transitionSourceRecyclerAnchor
|
|
1724
|
+
else displayRecyclerAnchors[key] ?: recyclerReadingAnchors[key])
|
|
1725
|
+
?.takeIf { it.view.get() === recycler && it.adapter === recycler.adapter }
|
|
1726
|
+
}
|
|
1727
|
+
} else null
|
|
1728
|
+
val first = savedAnchor?.position
|
|
1729
|
+
?: manager?.takeIf { it.orientation == RecyclerView.VERTICAL }
|
|
1730
|
+
?.findFirstVisibleItemPosition()
|
|
1731
|
+
?: RecyclerView.NO_POSITION
|
|
1732
|
+
val anchorOffset = savedAnchor?.top ?: if (first != RecyclerView.NO_POSITION) {
|
|
1634
1733
|
manager?.findViewByPosition(first)?.let { manager.getDecoratedTop(it) - recycler.paddingTop }
|
|
1635
1734
|
} else null
|
|
1636
|
-
offsetDiag("recycler-inset-before-nextTop=$top-anchorPosition=$first-anchorOffset=$anchorOffset", recycler,
|
|
1735
|
+
offsetDiag("recycler-inset-before-nextTop=$top-anchorPosition=$first-anchorOffset=$anchorOffset", recycler, key, pageIndex)
|
|
1637
1736
|
recycler.setPadding(original.left, top, original.right, bottom)
|
|
1638
1737
|
if (anchorOffset != null) manager?.scrollToPositionWithOffset(first, anchorOffset)
|
|
1639
1738
|
// React Native can stop this nested requestLayout at its React-owned parent.
|
|
1640
1739
|
// Commit the changed inset before drawing instead of waiting for a data update.
|
|
1641
|
-
if (recycler.width > 0 && recycler.height > 0 && !recycler.isComputingLayout) {
|
|
1740
|
+
if (recycler.width > 0 && recycler.height > 0 && !recycler.isLayoutSuppressed && !recycler.isComputingLayout) {
|
|
1642
1741
|
recycler.forceLayout()
|
|
1643
1742
|
recycler.measure(
|
|
1644
1743
|
MeasureSpec.makeMeasureSpec(recycler.width, MeasureSpec.EXACTLY),
|
|
@@ -1647,9 +1746,8 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
1647
1746
|
recycler.layout(recycler.left, recycler.top, recycler.right, recycler.bottom)
|
|
1648
1747
|
}
|
|
1649
1748
|
}
|
|
1650
|
-
if (original.appliedTopInset != topInset) offsetDiag("recycler-inset-after-old=${original.appliedTopInset}-new=$topInset", recycler,
|
|
1749
|
+
if (original.appliedTopInset != topInset) offsetDiag("recycler-inset-after-old=${original.appliedTopInset}-new=$topInset", recycler, key, pageIndex)
|
|
1651
1750
|
original.appliedTopInset = topInset
|
|
1652
|
-
val key = pageKey(pageIndex)
|
|
1653
1751
|
val geometry = Triple(recycler.computeVerticalScrollRange(), recycler.computeVerticalScrollExtent(), recycler.childCount)
|
|
1654
1752
|
deferredRecyclerGeometry[recycler]?.let { deferred ->
|
|
1655
1753
|
if (geometry != deferred) {
|
|
@@ -2012,14 +2110,16 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
2012
2110
|
}
|
|
2013
2111
|
}
|
|
2014
2112
|
|
|
2015
|
-
private fun headerRegionAt(y: Float): String? {
|
|
2113
|
+
private fun headerRegionAt(x: Float, y: Float): String? {
|
|
2016
2114
|
val headerBottom = headerHeightPx - headerOffsetPx
|
|
2017
2115
|
val stickyBottom = headerHeightPx + stickyHeaderHeightPx - headerOffsetPx
|
|
2018
2116
|
return when {
|
|
2019
2117
|
y < 0 || y >= stickyBottom -> null
|
|
2020
2118
|
y < headerBottom -> "header"
|
|
2021
2119
|
y < headerBottom + nativeTabBarView.measuredHeight &&
|
|
2022
|
-
nativeTabBarView.visibility == View.VISIBLE ->
|
|
2120
|
+
nativeTabBarView.visibility == View.VISIBLE ->
|
|
2121
|
+
if (x >= nativeTabBarView.left && x < nativeTabBarView.right) "primary-tab"
|
|
2122
|
+
else "sticky-controls"
|
|
2023
2123
|
y < headerBottom + nativeTabBarView.measuredHeight + nativeSubHeaderView.measuredHeight &&
|
|
2024
2124
|
nativeSubHeaderView.visibility == View.VISIBLE -> "secondary-header"
|
|
2025
2125
|
else -> "sticky-controls"
|
|
@@ -2128,7 +2228,7 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
2128
2228
|
pressCancelled = false
|
|
2129
2229
|
nativeGestureStarted = false
|
|
2130
2230
|
nestedNativeGestureStarted = false
|
|
2131
|
-
headerTouchRegion = headerRegionAt(event.y) ?: "none"
|
|
2231
|
+
headerTouchRegion = headerRegionAt(event.x, event.y) ?: "none"
|
|
2132
2232
|
headerTouchActive = headerTouchRegion != "none"
|
|
2133
2233
|
if (!headerTouchActive) return super.dispatchTouchEvent(event)
|
|
2134
2234
|
headerDownX = event.x
|