@onekeyfe/react-native-pager-view 3.0.173 → 3.0.175
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/src/main/java/com/reactnativepagerview/CollapsiblePagerHost.kt +860 -25
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerViewManager.kt +17 -0
- package/android/src/main/java/com/reactnativepagerview/NativeScrollerView.kt +16 -0
- package/android/src/main/java/com/reactnativepagerview/event/CollapsibleStateChangedEvent.kt +4 -0
- package/android/src/main/res/values/native_scroll_coordinator_ids.xml +2 -0
- package/android/src/test/java/com/reactnativepagerview/NativeScrollerPendingRestoreTest.kt +11 -0
- package/docs/SPEC.md +65 -1
- package/ios/RNCCollapsiblePagerViewComponentView.mm +616 -30
- package/ios/RNCNativeScrollerComponentView.h +1 -0
- package/ios/RNCNativeScrollerComponentView.mm +48 -0
- package/ios/tests/RNCCollapsiblePagerFirstFrameTests.mm +332 -0
- package/ios/tests/RNCCollapsiblePagerRefreshControlTests.mm +14 -5
- package/lib/module/CollapsiblePagerView.js +6 -0
- package/lib/module/CollapsiblePagerView.web.js +81 -0
- package/lib/module/CollapsiblePagerViewNativeComponent.ts +8 -0
- package/lib/typescript/src/CollapsiblePagerView.d.ts +6 -0
- package/lib/typescript/src/CollapsiblePagerView.web.d.ts +11 -0
- package/lib/typescript/src/CollapsiblePagerViewNativeComponent.d.ts +3 -0
- package/package.json +2 -2
- package/src/CollapsiblePagerView.tsx +18 -0
- package/src/CollapsiblePagerView.web.tsx +170 -36
- package/src/CollapsiblePagerViewNativeComponent.ts +8 -0
|
@@ -43,6 +43,7 @@ class CollapsiblePagerViewManager : ViewGroupManager<CollapsiblePagerHost>(),
|
|
|
43
43
|
)
|
|
44
44
|
host.pager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
|
|
45
45
|
override fun onPageScrolled(position: Int, offset: Float, offsetPixels: Int) {
|
|
46
|
+
host.onPagerScrolled(position, offset)
|
|
46
47
|
dispatch(reactContext, host, PageScrollEvent(host.id, position, offset))
|
|
47
48
|
}
|
|
48
49
|
|
|
@@ -55,6 +56,7 @@ class CollapsiblePagerViewManager : ViewGroupManager<CollapsiblePagerHost>(),
|
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
override fun onPageScrollStateChanged(state: Int) {
|
|
59
|
+
host.onPagerScrollStateChanged(state)
|
|
58
60
|
val value = when (state) {
|
|
59
61
|
ViewPager2.SCROLL_STATE_IDLE -> "idle"
|
|
60
62
|
ViewPager2.SCROLL_STATE_DRAGGING -> "dragging"
|
|
@@ -92,6 +94,9 @@ class CollapsiblePagerViewManager : ViewGroupManager<CollapsiblePagerHost>(),
|
|
|
92
94
|
),
|
|
93
95
|
)
|
|
94
96
|
}
|
|
97
|
+
host.onHeaderSettledStateChanged = {
|
|
98
|
+
dispatchDiagnostics(reactContext, host, "header-settled-state")
|
|
99
|
+
}
|
|
95
100
|
host.onHeaderOffsetChanged = {
|
|
96
101
|
if (!host.pager.isFakeDragging && host.pager.scrollState == ViewPager2.SCROLL_STATE_IDLE) {
|
|
97
102
|
dispatchDiagnostics(reactContext, host, "vertical-idle")
|
|
@@ -125,6 +130,8 @@ class CollapsiblePagerViewManager : ViewGroupManager<CollapsiblePagerHost>(),
|
|
|
125
130
|
host.observedScrollableCount(),
|
|
126
131
|
host.retainedPages,
|
|
127
132
|
reason,
|
|
133
|
+
host.headerStatePageKey(),
|
|
134
|
+
host.headerSettledAtTop,
|
|
128
135
|
),
|
|
129
136
|
)
|
|
130
137
|
}
|
|
@@ -322,15 +329,25 @@ class CollapsiblePagerViewManager : ViewGroupManager<CollapsiblePagerHost>(),
|
|
|
322
329
|
delegate.receiveCommand(root, commandId, args)
|
|
323
330
|
}
|
|
324
331
|
|
|
332
|
+
override fun scrollToTop(view: CollapsiblePagerHost?, target: Int, animated: Boolean) {
|
|
333
|
+
view?.scrollToTop(target, animated)
|
|
334
|
+
}
|
|
335
|
+
|
|
325
336
|
override fun setPage(view: CollapsiblePagerHost?, selectedPage: Int) {
|
|
326
337
|
if (view != null && selectedPage in 0 until view.adapter.itemCount) {
|
|
338
|
+
view.beginPageTransition(selectedPage)
|
|
327
339
|
view.pager.setCurrentItem(selectedPage, true)
|
|
340
|
+
if (view.pager.scrollState == ViewPager2.SCROLL_STATE_IDLE) {
|
|
341
|
+
view.onPagerScrollStateChanged(ViewPager2.SCROLL_STATE_IDLE)
|
|
342
|
+
}
|
|
328
343
|
}
|
|
329
344
|
}
|
|
330
345
|
|
|
331
346
|
override fun setPageWithoutAnimation(view: CollapsiblePagerHost?, selectedPage: Int) {
|
|
332
347
|
if (view != null && selectedPage in 0 until view.adapter.itemCount) {
|
|
348
|
+
view.beginPageTransition(selectedPage)
|
|
333
349
|
view.pager.setCurrentItem(selectedPage, false)
|
|
350
|
+
view.onPagerScrollStateChanged(ViewPager2.SCROLL_STATE_IDLE)
|
|
334
351
|
}
|
|
335
352
|
}
|
|
336
353
|
|
|
@@ -255,6 +255,8 @@ class NativeScrollerView(context: Context) : FrameLayout(context) {
|
|
|
255
255
|
|
|
256
256
|
internal fun cancelPendingScrollRestore() = pendingRestore.cancel()
|
|
257
257
|
|
|
258
|
+
internal fun hasPendingScrollRestore(): Boolean = pendingRestore.target != null
|
|
259
|
+
|
|
258
260
|
internal fun setRefreshOffset(value: Int) {
|
|
259
261
|
refreshOffset = value
|
|
260
262
|
applyRefreshOffset()
|
|
@@ -293,11 +295,22 @@ class NativeScrollerView(context: Context) : FrameLayout(context) {
|
|
|
293
295
|
}
|
|
294
296
|
|
|
295
297
|
internal fun scrollToPosition(x: Int, y: Int, animated: Boolean) {
|
|
298
|
+
notifyScrollCommand(0, y <= 0)
|
|
296
299
|
viewport.stopMotion()
|
|
297
300
|
pendingRestore.cancel()
|
|
301
|
+
notifyScrollCommand(1, y <= 0)
|
|
298
302
|
if (animated) viewport.scrollToAnimated(y)
|
|
299
303
|
else viewport.scrollTo(0, y.coerceAtLeast(0))
|
|
300
304
|
emitScroll(force = true)
|
|
305
|
+
if (!viewport.isReadingMotionActive()) notifyScrollCommand(2, y <= 0)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
internal fun notifyScrollCommand(phase: Int, explicitTop: Boolean = false) {
|
|
309
|
+
var owner = parent
|
|
310
|
+
while (owner is View) {
|
|
311
|
+
if (owner is CollapsiblePagerHost) owner.acceptObservedScrollCommand(viewport, explicitTop, phase)
|
|
312
|
+
owner = owner.parent
|
|
313
|
+
}
|
|
301
314
|
}
|
|
302
315
|
|
|
303
316
|
internal fun scrollToEnd(animated: Boolean) = scrollToPosition(
|
|
@@ -587,6 +600,8 @@ internal class NativeScrollerViewport(context: Context, internal val host: Nativ
|
|
|
587
600
|
super.fling(velocityY)
|
|
588
601
|
}
|
|
589
602
|
|
|
603
|
+
internal fun isReadingMotionActive() = dragging || momentum
|
|
604
|
+
|
|
590
605
|
internal fun scrollToAnimated(y: Int) {
|
|
591
606
|
val child = getChildAt(0) ?: return
|
|
592
607
|
val margins = child.layoutParams as FrameLayout.LayoutParams
|
|
@@ -637,6 +652,7 @@ internal class NativeScrollerViewport(context: Context, internal val host: Nativ
|
|
|
637
652
|
private fun finishMomentum() {
|
|
638
653
|
if (!momentum) return
|
|
639
654
|
momentum = false
|
|
655
|
+
host.notifyScrollCommand(2)
|
|
640
656
|
host.emitScroll(force = true)
|
|
641
657
|
host.emitLifecycle("topMomentumScrollEnd")
|
|
642
658
|
}
|
package/android/src/main/java/com/reactnativepagerview/event/CollapsibleStateChangedEvent.kt
CHANGED
|
@@ -14,6 +14,8 @@ class CollapsibleStateChangedEvent(
|
|
|
14
14
|
private val observedScrollableCount: Int,
|
|
15
15
|
private val retainedPages: String,
|
|
16
16
|
private val reason: String,
|
|
17
|
+
private val pageKey: String,
|
|
18
|
+
private val headerSettledAtTop: Boolean,
|
|
17
19
|
) : Event<CollapsibleStateChangedEvent>(viewTag) {
|
|
18
20
|
override fun getEventName() = EVENT_NAME
|
|
19
21
|
|
|
@@ -31,6 +33,8 @@ class CollapsibleStateChangedEvent(
|
|
|
31
33
|
putInt("observedScrollableCount", observedScrollableCount)
|
|
32
34
|
putString("retainedPages", retainedPages)
|
|
33
35
|
putString("reason", reason)
|
|
36
|
+
putString("pageKey", pageKey)
|
|
37
|
+
putBoolean("headerSettledAtTop", headerSettledAtTop)
|
|
34
38
|
}
|
|
35
39
|
|
|
36
40
|
companion object {
|
|
@@ -2,4 +2,6 @@
|
|
|
2
2
|
<item name="onekey_native_scroll_coordinator_consumed_offsets" type="id" />
|
|
3
3
|
<item name="onekey_native_scroll_coordinator_offset_listener" type="id" />
|
|
4
4
|
<item name="onekey_native_scroll_coordinator_refresh_inset" type="id" />
|
|
5
|
+
<item name="onekey_native_scroll_coordinator_command_listener" type="id" />
|
|
6
|
+
<item name="onekey_native_scroll_coordinator_command_cancel_listener" type="id" />
|
|
5
7
|
</resources>
|
|
@@ -27,5 +27,16 @@ fun main() {
|
|
|
27
27
|
restore.cancel()
|
|
28
28
|
check(restore.target == null)
|
|
29
29
|
check(restore.anchor(120) == 120)
|
|
30
|
+
// Cancellation clears the old target without requiring the reached offset to change.
|
|
31
|
+
restore.request(target = 640, reached = 120)
|
|
32
|
+
check(restore.anchor(120) == 640)
|
|
33
|
+
restore.cancel()
|
|
34
|
+
check(restore.target == null)
|
|
35
|
+
check(restore.anchor(120) == 120)
|
|
36
|
+
// A fresh request owns its target after cancellation.
|
|
37
|
+
restore.request(target = 320, reached = 120)
|
|
38
|
+
check(restore.target == 320)
|
|
39
|
+
restore.settle(reached = 320)
|
|
40
|
+
check(restore.target == null)
|
|
30
41
|
println("Native scroller pending restore: clamp retry, partial growth, settle and cancel passed")
|
|
31
42
|
}
|
package/docs/SPEC.md
CHANGED
|
@@ -3,6 +3,60 @@
|
|
|
3
3
|
Existing PagerView and CollapsiblePagerView APIs are documented in README.md.
|
|
4
4
|
The sections below define native coordination boundaries without new React props.
|
|
5
5
|
|
|
6
|
+
## Smooth shared-header and per-page body memory
|
|
7
|
+
|
|
8
|
+
With nativeSmoothHeaderScrollEnabled, the header collapse C is shared across all
|
|
9
|
+
pages and the available collapse distance is D. Each page separately remembers
|
|
10
|
+
its sticky-body reading distance B (unvisited pages default to zero). Outside
|
|
11
|
+
pull overscroll, the rendered whole-page distance is Q = C + effectiveB.
|
|
12
|
+
B denotes the saved reading position. For an owned vertical LinearLayoutManager
|
|
13
|
+
RecyclerView, restoration uses the first visible adapter position and its
|
|
14
|
+
decorated top relative to padding; scrollbar offset estimates MUST NOT decide
|
|
15
|
+
completion of that anchor restoration.
|
|
16
|
+
When C = D, selection restores the destination's saved B. When C < D, effectiveB
|
|
17
|
+
is zero while the previous saved B remains suspended. Horizontal selection by
|
|
18
|
+
tap, swipe or an existing programmatic command MUST preserve C throughout.
|
|
19
|
+
|
|
20
|
+
Alignment, prefetch, replacement, inset changes and pending restoration MUST NOT
|
|
21
|
+
commit new B. Actual consumed vertical scrolling takes over the visible body
|
|
22
|
+
position; reaching full collapse during that gesture MUST NOT resurrect the old
|
|
23
|
+
suspended B. Selecting a page anew at full collapse may restore its saved B.
|
|
24
|
+
Valid explicit native scroll commands, including same-offset commands, cancel
|
|
25
|
+
only their current owned restoration and take over body memory. Invalid commands
|
|
26
|
+
and stale owners do not affect another page. Existing command coordinate systems
|
|
27
|
+
are unchanged; in particular NativeScroller iOS uses physical offsets whereas
|
|
28
|
+
NativeList offset commands exclude the content-start inset.
|
|
29
|
+
|
|
30
|
+
Transitions use immutable destination memory and the source's actual displayed
|
|
31
|
+
position. Cancellation restores that source presentation, even when a cached B
|
|
32
|
+
was temporarily unreachable. Loading or zero geometry preserves pending intent;
|
|
33
|
+
a genuinely short page clamps its display without silently replacing saved B.
|
|
34
|
+
Cancelling a horizontal gesture invalidates queued callbacks, but preserves
|
|
35
|
+
unreachable reading intent until real vertical consumption or a valid command
|
|
36
|
+
takes over; later usable geometry may resume it.
|
|
37
|
+
Late callbacks cannot revive cancelled restoration or erase a newer request.
|
|
38
|
+
Observers and commands must match page key, view identity and generation.
|
|
39
|
+
Removed or recycled owners cannot apply callbacks to another page; recycling
|
|
40
|
+
resets owned memory.
|
|
41
|
+
Legacy mode and pagers without a collapsible header retain their existing
|
|
42
|
+
behavior. Vertical scrolling, refresh, snapping and repeated-current-tab behavior
|
|
43
|
+
are unchanged. This state belongs to native UI instances, with no new React prop
|
|
44
|
+
or per-frame JavaScript synchronization.
|
|
45
|
+
|
|
46
|
+
## Current-page top commands
|
|
47
|
+
|
|
48
|
+
`scrollToPageTop(options?: Readonly<{ animated?: boolean }>)` restores the current
|
|
49
|
+
page to C = 0 and B = 0. `scrollToStickyTop(options?: Readonly<{ animated?: boolean }>)`
|
|
50
|
+
restores it to C = D and B = 0, including from a partially expanded header.
|
|
51
|
+
Animation defaults to true. Neither command clears another page's memory.
|
|
52
|
+
Both commands require a stable, attached current owner; horizontal transitions
|
|
53
|
+
or missing owners reject them rather than queueing a command for another page.
|
|
54
|
+
Acceptance cancels older owned list commands and pending restoration before the
|
|
55
|
+
new target is established. Same-target commands also take over old reading
|
|
56
|
+
intent. Real vertical consumption, a newer valid command, or owner detachment
|
|
57
|
+
releases the target. Existing NativeList and NativeScroller public coordinates
|
|
58
|
+
are unchanged. The same page-top/sticky-top destinations are available without smooth-header mode.
|
|
59
|
+
|
|
6
60
|
## Independent NativeScroller migration
|
|
7
61
|
|
|
8
62
|
Status: implemented in source. Both native builds pass. Android focused emulator
|
|
@@ -454,7 +508,8 @@ When restoring a retained page's cached vertical offset, the pager limits only
|
|
|
454
508
|
the upper bound to the current content height, viewport and applied insets. A
|
|
455
509
|
cache captured before rows were removed MUST NOT restore an offset below the new
|
|
456
510
|
content bottom, including when removal occurs during a page transition. Valid
|
|
457
|
-
deep offsets and
|
|
511
|
+
deep offsets and pull-overscroll behavior are preserved; smooth-header selection
|
|
512
|
+
follows the shared-header and per-page body-memory contract above.
|
|
458
513
|
This check runs only on explicit restoration, not on scroll frames; UIKit pull
|
|
459
514
|
overscroll and refresh settlement retain their existing ownership. An unmeasured
|
|
460
515
|
zero-height viewport defers this upper-bound limit to a later restoration.
|
|
@@ -662,3 +717,12 @@ cases additionally verified recorded refresh, header, restoration and gesture
|
|
|
662
717
|
paths; they do not establish every possible sequence or runtime acceptance of a
|
|
663
718
|
newly installed module release. Physical-device feel and semantic accessibility
|
|
664
719
|
activation remain unverified.
|
|
720
|
+
|
|
721
|
+
|
|
722
|
+
### Native Header stability snapshot
|
|
723
|
+
|
|
724
|
+
`onCollapsibleStateChanged` retains its existing diagnostic fields and reasons and adds native-only `pageKey: string` and `headerSettledAtTop: boolean`. The key is the actual mounted React child key, not an application tab identifier. Unattached, unknown, replaced or transitioning owners MUST report false. A mounted current owner emits an initial identity snapshot without requiring a first user scroll.
|
|
725
|
+
|
|
726
|
+
The Header is settled only when C is at its expanded top, the current attached view/key/index/generation still matches, and there is no actual drag, deceleration, scrolling command or horizontal transition. Offset/inset/content/header geometry MUST remain stable for 150ms before true is emitted. Motion or geometry changes revoke true promptly. Deferred reading memory alone MUST NOT be treated as active motion. Pull-to-refresh may settle at a normalized negative offset after drag, bounce and inset geometry stop; network refreshing alone is not a motion gate. Checks stop for collapsed, detached and recycled owners; delayed callbacks revalidate their captured identity. State changes are deduplicated; identity changes still publish an unknown/false snapshot.
|
|
727
|
+
|
|
728
|
+
The native Home Spotlight adapter consumes this snapshot in the main UI runtime and keeps unknown states paused. It MUST use the mounted page-key mapping and reject old page/source callbacks. Native snapshots already include the 150ms stabilization delay: consumers MUST NOT add another such delay. Legacy Tabs keep their existing normalized-offset and debounce behavior. Anchor measurement, guide qualification and the existing display delay remain independent gates.
|