@onekeyfe/react-native-pager-view 3.0.158 → 3.0.159
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 +57 -4
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerHost.kt +379 -31
- package/android/src/main/java/com/reactnativepagerview/NativeScrollCoordinatorContributions.kt +15 -0
- package/android/src/main/java/com/reactnativepagerview/NativeScrollerPendingRestore.kt +26 -0
- package/android/src/main/java/com/reactnativepagerview/NativeScrollerScrollAwayInsets.kt +41 -0
- package/android/src/main/java/com/reactnativepagerview/NativeScrollerView.kt +600 -0
- package/android/src/main/java/com/reactnativepagerview/NativeScrollerViewManager.kt +145 -0
- package/android/src/main/java/com/reactnativepagerview/PagerViewViewPackage.kt +1 -1
- package/android/src/main/jni/CMakeLists.txt +19 -0
- package/android/src/main/jni/pagerview.h +11 -0
- package/android/src/main/res/values/native_scroll_coordinator_ids.xml +5 -0
- package/android/src/test/java/com/reactnativepagerview/NativeScrollCoordinatorContributionsTest.kt +26 -0
- package/android/src/test/java/com/reactnativepagerview/NativeScrollerPendingRestoreTest.kt +31 -0
- package/android/src/test/java/com/reactnativepagerview/NativeScrollerScrollAwayInsetsTest.kt +75 -0
- package/common/cpp/react/renderer/components/pagerview/RNCNativeScrollerComponentDescriptor.h +8 -0
- package/common/cpp/react/renderer/components/pagerview/RNCNativeScrollerShadowNode.cpp +15 -0
- package/common/cpp/react/renderer/components/pagerview/RNCNativeScrollerShadowNode.h +23 -0
- package/common/cpp/react/renderer/components/pagerview/RNCNativeScrollerState.h +33 -0
- package/docs/SPEC.md +451 -0
- package/ios/RNCCollapsiblePagerReleasedStatePolicy.h +8 -0
- package/ios/RNCCollapsiblePagerReleasedStatePolicy.m +13 -0
- package/ios/RNCCollapsiblePagerViewComponentView.mm +420 -2
- package/ios/RNCNativeScrollerComponentView.h +17 -0
- package/ios/RNCNativeScrollerComponentView.mm +479 -0
- package/ios/tests/RNCCollapsiblePagerHitTestTests.mm +80 -0
- package/ios/tests/RNCCollapsiblePagerPressCancellationTests.mm +788 -0
- package/ios/tests/RNCCollapsiblePagerPressCancellationWiringTests.mm +109 -0
- package/ios/tests/RNCCollapsiblePagerRefreshControlTests.mm +214 -0
- package/ios/tests/RNCCollapsiblePagerReleasedStatePolicyTests.mm +23 -0
- package/ios/tests/RNCNativeScrollerTests.mm +252 -0
- package/lib/module/CollapsiblePagerView.js +10 -1
- package/lib/module/NativeScroller.js +171 -11
- package/lib/module/NativeScrollerNativeComponent.ts +101 -0
- package/lib/typescript/src/CollapsiblePagerView.d.ts +3 -3
- package/lib/typescript/src/NativeScroller.d.ts +15 -7
- package/lib/typescript/src/NativeScrollerNativeComponent.d.ts +79 -0
- package/package.json +8 -4
- package/react-native-pager-view.podspec +2 -1
- package/react-native.config.js +15 -0
- package/src/CollapsiblePagerView.tsx +14 -4
- package/src/NativeScroller.tsx +291 -14
- package/src/NativeScrollerNativeComponent.ts +101 -0
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
package com.reactnativepagerview
|
|
2
2
|
|
|
3
3
|
import android.content.Context
|
|
4
|
+
import android.graphics.Canvas
|
|
5
|
+
import android.graphics.Matrix
|
|
4
6
|
import android.graphics.Rect
|
|
5
7
|
import android.os.SystemClock
|
|
6
8
|
import android.view.MotionEvent
|
|
@@ -9,6 +11,7 @@ import android.view.ViewConfiguration
|
|
|
9
11
|
import android.view.ViewGroup
|
|
10
12
|
import android.view.ViewTreeObserver
|
|
11
13
|
import android.widget.HorizontalScrollView
|
|
14
|
+
import android.widget.ImageView
|
|
12
15
|
import android.widget.ScrollView
|
|
13
16
|
// OneKey patch: FrameLayout is inherited through NestedScrollableHost.
|
|
14
17
|
// import android.widget.FrameLayout
|
|
@@ -17,10 +20,14 @@ import androidx.core.view.NestedScrollingParentHelper
|
|
|
17
20
|
import androidx.core.view.ViewCompat
|
|
18
21
|
import androidx.recyclerview.widget.LinearLayoutManager
|
|
19
22
|
import androidx.recyclerview.widget.RecyclerView
|
|
23
|
+
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
|
|
24
|
+
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
|
20
25
|
import androidx.viewpager2.widget.ViewPager2
|
|
21
26
|
import com.facebook.react.R as ReactR
|
|
22
27
|
import com.facebook.react.uimanager.events.NativeGestureUtil
|
|
28
|
+
import com.facebook.react.views.scroll.ReactScrollView
|
|
23
29
|
import com.margelo.nitro.nativelogger.OneKeyLog
|
|
30
|
+
import java.lang.ref.WeakReference
|
|
24
31
|
import java.util.WeakHashMap
|
|
25
32
|
import kotlin.math.max
|
|
26
33
|
import kotlin.math.min
|
|
@@ -115,11 +122,24 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
115
122
|
private val restoredRecyclerKeys = WeakHashMap<RecyclerView, String>()
|
|
116
123
|
private val originalScrollerPadding = WeakHashMap<ScrollView, ScrollerPadding>()
|
|
117
124
|
private val restoredScrollerKeys = WeakHashMap<ScrollView, String>()
|
|
125
|
+
private val ownedScrollerKeys = WeakHashMap<NativeScrollerViewport, String>()
|
|
126
|
+
private var foregroundRefreshIndicator: WeakReference<ImageView>? = null
|
|
127
|
+
private var refreshForegroundWasVisible = false
|
|
128
|
+
private val refreshForegroundMatrix = Matrix()
|
|
129
|
+
private val refreshForegroundOrigin = FloatArray(2)
|
|
130
|
+
private val refreshForegroundAncestors = ArrayList<View>()
|
|
131
|
+
private var refreshForegroundAncestorAlpha = 1f
|
|
118
132
|
private val pageOffsets = HashMap<String, Int>()
|
|
119
133
|
private var observedRecyclerView: RecyclerView? = null
|
|
120
134
|
private var observedScrollListener: RecyclerView.OnScrollListener? = null
|
|
135
|
+
private var observedOwnedScroller: NativeScrollerViewport? = null
|
|
136
|
+
private var observedOwnedScrollerKey: String? = null
|
|
137
|
+
private var observedOwnedScrollerListener: ViewTreeObserver.OnScrollChangedListener? = null
|
|
138
|
+
private var observedOwnedScrollerTreeObserver: ViewTreeObserver? = null
|
|
121
139
|
private var observedNativeScroller: ScrollView? = null
|
|
122
140
|
private var observedNativeScrollerListener: ViewTreeObserver.OnScrollChangedListener? = null
|
|
141
|
+
private var observedNativeScrollerTreeObserver: ViewTreeObserver? = null
|
|
142
|
+
private var observedPageRemoved = false
|
|
123
143
|
private var attachmentGeneration = 0
|
|
124
144
|
private val touchSlopPx = ViewConfiguration.get(context).scaledTouchSlop
|
|
125
145
|
private val hostIdentity = System.identityHashCode(this)
|
|
@@ -133,6 +153,7 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
133
153
|
private var headerDownEvent: MotionEvent? = null
|
|
134
154
|
private var headerHasHorizontalChild = false
|
|
135
155
|
private var forwardedScrollable: View? = null
|
|
156
|
+
private var pendingRefreshPull: WeakReference<SwipeRefreshLayout>? = null
|
|
136
157
|
private var nativeGestureStarted = false
|
|
137
158
|
private var pressCancelled = false
|
|
138
159
|
private val pageContentLayoutListener = ViewTreeObserver.OnPreDrawListener {
|
|
@@ -308,6 +329,7 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
308
329
|
}
|
|
309
330
|
|
|
310
331
|
fun removeReactChild(child: View) {
|
|
332
|
+
releaseRefreshForeground()
|
|
311
333
|
val index = logicalChildren.indexOf(child)
|
|
312
334
|
if (index < 0) return
|
|
313
335
|
logicalChildren.removeAt(index)
|
|
@@ -321,10 +343,17 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
321
343
|
stickyHeaderView = null
|
|
322
344
|
}
|
|
323
345
|
else -> {
|
|
346
|
+
val observed = observedRecyclerView ?: observedOwnedScroller ?: observedNativeScroller
|
|
347
|
+
if (observed != null && isDescendant(observed, child)) {
|
|
348
|
+
// The page detaches only later; its scroller must not keep writing this key.
|
|
349
|
+
detachPrimaryScrollObserver(recordOffset = false)
|
|
350
|
+
observedPageRemoved = true
|
|
351
|
+
}
|
|
324
352
|
when (val scrollable =
|
|
325
353
|
findExplicitNativeScrollerInView(child) ?: findVerticalScrollableView(child)) {
|
|
326
354
|
is RecyclerView -> restoreRecyclerInsets(scrollable)
|
|
327
355
|
is ScrollView -> restoreNativeScrollerInsets(scrollable)
|
|
356
|
+
is NativeScrollerViewport -> restoreOwnedScrollerInsets(scrollable)
|
|
328
357
|
}
|
|
329
358
|
adapter.removePage(child)
|
|
330
359
|
log("page-detach slot=${index - PAGE_SLOT_OFFSET} nativePages=${adapter.itemCount}")
|
|
@@ -333,6 +362,7 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
333
362
|
}
|
|
334
363
|
|
|
335
364
|
fun removeAllReactChildren() {
|
|
365
|
+
releaseRefreshForeground()
|
|
336
366
|
detachPrimaryScrollObserver()
|
|
337
367
|
for (recycler in originalRecyclerPadding.keys.toList()) {
|
|
338
368
|
restoreRecyclerInsets(recycler)
|
|
@@ -340,6 +370,7 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
340
370
|
for (scroller in originalScrollerPadding.keys.toList()) {
|
|
341
371
|
restoreNativeScrollerInsets(scroller)
|
|
342
372
|
}
|
|
373
|
+
for (scroller in ownedScrollerKeys.keys.toList()) restoreOwnedScrollerInsets(scroller)
|
|
343
374
|
headerView?.let { view -> super.removeView(view) }
|
|
344
375
|
stickyHeaderView?.let { view -> super.removeView(view) }
|
|
345
376
|
headerView = null
|
|
@@ -355,8 +386,10 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
355
386
|
fun reactChildAt(index: Int) = logicalChildren[index]
|
|
356
387
|
|
|
357
388
|
fun selectPage(position: Int) {
|
|
389
|
+
pendingRefreshPull = null
|
|
358
390
|
if (adapter.itemCount == 0) return
|
|
359
391
|
detachPrimaryScrollObserver()
|
|
392
|
+
releaseRefreshForeground()
|
|
360
393
|
selectedPage = position.coerceIn(0, adapter.itemCount - 1)
|
|
361
394
|
val savedOffset = pageOffsets[currentPageKey()]
|
|
362
395
|
?: primaryScrollableForPage(selectedPage)?.let(::verticalScrollOffset)
|
|
@@ -390,7 +423,7 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
390
423
|
fun attachedPageCount(): Int = (pager.getChildAt(0) as? RecyclerView)?.childCount ?: 0
|
|
391
424
|
|
|
392
425
|
fun observedScrollableCount(): Int =
|
|
393
|
-
originalRecyclerPadding.size + originalScrollerPadding.size
|
|
426
|
+
originalRecyclerPadding.size + originalScrollerPadding.size + ownedScrollerKeys.size
|
|
394
427
|
|
|
395
428
|
private fun layoutPagerIfRequested() {
|
|
396
429
|
val recycler = pager.getChildAt(0) as? RecyclerView ?: return
|
|
@@ -487,6 +520,7 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
487
520
|
|
|
488
521
|
private fun maximumHeaderOffset(): Int {
|
|
489
522
|
val scrollable = observedRecyclerView
|
|
523
|
+
?: observedOwnedScroller
|
|
490
524
|
?: observedNativeScroller
|
|
491
525
|
?: primaryScrollableForPage(selectedPage)
|
|
492
526
|
?: return headerHeightPx
|
|
@@ -508,6 +542,19 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
508
542
|
applyHeaderOffset()
|
|
509
543
|
}
|
|
510
544
|
|
|
545
|
+
private fun publishConsumedOffset(recycler: View, remove: Boolean = false) {
|
|
546
|
+
val key = R.id.onekey_native_scroll_coordinator_consumed_offsets
|
|
547
|
+
val existing = recycler.getTag(key) as? NativeScrollCoordinatorContributions
|
|
548
|
+
val contributions = existing ?: if (remove) return else NativeScrollCoordinatorContributions().also {
|
|
549
|
+
recycler.setTag(key, it)
|
|
550
|
+
}
|
|
551
|
+
val changed = if (remove) contributions.clearOwner(this) else contributions.update(this, headerOffsetPx)
|
|
552
|
+
if (changed) {
|
|
553
|
+
(recycler.getTag(R.id.onekey_native_scroll_coordinator_offset_listener) as? Runnable)?.run()
|
|
554
|
+
}
|
|
555
|
+
if (contributions.isEmpty()) recycler.setTag(key, null)
|
|
556
|
+
}
|
|
557
|
+
|
|
511
558
|
private fun applyHeaderOffset() {
|
|
512
559
|
val offset = headerOffsetPx.toFloat()
|
|
513
560
|
pager.translationY = -offset
|
|
@@ -515,6 +562,9 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
515
562
|
stickyHeaderView?.translationY = -offset
|
|
516
563
|
nativeTabBarView.translationY = -offset
|
|
517
564
|
nativeSubHeaderView.translationY = -offset
|
|
565
|
+
observedRecyclerView?.let { publishConsumedOffset(it) }
|
|
566
|
+
// Adjacent owned pages are translated too; unchanged values are not republished.
|
|
567
|
+
for (scroller in ownedScrollerKeys.keys) publishConsumedOffset(scroller)
|
|
518
568
|
}
|
|
519
569
|
|
|
520
570
|
private fun pageKey(index: Int): String = pageKeys.getOrNull(index) ?: "page-$index"
|
|
@@ -541,6 +591,7 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
541
591
|
}
|
|
542
592
|
|
|
543
593
|
private fun findExplicitNativeScrollerInView(view: View): View? {
|
|
594
|
+
if (view is NativeScrollerView) return view.viewport
|
|
544
595
|
if (view !== this && view is CollapsiblePagerHost) {
|
|
545
596
|
return view.currentPrimaryScrollableForParent()
|
|
546
597
|
}
|
|
@@ -557,6 +608,7 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
557
608
|
}
|
|
558
609
|
|
|
559
610
|
private fun findVerticalScrollableView(view: View): View? {
|
|
611
|
+
if (view is NativeScrollerView) return view.viewport
|
|
560
612
|
if (view !== this && view is CollapsiblePagerHost) {
|
|
561
613
|
return view.currentPrimaryScrollableForParent()
|
|
562
614
|
}
|
|
@@ -585,6 +637,7 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
585
637
|
private fun verticalScrollOffset(view: View): Int = when (view) {
|
|
586
638
|
is RecyclerView -> view.computeVerticalScrollOffset()
|
|
587
639
|
is ScrollView -> view.scrollY
|
|
640
|
+
is NativeScrollerViewport -> view.scrollY
|
|
588
641
|
else -> 0
|
|
589
642
|
}
|
|
590
643
|
|
|
@@ -595,14 +648,17 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
595
648
|
when (val scrollable = primaryScrollableForPage(index)) {
|
|
596
649
|
is RecyclerView -> applyRecyclerInsets(scrollable, index)
|
|
597
650
|
is ScrollView -> applyNativeScrollerInsets(scrollable, index)
|
|
651
|
+
is NativeScrollerViewport -> applyOwnedScrollerInsets(scrollable, index)
|
|
598
652
|
}
|
|
599
653
|
}
|
|
654
|
+
updateRefreshForeground()
|
|
600
655
|
}
|
|
601
656
|
|
|
602
657
|
private fun attachPrimaryScrollObserver() {
|
|
603
658
|
if (selectedPage !in 0 until adapter.itemCount) return
|
|
604
|
-
val previous = observedRecyclerView ?: observedNativeScroller
|
|
605
|
-
if (previous != null && !isDescendant(previous, adapter.pageAt(selectedPage))) {
|
|
659
|
+
val previous = observedRecyclerView ?: observedOwnedScroller ?: observedNativeScroller
|
|
660
|
+
if (observedPageRemoved || previous != null && !isDescendant(previous, adapter.pageAt(selectedPage))) {
|
|
661
|
+
observedPageRemoved = false
|
|
606
662
|
detachPrimaryScrollObserver()
|
|
607
663
|
// A replacement scroller starts at the beginning while retaining header collapse.
|
|
608
664
|
pageOffsets[currentPageKey()] = 0
|
|
@@ -610,18 +666,21 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
610
666
|
when (val scrollable = primaryScrollableForPage(selectedPage)) {
|
|
611
667
|
is RecyclerView -> attachRecyclerObserver(scrollable)
|
|
612
668
|
is ScrollView -> attachNativeScrollerObserver(scrollable)
|
|
669
|
+
is NativeScrollerViewport -> attachOwnedScrollerObserver(scrollable)
|
|
613
670
|
}
|
|
614
671
|
}
|
|
615
672
|
|
|
616
673
|
private fun attachRecyclerObserver(recycler: RecyclerView) {
|
|
617
674
|
if (observedRecyclerView === recycler) {
|
|
618
675
|
applyRecyclerInsets(recycler, selectedPage)
|
|
676
|
+
publishConsumedOffset(recycler)
|
|
619
677
|
return
|
|
620
678
|
}
|
|
621
679
|
|
|
622
680
|
detachPrimaryScrollObserver()
|
|
623
681
|
observedRecyclerView = recycler
|
|
624
682
|
applyRecyclerInsets(recycler, selectedPage)
|
|
683
|
+
publishConsumedOffset(recycler)
|
|
625
684
|
val listener = object : RecyclerView.OnScrollListener() {
|
|
626
685
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
|
627
686
|
val contentOffset = recyclerView.computeVerticalScrollOffset()
|
|
@@ -641,11 +700,12 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
641
700
|
)
|
|
642
701
|
}
|
|
643
702
|
|
|
644
|
-
private fun detachRecyclerObserver() {
|
|
703
|
+
private fun detachRecyclerObserver(recordOffset: Boolean) {
|
|
645
704
|
val recycler = observedRecyclerView
|
|
646
705
|
val listener = observedScrollListener
|
|
647
706
|
if (recycler != null) {
|
|
648
|
-
pageOffsets[currentPageKey()] = recycler.computeVerticalScrollOffset()
|
|
707
|
+
if (recordOffset) pageOffsets[currentPageKey()] = recycler.computeVerticalScrollOffset()
|
|
708
|
+
publishConsumedOffset(recycler, remove = true)
|
|
649
709
|
}
|
|
650
710
|
if (recycler != null && listener != null) recycler.removeOnScrollListener(listener)
|
|
651
711
|
if (recycler != null) {
|
|
@@ -667,27 +727,35 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
667
727
|
detachPrimaryScrollObserver()
|
|
668
728
|
observedNativeScroller = scroller
|
|
669
729
|
applyNativeScrollerInsets(scroller, selectedPage)
|
|
670
|
-
val listener = ViewTreeObserver.OnScrollChangedListener {
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
730
|
+
val listener = object : ViewTreeObserver.OnScrollChangedListener {
|
|
731
|
+
override fun onScrollChanged() {
|
|
732
|
+
if (observedNativeScrollerListener !== this) {
|
|
733
|
+
removeScrollChangedListener(null, scroller, this)
|
|
734
|
+
return
|
|
735
|
+
}
|
|
736
|
+
val contentOffset = scroller.scrollY
|
|
737
|
+
pageOffsets[currentPageKey()] = contentOffset
|
|
738
|
+
if (contentOffset > 0 && headerOffsetPx < headerHeightPx) {
|
|
739
|
+
setHeaderOffset(headerHeightPx)
|
|
740
|
+
}
|
|
675
741
|
}
|
|
676
742
|
}
|
|
677
743
|
observedNativeScrollerListener = listener
|
|
678
|
-
scroller.viewTreeObserver.
|
|
744
|
+
observedNativeScrollerTreeObserver = scroller.viewTreeObserver.also {
|
|
745
|
+
it.addOnScrollChangedListener(listener)
|
|
746
|
+
}
|
|
679
747
|
log(
|
|
680
748
|
"scroller-observer-attach inner=$selectedPage key=${currentPageKey()} " +
|
|
681
749
|
"scroller=${System.identityHashCode(scroller)}",
|
|
682
750
|
)
|
|
683
751
|
}
|
|
684
752
|
|
|
685
|
-
private fun detachNativeScrollerObserver() {
|
|
753
|
+
private fun detachNativeScrollerObserver(recordOffset: Boolean) {
|
|
686
754
|
val scroller = observedNativeScroller
|
|
687
755
|
val listener = observedNativeScrollerListener
|
|
688
|
-
if (scroller != null) pageOffsets[currentPageKey()] = scroller.scrollY
|
|
689
|
-
if (scroller != null && listener != null
|
|
690
|
-
scroller
|
|
756
|
+
if (scroller != null && recordOffset) pageOffsets[currentPageKey()] = scroller.scrollY
|
|
757
|
+
if (scroller != null && listener != null) {
|
|
758
|
+
removeScrollChangedListener(observedNativeScrollerTreeObserver, scroller, listener)
|
|
691
759
|
}
|
|
692
760
|
if (scroller != null) {
|
|
693
761
|
log(
|
|
@@ -697,11 +765,87 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
697
765
|
}
|
|
698
766
|
observedNativeScroller = null
|
|
699
767
|
observedNativeScrollerListener = null
|
|
768
|
+
observedNativeScrollerTreeObserver = null
|
|
700
769
|
}
|
|
701
770
|
|
|
702
|
-
private fun detachPrimaryScrollObserver() {
|
|
703
|
-
|
|
704
|
-
|
|
771
|
+
private fun detachPrimaryScrollObserver(recordOffset: Boolean = true) {
|
|
772
|
+
detachOwnedScrollerObserver(recordOffset)
|
|
773
|
+
detachRecyclerObserver(recordOffset)
|
|
774
|
+
detachNativeScrollerObserver(recordOffset)
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
private fun removeScrollChangedListener(
|
|
778
|
+
registered: ViewTreeObserver?,
|
|
779
|
+
scroller: View,
|
|
780
|
+
listener: ViewTreeObserver.OnScrollChangedListener,
|
|
781
|
+
) {
|
|
782
|
+
// A detached scroller returns a new floating observer, while the listener is
|
|
783
|
+
// still registered on the window observer. Try every instance that can own it.
|
|
784
|
+
for (observer in arrayOf(registered, scroller.viewTreeObserver, viewTreeObserver)) {
|
|
785
|
+
if (observer != null && observer.isAlive) observer.removeOnScrollChangedListener(listener)
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
private fun applyOwnedScrollerInsets(scroller: NativeScrollerViewport, pageIndex: Int) {
|
|
790
|
+
scroller.host.setPagerInset(this, headerHeightPx + stickyHeaderHeightPx)
|
|
791
|
+
publishConsumedOffset(scroller)
|
|
792
|
+
val key = pageKey(pageIndex)
|
|
793
|
+
if (ownedScrollerKeys[scroller] == key) return
|
|
794
|
+
ownedScrollerKeys[scroller] = key
|
|
795
|
+
postForCurrentAttachment {
|
|
796
|
+
if (ownedScrollerKeys[scroller] == key && scroller.isAttachedToWindow) {
|
|
797
|
+
scroller.host.restoreScrollOffset(pageOffsets[key] ?: 0)
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
private fun restoreOwnedScrollerInsets(scroller: NativeScrollerViewport) {
|
|
803
|
+
scroller.stopMotion()
|
|
804
|
+
scroller.host.cancelPendingScrollRestore()
|
|
805
|
+
ownedScrollerKeys.remove(scroller)
|
|
806
|
+
publishConsumedOffset(scroller, remove = true)
|
|
807
|
+
scroller.host.setPagerInset(this, null)
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
private fun attachOwnedScrollerObserver(scroller: NativeScrollerViewport) {
|
|
811
|
+
if (observedOwnedScroller === scroller && observedOwnedScrollerKey == currentPageKey()) {
|
|
812
|
+
applyOwnedScrollerInsets(scroller, selectedPage)
|
|
813
|
+
return
|
|
814
|
+
}
|
|
815
|
+
detachPrimaryScrollObserver()
|
|
816
|
+
observedOwnedScroller = scroller
|
|
817
|
+
observedOwnedScrollerKey = currentPageKey()
|
|
818
|
+
val key = currentPageKey()
|
|
819
|
+
applyOwnedScrollerInsets(scroller, selectedPage)
|
|
820
|
+
val listener = object : ViewTreeObserver.OnScrollChangedListener {
|
|
821
|
+
override fun onScrollChanged() {
|
|
822
|
+
if (observedOwnedScrollerListener !== this) {
|
|
823
|
+
removeScrollChangedListener(null, scroller, this)
|
|
824
|
+
return
|
|
825
|
+
}
|
|
826
|
+
val contentOffset = scroller.scrollY
|
|
827
|
+
pageOffsets[key] = contentOffset
|
|
828
|
+
if (contentOffset > 0 && headerOffsetPx < headerHeightPx) setHeaderOffset(headerHeightPx)
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
observedOwnedScrollerListener = listener
|
|
832
|
+
observedOwnedScrollerTreeObserver = scroller.viewTreeObserver.also {
|
|
833
|
+
it.addOnScrollChangedListener(listener)
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
private fun detachOwnedScrollerObserver(recordOffset: Boolean) {
|
|
838
|
+
val scroller = observedOwnedScroller ?: return
|
|
839
|
+
scroller.stopMotion()
|
|
840
|
+
publishConsumedOffset(scroller, remove = true)
|
|
841
|
+
if (recordOffset) observedOwnedScrollerKey?.let { pageOffsets[it] = scroller.scrollY }
|
|
842
|
+
observedOwnedScrollerListener?.let {
|
|
843
|
+
removeScrollChangedListener(observedOwnedScrollerTreeObserver, scroller, it)
|
|
844
|
+
}
|
|
845
|
+
observedOwnedScroller = null
|
|
846
|
+
observedOwnedScrollerKey = null
|
|
847
|
+
observedOwnedScrollerListener = null
|
|
848
|
+
observedOwnedScrollerTreeObserver = null
|
|
705
849
|
}
|
|
706
850
|
|
|
707
851
|
private fun applyRecyclerInsets(recycler: RecyclerView, pageIndex: Int) {
|
|
@@ -775,11 +919,29 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
775
919
|
}
|
|
776
920
|
updateScrollerPaddingOwnership(scroller, original)
|
|
777
921
|
val topInset = headerHeightPx + stickyHeaderHeightPx
|
|
778
|
-
val top = original.top + topInset
|
|
779
922
|
scroller.clipToPadding = false
|
|
780
|
-
if (scroller
|
|
781
|
-
|
|
782
|
-
|
|
923
|
+
if (scroller is ReactScrollView) {
|
|
924
|
+
// Fabric owns the content child's layout, so ScrollView padding alone
|
|
925
|
+
// cannot move it. RN's native ScrollAway API also updates Fabric culling.
|
|
926
|
+
val state = scroller.reactScrollViewScrollState
|
|
927
|
+
val scrollAwayTop = nativeScrollerScrollAwayTop(
|
|
928
|
+
state.scrollAwayPaddingTop, original.appliedTopInset, topInset,
|
|
929
|
+
)
|
|
930
|
+
if (state.scrollAwayPaddingTop != scrollAwayTop ||
|
|
931
|
+
scroller.getChildAt(0)?.translationY != scrollAwayTop.toFloat()) {
|
|
932
|
+
scroller.setScrollAwayPaddingEnabledUnstable(scrollAwayTop, state.scrollAwayPaddingBottom)
|
|
933
|
+
}
|
|
934
|
+
val bottom = original.bottom + topInset
|
|
935
|
+
if (scroller.paddingLeft != original.left || scroller.paddingTop != original.top ||
|
|
936
|
+
scroller.paddingRight != original.right || scroller.paddingBottom != bottom) {
|
|
937
|
+
scroller.setPadding(original.left, original.top, original.right, bottom)
|
|
938
|
+
}
|
|
939
|
+
} else {
|
|
940
|
+
val top = original.top + topInset
|
|
941
|
+
if (scroller.paddingLeft != original.left || scroller.paddingTop != top ||
|
|
942
|
+
scroller.paddingRight != original.right || scroller.paddingBottom != original.bottom) {
|
|
943
|
+
scroller.setPadding(original.left, top, original.right, original.bottom)
|
|
944
|
+
}
|
|
783
945
|
}
|
|
784
946
|
original.appliedTopInset = topInset
|
|
785
947
|
|
|
@@ -804,13 +966,29 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
804
966
|
scroller: ScrollView,
|
|
805
967
|
original: ScrollerPadding,
|
|
806
968
|
) {
|
|
807
|
-
val
|
|
969
|
+
val isReactScroller = scroller is ReactScrollView
|
|
970
|
+
if (scroller is ReactScrollView) {
|
|
971
|
+
val state = scroller.reactScrollViewScrollState
|
|
972
|
+
if (nativeScrollerPaddingWasResetByScrollAway(
|
|
973
|
+
original.appliedTopInset,
|
|
974
|
+
scroller.paddingLeft, scroller.paddingTop, scroller.paddingRight, scroller.paddingBottom,
|
|
975
|
+
state.scrollAwayPaddingTop, state.scrollAwayPaddingBottom,
|
|
976
|
+
)) {
|
|
977
|
+
// RN may replay ScrollAway state before this pass. Its setter clears
|
|
978
|
+
// caller padding; that native reset must not become our new baseline.
|
|
979
|
+
return
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
val ownedTop = if (isReactScroller) 0 else original.appliedTopInset
|
|
983
|
+
val ownedBottom = if (isReactScroller) original.appliedTopInset else 0
|
|
808
984
|
if (scroller.paddingLeft != original.left) original.left = scroller.paddingLeft
|
|
809
|
-
if (scroller.paddingTop !=
|
|
810
|
-
original.top = (scroller.paddingTop -
|
|
985
|
+
if (scroller.paddingTop != original.top + ownedTop) {
|
|
986
|
+
original.top = (scroller.paddingTop - ownedTop).coerceAtLeast(0)
|
|
811
987
|
}
|
|
812
988
|
if (scroller.paddingRight != original.right) original.right = scroller.paddingRight
|
|
813
|
-
if (scroller.paddingBottom != original.bottom
|
|
989
|
+
if (scroller.paddingBottom != original.bottom + ownedBottom) {
|
|
990
|
+
original.bottom = (scroller.paddingBottom - ownedBottom).coerceAtLeast(0)
|
|
991
|
+
}
|
|
814
992
|
if (scroller.clipToPadding) original.clipToPadding = true
|
|
815
993
|
}
|
|
816
994
|
|
|
@@ -835,6 +1013,7 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
835
1013
|
}
|
|
836
1014
|
|
|
837
1015
|
private fun restoreRecyclerInsets(recycler: RecyclerView) {
|
|
1016
|
+
publishConsumedOffset(recycler, remove = true)
|
|
838
1017
|
val original = originalRecyclerPadding.remove(recycler) ?: return
|
|
839
1018
|
updateRecyclerPaddingOwnership(recycler, original)
|
|
840
1019
|
restoredRecyclerKeys.remove(recycler)
|
|
@@ -846,10 +1025,147 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
846
1025
|
val original = originalScrollerPadding.remove(scroller) ?: return
|
|
847
1026
|
updateScrollerPaddingOwnership(scroller, original)
|
|
848
1027
|
restoredScrollerKeys.remove(scroller)
|
|
1028
|
+
if (scroller is ReactScrollView) {
|
|
1029
|
+
val state = scroller.reactScrollViewScrollState
|
|
1030
|
+
scroller.setScrollAwayPaddingEnabledUnstable(
|
|
1031
|
+
nativeScrollerScrollAwayTop(state.scrollAwayPaddingTop, original.appliedTopInset, 0),
|
|
1032
|
+
state.scrollAwayPaddingBottom,
|
|
1033
|
+
)
|
|
1034
|
+
}
|
|
849
1035
|
scroller.clipToPadding = original.clipToPadding
|
|
850
1036
|
scroller.setPadding(original.left, original.top, original.right, original.bottom)
|
|
851
1037
|
}
|
|
852
1038
|
|
|
1039
|
+
private fun refreshLayoutFor(scrollable: View): SwipeRefreshLayout? {
|
|
1040
|
+
if (scrollable !is ScrollView && scrollable !is RecyclerView && scrollable !is NativeScrollerViewport) return null
|
|
1041
|
+
var ancestor = scrollable.parent
|
|
1042
|
+
while (ancestor != null && ancestor !is SwipeRefreshLayout) ancestor = ancestor.parent
|
|
1043
|
+
return ancestor as? SwipeRefreshLayout
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
private fun refreshIndicatorFor(refresh: SwipeRefreshLayout): ImageView? {
|
|
1047
|
+
for (index in 0 until refresh.childCount) {
|
|
1048
|
+
val child = refresh.getChildAt(index)
|
|
1049
|
+
if (child is ImageView && child.drawable is CircularProgressDrawable) return child
|
|
1050
|
+
}
|
|
1051
|
+
return null
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
private fun refreshForegroundOwner(scrollable: View): CollapsiblePagerHost? {
|
|
1055
|
+
var owner: CollapsiblePagerHost? = null
|
|
1056
|
+
var ancestor = scrollable.parent
|
|
1057
|
+
while (ancestor != null) {
|
|
1058
|
+
if (ancestor is CollapsiblePagerHost && ancestor.nativeSmoothHeaderScrollEnabled &&
|
|
1059
|
+
ancestor.isAttachedToWindow && ancestor.isShown &&
|
|
1060
|
+
ancestor.primaryScrollableForPage(ancestor.selectedPage) === scrollable) {
|
|
1061
|
+
owner = ancestor
|
|
1062
|
+
}
|
|
1063
|
+
ancestor = ancestor.parent
|
|
1064
|
+
}
|
|
1065
|
+
return owner
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
private fun updateRefreshForeground() {
|
|
1069
|
+
val scrollable = primaryScrollableForPage(selectedPage)
|
|
1070
|
+
val refresh = scrollable?.takeIf { refreshForegroundOwner(it) === this }?.let(::refreshLayoutFor)
|
|
1071
|
+
val indicator = refresh?.let(::refreshIndicatorFor)
|
|
1072
|
+
if (foregroundRefreshIndicator?.get() !== indicator) releaseRefreshForeground()
|
|
1073
|
+
if (indicator != null) {
|
|
1074
|
+
if (foregroundRefreshIndicator?.get() !== indicator) {
|
|
1075
|
+
foregroundRefreshIndicator = WeakReference(indicator)
|
|
1076
|
+
}
|
|
1077
|
+
val state = refreshIndicatorAlphas.getOrPut(indicator) {
|
|
1078
|
+
NativeRefreshForegroundAlpha(indicator.alpha)
|
|
1079
|
+
}
|
|
1080
|
+
val alpha = state.update(indicator.alpha, this, acquire = true)
|
|
1081
|
+
if (indicator.alpha != alpha) indicator.alpha = alpha
|
|
1082
|
+
}
|
|
1083
|
+
val visible = indicator?.isShown == true &&
|
|
1084
|
+
(refreshIndicatorAlphas[indicator]?.drawingAlpha ?: 0f) > 0f
|
|
1085
|
+
// The foreground lies outside the original child's invalidation rectangle.
|
|
1086
|
+
// Repaint once on disappearance as well as throughout the native animation.
|
|
1087
|
+
if (visible || refreshForegroundWasVisible) postInvalidateOnAnimation()
|
|
1088
|
+
refreshForegroundWasVisible = visible
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
private fun releaseRefreshForeground() {
|
|
1092
|
+
val indicator = foregroundRefreshIndicator?.get()
|
|
1093
|
+
foregroundRefreshIndicator = null
|
|
1094
|
+
if (indicator != null) {
|
|
1095
|
+
refreshIndicatorAlphas[indicator]?.let { state ->
|
|
1096
|
+
indicator.alpha = state.update(indicator.alpha, this, acquire = false)
|
|
1097
|
+
if (state.isEmpty) refreshIndicatorAlphas.remove(indicator)
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
if (indicator != null || refreshForegroundWasVisible) invalidate()
|
|
1101
|
+
refreshForegroundWasVisible = false
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
private fun refreshMatrixInHost(refresh: View): Boolean {
|
|
1105
|
+
refreshForegroundMatrix.reset()
|
|
1106
|
+
refreshForegroundAncestorAlpha = 1f
|
|
1107
|
+
refreshForegroundAncestors.clear()
|
|
1108
|
+
try {
|
|
1109
|
+
var current: View? = refresh
|
|
1110
|
+
while (current != null && current !== this) {
|
|
1111
|
+
refreshForegroundAncestors.add(current)
|
|
1112
|
+
current = current.parent as? View
|
|
1113
|
+
}
|
|
1114
|
+
if (current !== this) return false
|
|
1115
|
+
for (index in refreshForegroundAncestors.lastIndex downTo 0) {
|
|
1116
|
+
val child = refreshForegroundAncestors[index]
|
|
1117
|
+
val parent = child.parent as? View ?: return false
|
|
1118
|
+
refreshForegroundMatrix.preTranslate(
|
|
1119
|
+
(child.left - parent.scrollX).toFloat(), (child.top - parent.scrollY).toFloat(),
|
|
1120
|
+
)
|
|
1121
|
+
refreshForegroundMatrix.preConcat(child.matrix)
|
|
1122
|
+
refreshForegroundAncestorAlpha *= child.alpha
|
|
1123
|
+
}
|
|
1124
|
+
return true
|
|
1125
|
+
} finally {
|
|
1126
|
+
refreshForegroundAncestors.clear()
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
override fun dispatchDraw(canvas: Canvas) {
|
|
1131
|
+
// The original parent advances native refresh animations and their listeners.
|
|
1132
|
+
super.dispatchDraw(canvas)
|
|
1133
|
+
val indicator = foregroundRefreshIndicator?.get() ?: return
|
|
1134
|
+
val scrollable = primaryScrollableForPage(selectedPage) ?: return
|
|
1135
|
+
if (refreshForegroundOwner(scrollable) !== this) return
|
|
1136
|
+
val refresh = refreshLayoutFor(scrollable) ?: return
|
|
1137
|
+
if (refreshIndicatorFor(refresh) !== indicator || !indicator.isShown ||
|
|
1138
|
+
indicator.width <= 0 || indicator.height <= 0 || !refreshMatrixInHost(refresh)) return
|
|
1139
|
+
val alpha = (refreshIndicatorAlphas[indicator]?.drawingAlpha ?: return) *
|
|
1140
|
+
refreshForegroundAncestorAlpha
|
|
1141
|
+
if (alpha <= 0f) return
|
|
1142
|
+
refreshForegroundOrigin[0] = 0f
|
|
1143
|
+
refreshForegroundOrigin[1] = 0f
|
|
1144
|
+
refreshForegroundMatrix.mapPoints(refreshForegroundOrigin)
|
|
1145
|
+
val appliedInset = (refresh.getTag(R.id.onekey_native_scroll_coordinator_refresh_inset) as? Number)
|
|
1146
|
+
?.toFloat() ?: 0f
|
|
1147
|
+
val save = canvas.save()
|
|
1148
|
+
try {
|
|
1149
|
+
// Pin the refresh container's origin to this Host top while retaining its
|
|
1150
|
+
// horizontal page position, native scale/pivot, and caller translation.
|
|
1151
|
+
canvas.translate(0f, -refreshForegroundOrigin[1])
|
|
1152
|
+
canvas.concat(refreshForegroundMatrix)
|
|
1153
|
+
canvas.translate(indicator.left.toFloat(), nativeRefreshForegroundTop(indicator.top, appliedInset))
|
|
1154
|
+
canvas.concat(indicator.matrix)
|
|
1155
|
+
if (alpha < 1f) {
|
|
1156
|
+
canvas.saveLayerAlpha(
|
|
1157
|
+
0f, 0f, indicator.width.toFloat(), indicator.height.toFloat(),
|
|
1158
|
+
(alpha * 255f).roundToInt().coerceIn(0, 255),
|
|
1159
|
+
)
|
|
1160
|
+
}
|
|
1161
|
+
// Direct draw deliberately bypasses the alpha used to hide the original
|
|
1162
|
+
// RenderNode. Do not advance Animation.getTransformation a second time.
|
|
1163
|
+
indicator.draw(canvas)
|
|
1164
|
+
} finally {
|
|
1165
|
+
canvas.restoreToCount(save)
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
|
|
853
1169
|
private fun headerRegionAt(y: Float): String? {
|
|
854
1170
|
val headerBottom = headerHeightPx - headerOffsetPx
|
|
855
1171
|
val stickyBottom = headerHeightPx + stickyHeaderHeightPx - headerOffsetPx
|
|
@@ -1074,10 +1390,12 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
1074
1390
|
axes and ViewCompat.SCROLL_AXIS_VERTICAL != 0 && isCurrentPageTarget(target)
|
|
1075
1391
|
|
|
1076
1392
|
override fun onNestedScrollAccepted(child: View, target: View, axes: Int, type: Int) {
|
|
1393
|
+
if (type == ViewCompat.TYPE_TOUCH) pendingRefreshPull = null
|
|
1077
1394
|
nestedScrollingParentHelper.onNestedScrollAccepted(child, target, axes, type)
|
|
1078
1395
|
}
|
|
1079
1396
|
|
|
1080
1397
|
override fun onStopNestedScroll(target: View, type: Int) {
|
|
1398
|
+
if (pendingRefreshPull?.get() === target) pendingRefreshPull = null
|
|
1081
1399
|
nestedScrollingParentHelper.onStopNestedScroll(target, type)
|
|
1082
1400
|
onHeaderOffsetChanged?.invoke()
|
|
1083
1401
|
}
|
|
@@ -1092,10 +1410,18 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
1092
1410
|
val used = min(dy, maximumOffset - headerOffsetPx)
|
|
1093
1411
|
setHeaderOffset(headerOffsetPx + used)
|
|
1094
1412
|
consumed[1] += used
|
|
1095
|
-
} else if (dy < 0 && headerOffsetPx > 0
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1413
|
+
} else if (dy < 0 && headerOffsetPx > 0) {
|
|
1414
|
+
// Refresh wrappers forward nested scrolls but do not scroll themselves.
|
|
1415
|
+
val canScrollUp = if (target is SwipeRefreshLayout) {
|
|
1416
|
+
target.canChildScrollUp()
|
|
1417
|
+
} else {
|
|
1418
|
+
target.canScrollVertically(-1)
|
|
1419
|
+
}
|
|
1420
|
+
if (!canScrollUp) {
|
|
1421
|
+
val used = max(dy, -headerOffsetPx)
|
|
1422
|
+
setHeaderOffset(headerOffsetPx + used)
|
|
1423
|
+
consumed[1] += used
|
|
1424
|
+
}
|
|
1099
1425
|
}
|
|
1100
1426
|
}
|
|
1101
1427
|
|
|
@@ -1108,10 +1434,14 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
1108
1434
|
type: Int,
|
|
1109
1435
|
consumed: IntArray,
|
|
1110
1436
|
) {
|
|
1111
|
-
if (!isCurrentPageTarget(target) || dyUnconsumed >= 0
|
|
1437
|
+
if (!isCurrentPageTarget(target) || dyUnconsumed >= 0) return
|
|
1112
1438
|
val used = max(dyUnconsumed, -headerOffsetPx)
|
|
1113
1439
|
setHeaderOffset(headerOffsetPx + used)
|
|
1114
1440
|
consumed[1] += used
|
|
1441
|
+
if (type == ViewCompat.TYPE_TOUCH && dyUnconsumed < used &&
|
|
1442
|
+
target is SwipeRefreshLayout && target.isEnabled) {
|
|
1443
|
+
pendingRefreshPull = WeakReference(target)
|
|
1444
|
+
}
|
|
1115
1445
|
}
|
|
1116
1446
|
|
|
1117
1447
|
override fun onNestedScroll(
|
|
@@ -1133,6 +1463,20 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
1133
1463
|
)
|
|
1134
1464
|
}
|
|
1135
1465
|
|
|
1466
|
+
override fun onNestedPreFling(target: View, velocityX: Float, velocityY: Float): Boolean {
|
|
1467
|
+
val refresh = pendingRefreshPull?.get()
|
|
1468
|
+
val scrollable = primaryScrollableForPage(selectedPage)
|
|
1469
|
+
if (nativeSmoothHeaderScrollEnabled && refresh != null && refresh === target && isCurrentPageTarget(target) &&
|
|
1470
|
+
headerOffsetPx == 0 && scrollable != null && !scrollable.canScrollVertically(-1) &&
|
|
1471
|
+
refresh.isEnabled && !refresh.isRefreshing &&
|
|
1472
|
+
refreshIndicatorFor(refresh)?.visibility == View.VISIBLE) {
|
|
1473
|
+
// A small release reversal must settle the current refresh pull, not fling
|
|
1474
|
+
// content upward and make its first positive offset collapse the header.
|
|
1475
|
+
return true
|
|
1476
|
+
}
|
|
1477
|
+
return super.onNestedPreFling(target, velocityX, velocityY)
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1136
1480
|
override fun getNestedScrollAxes() = nestedScrollingParentHelper.nestedScrollAxes
|
|
1137
1481
|
|
|
1138
1482
|
override fun onAttachedToWindow() {
|
|
@@ -1148,6 +1492,8 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
1148
1492
|
}
|
|
1149
1493
|
|
|
1150
1494
|
override fun onDetachedFromWindow() {
|
|
1495
|
+
pendingRefreshPull = null
|
|
1496
|
+
releaseRefreshForeground()
|
|
1151
1497
|
attachmentGeneration += 1
|
|
1152
1498
|
headerDownEvent?.recycle()
|
|
1153
1499
|
headerDownEvent = null
|
|
@@ -1164,6 +1510,7 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
1164
1510
|
for (scroller in originalScrollerPadding.keys.toList()) {
|
|
1165
1511
|
restoreNativeScrollerInsets(scroller)
|
|
1166
1512
|
}
|
|
1513
|
+
for (scroller in ownedScrollerKeys.keys.toList()) restoreOwnedScrollerInsets(scroller)
|
|
1167
1514
|
log(
|
|
1168
1515
|
"host-detach generation=$attachmentGeneration inner=$selectedPage " +
|
|
1169
1516
|
"outer=${outerPagerIndex()} nativePages=${adapter.itemCount}",
|
|
@@ -1172,6 +1519,7 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
|
|
|
1172
1519
|
}
|
|
1173
1520
|
|
|
1174
1521
|
companion object {
|
|
1522
|
+
private val refreshIndicatorAlphas = WeakHashMap<ImageView, NativeRefreshForegroundAlpha>()
|
|
1175
1523
|
const val NATIVE_SCROLLER_ID_PREFIX = "rnc-collapsible-pager-native-scroller:"
|
|
1176
1524
|
const val PAGE_SLOT_OFFSET = 2
|
|
1177
1525
|
}
|