@onekeyfe/react-native-pager-view 6.7.0-alpha.392 → 6.7.0-alpha.394
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/androidTest/java/com/reactnativepagerview/CollapsiblePagerTabReservationTest.kt +59 -0
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerHost.kt +16 -2
- package/android/src/main/java/com/reactnativepagerview/NativeScrollerScrollAwayInsets.kt +19 -0
- package/android/src/test/java/com/reactnativepagerview/NativeScrollerScrollAwayInsetsTest.kt +40 -0
- package/docs/SPEC.md +13 -2
- package/package.json +2 -2
package/android/src/androidTest/java/com/reactnativepagerview/CollapsiblePagerTabReservationTest.kt
CHANGED
|
@@ -12,6 +12,8 @@ import android.view.View
|
|
|
12
12
|
import android.view.ViewGroup
|
|
13
13
|
import android.widget.HorizontalScrollView
|
|
14
14
|
import android.widget.TextView
|
|
15
|
+
import androidx.recyclerview.widget.RecyclerView
|
|
16
|
+
import androidx.viewpager2.widget.ViewPager2
|
|
15
17
|
import com.facebook.react.uimanager.PointerEvents
|
|
16
18
|
import com.facebook.react.views.view.ReactViewGroup
|
|
17
19
|
import java.util.concurrent.atomic.AtomicInteger
|
|
@@ -244,4 +246,61 @@ class CollapsiblePagerTabReservationTest : InstrumentationTestCase() {
|
|
|
244
246
|
instrumentation.waitForIdleSync()
|
|
245
247
|
}
|
|
246
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
|
+
}
|
|
247
306
|
}
|
|
@@ -327,6 +327,7 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
327
327
|
pager.id = View.generateViewId()
|
|
328
328
|
pager.adapter = adapter
|
|
329
329
|
pager.isSaveEnabled = false
|
|
330
|
+
(pager.getChildAt(0) as? RecyclerView)?.itemAnimator = null
|
|
330
331
|
super.addView(
|
|
331
332
|
pager,
|
|
332
333
|
LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT),
|
|
@@ -708,7 +709,16 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
708
709
|
|
|
709
710
|
private fun layoutPagerIfRequested() {
|
|
710
711
|
val recycler = pager.getChildAt(0) as? RecyclerView ?: return
|
|
711
|
-
if (
|
|
712
|
+
if (
|
|
713
|
+
!shouldDrainCollapsiblePagerLayout(
|
|
714
|
+
recycler.isLayoutRequested,
|
|
715
|
+
recycler.isLayoutSuppressed,
|
|
716
|
+
recycler.isComputingLayout,
|
|
717
|
+
recycler.width,
|
|
718
|
+
recycler.height,
|
|
719
|
+
adapter.itemCount,
|
|
720
|
+
)
|
|
721
|
+
) return
|
|
712
722
|
// OneKey patch: a non-animated page command queues RecyclerView layout, but RN
|
|
713
723
|
// can stop that request after navigation. Drain it before showing the page.
|
|
714
724
|
recycler.measure(
|
|
@@ -732,7 +742,11 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
732
742
|
}
|
|
733
743
|
|
|
734
744
|
private fun isPagerRecyclerLayoutSuppressed(): Boolean =
|
|
735
|
-
(
|
|
745
|
+
shouldSuppressCollapsiblePagerRecyclerLayout(
|
|
746
|
+
(pager.getChildAt(0) as? RecyclerView)?.isLayoutSuppressed == true,
|
|
747
|
+
adapter.itemCount,
|
|
748
|
+
attachedPageCount(),
|
|
749
|
+
)
|
|
736
750
|
|
|
737
751
|
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
|
738
752
|
val width = MeasureSpec.getSize(widthMeasureSpec)
|
|
@@ -39,3 +39,22 @@ internal class NativeRefreshForegroundAlpha(initialAlpha: Float) {
|
|
|
39
39
|
return lastApplied
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
+
|
|
43
|
+
// Skip ViewPager2 RecyclerView layout when explicitly suppressed or when all React page
|
|
44
|
+
// children have been removed on unmount while rendered ViewHolder views remain attached.
|
|
45
|
+
internal fun shouldSuppressCollapsiblePagerRecyclerLayout(
|
|
46
|
+
isLayoutSuppressed: Boolean,
|
|
47
|
+
adapterItemCount: Int,
|
|
48
|
+
attachedPageCount: Int,
|
|
49
|
+
): Boolean = isLayoutSuppressed || (adapterItemCount <= 0 && attachedPageCount > 0)
|
|
50
|
+
|
|
51
|
+
internal fun shouldDrainCollapsiblePagerLayout(
|
|
52
|
+
isLayoutRequested: Boolean,
|
|
53
|
+
isLayoutSuppressed: Boolean,
|
|
54
|
+
isComputingLayout: Boolean,
|
|
55
|
+
width: Int,
|
|
56
|
+
height: Int,
|
|
57
|
+
adapterItemCount: Int,
|
|
58
|
+
): Boolean = adapterItemCount > 0 && isLayoutRequested && !isLayoutSuppressed &&
|
|
59
|
+
!isComputingLayout && width > 0 && height > 0
|
|
60
|
+
|
package/android/src/test/java/com/reactnativepagerview/NativeScrollerScrollAwayInsetsTest.kt
CHANGED
|
@@ -72,4 +72,44 @@ fun main() {
|
|
|
72
72
|
check(nativeRefreshForegroundTop(37 + 230 + 64, 294f) == 37f)
|
|
73
73
|
check(nativeRefreshForegroundTop(-40 + 294, 294f) == -40f)
|
|
74
74
|
println("Refresh foreground policy: repeat, owner transfer/release, external alpha, transparent baseline, caller offset, and combined nested inset passed")
|
|
75
|
+
|
|
76
|
+
// Initial mount before any page ViewHolder is attached still measures ViewPager2.
|
|
77
|
+
check(!shouldSuppressCollapsiblePagerRecyclerLayout(isLayoutSuppressed = false, adapterItemCount = 0, attachedPageCount = 0))
|
|
78
|
+
// Unmount removes all adapter pages while rendered ViewHolder views remain attached.
|
|
79
|
+
check(shouldSuppressCollapsiblePagerRecyclerLayout(isLayoutSuppressed = false, adapterItemCount = 0, attachedPageCount = 1))
|
|
80
|
+
check(shouldSuppressCollapsiblePagerRecyclerLayout(isLayoutSuppressed = true, adapterItemCount = 2, attachedPageCount = 1))
|
|
81
|
+
// Re-adding pages resumes ViewPager2 layout.
|
|
82
|
+
check(!shouldSuppressCollapsiblePagerRecyclerLayout(isLayoutSuppressed = false, adapterItemCount = 1, attachedPageCount = 1))
|
|
83
|
+
|
|
84
|
+
check(
|
|
85
|
+
!shouldDrainCollapsiblePagerLayout(
|
|
86
|
+
isLayoutRequested = true,
|
|
87
|
+
isLayoutSuppressed = false,
|
|
88
|
+
isComputingLayout = false,
|
|
89
|
+
width = 1080,
|
|
90
|
+
height = 2400,
|
|
91
|
+
adapterItemCount = 0,
|
|
92
|
+
),
|
|
93
|
+
)
|
|
94
|
+
check(
|
|
95
|
+
shouldDrainCollapsiblePagerLayout(
|
|
96
|
+
isLayoutRequested = true,
|
|
97
|
+
isLayoutSuppressed = false,
|
|
98
|
+
isComputingLayout = false,
|
|
99
|
+
width = 1080,
|
|
100
|
+
height = 2400,
|
|
101
|
+
adapterItemCount = 2,
|
|
102
|
+
),
|
|
103
|
+
)
|
|
104
|
+
check(
|
|
105
|
+
!shouldDrainCollapsiblePagerLayout(
|
|
106
|
+
isLayoutRequested = true,
|
|
107
|
+
isLayoutSuppressed = true,
|
|
108
|
+
isComputingLayout = false,
|
|
109
|
+
width = 1080,
|
|
110
|
+
height = 2400,
|
|
111
|
+
adapterItemCount = 2,
|
|
112
|
+
),
|
|
113
|
+
)
|
|
114
|
+
println("CollapsiblePager unmount layout guard: initial mount, unmount retention, suppression, and re-add passed")
|
|
75
115
|
}
|
package/docs/SPEC.md
CHANGED
|
@@ -357,8 +357,19 @@ Status: implemented locally; unreleased. On the Android UI thread, during a
|
|
|
357
357
|
screen removal transition,
|
|
358
358
|
NativeList and CollapsiblePager layout entry points respect the RecyclerView
|
|
359
359
|
suppression state established by the screen-transition owner. The owner restores
|
|
360
|
-
the original state when its last transition ownership ends.
|
|
361
|
-
|
|
360
|
+
the original state when its last transition ownership ends. In addition,
|
|
361
|
+
`CollapsiblePagerHost` disables `DefaultItemAnimator` on its internal
|
|
362
|
+
`ViewPager2` `RecyclerView` (`itemAnimator = null`) and skips pre-draw
|
|
363
|
+
(`layoutPagerIfRequested`) and host-driven `ViewPager2` layout passes when all
|
|
364
|
+
React page children have been removed from the adapter (`adapter.itemCount == 0`)
|
|
365
|
+
while previously rendered page `ViewHolder` views remain attached
|
|
366
|
+
(`attachedPageCount() > 0`), matching `PagerViewViewManagerImpl`'s unmount
|
|
367
|
+
guard. Initial empty measurement before any page is attached
|
|
368
|
+
(`attachedPageCount() == 0`) and subsequent page insertions (`adapter.itemCount > 0`)
|
|
369
|
+
still measure and lay out normally. This prevents unmount-time empty-adapter
|
|
370
|
+
layout passes from clearing visible exit frames or recycling attached
|
|
371
|
+
`ViewHolder` views held in `mDisappearingChildren` by `ViewGroup.startViewTransition`.
|
|
372
|
+
This adds no public API and does not change Web or iOS behavior.
|
|
362
373
|
|
|
363
374
|
If Fabric removes the current NativeScroller React child while its origin-frame
|
|
364
375
|
transition remains active, Android retains the already displayed native extent
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/react-native-pager-view",
|
|
3
|
-
"version": "6.7.0-alpha.
|
|
3
|
+
"version": "6.7.0-alpha.394",
|
|
4
4
|
"description": "React Native wrapper for Android and iOS ViewPager",
|
|
5
5
|
"source": "./src/index.tsx",
|
|
6
6
|
"main": "./lib/module/index.js",
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
"typescript": "^5.9.2"
|
|
74
74
|
},
|
|
75
75
|
"peerDependencies": {
|
|
76
|
-
"@onekeyfe/react-native-native-logger": "6.7.0-alpha.
|
|
76
|
+
"@onekeyfe/react-native-native-logger": "6.7.0-alpha.394",
|
|
77
77
|
"react": "*",
|
|
78
78
|
"react-native": "*"
|
|
79
79
|
},
|