@onekeyfe/react-native-pager-view 1.1.35

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.
Files changed (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +306 -0
  3. package/android/Android.mk +45 -0
  4. package/android/build.gradle +237 -0
  5. package/android/debug.keystore +0 -0
  6. package/android/gradle.properties +5 -0
  7. package/android/registration.cpp +18 -0
  8. package/android/src/main/AndroidManifest.xml +4 -0
  9. package/android/src/main/java/com/reactnativepagerview/NestedScrollableHost.kt +148 -0
  10. package/android/src/main/java/com/reactnativepagerview/PagerViewViewManager.kt +222 -0
  11. package/android/src/main/java/com/reactnativepagerview/PagerViewViewManagerImpl.kt +228 -0
  12. package/android/src/main/java/com/reactnativepagerview/PagerViewViewPackage.kt +17 -0
  13. package/android/src/main/java/com/reactnativepagerview/ViewPagerAdapter.kt +121 -0
  14. package/android/src/main/java/com/reactnativepagerview/ViewPagerViewHolder.kt +21 -0
  15. package/android/src/main/java/com/reactnativepagerview/event/PageScrollEvent.kt +47 -0
  16. package/android/src/main/java/com/reactnativepagerview/event/PageScrollStateChangedEvent.kt +34 -0
  17. package/android/src/main/java/com/reactnativepagerview/event/PageSelectedEvent.kt +38 -0
  18. package/ios/PagerView.xcodeproj/project.pbxproj +274 -0
  19. package/ios/RCTOnPageScrollEvent.h +14 -0
  20. package/ios/RCTOnPageScrollEvent.m +60 -0
  21. package/ios/RNCPagerViewComponentView.h +11 -0
  22. package/ios/RNCPagerViewComponentView.mm +704 -0
  23. package/lib/module/PagerView.js +136 -0
  24. package/lib/module/PagerView.js.map +1 -0
  25. package/lib/module/PagerViewNativeComponent.ts +82 -0
  26. package/lib/module/codegen-types.d.js +2 -0
  27. package/lib/module/codegen-types.d.js.map +1 -0
  28. package/lib/module/index.js +6 -0
  29. package/lib/module/index.js.map +1 -0
  30. package/lib/module/package.json +1 -0
  31. package/lib/module/usePagerView.js +106 -0
  32. package/lib/module/usePagerView.js.map +1 -0
  33. package/lib/module/utils.js +27 -0
  34. package/lib/module/utils.js.map +1 -0
  35. package/lib/typescript/package.json +1 -0
  36. package/lib/typescript/src/PagerView.d.ts +70 -0
  37. package/lib/typescript/src/PagerView.d.ts.map +1 -0
  38. package/lib/typescript/src/PagerViewNativeComponent.d.ts +51 -0
  39. package/lib/typescript/src/PagerViewNativeComponent.d.ts.map +1 -0
  40. package/lib/typescript/src/index.d.ts +10 -0
  41. package/lib/typescript/src/index.d.ts.map +1 -0
  42. package/lib/typescript/src/usePagerView.d.ts +38 -0
  43. package/lib/typescript/src/usePagerView.d.ts.map +1 -0
  44. package/lib/typescript/src/utils.d.ts +3 -0
  45. package/lib/typescript/src/utils.d.ts.map +1 -0
  46. package/package.json +101 -0
  47. package/react-native-pager-view.podspec +20 -0
  48. package/src/PagerView.tsx +170 -0
  49. package/src/PagerViewNativeComponent.ts +82 -0
  50. package/src/codegen-types.d.ts +28 -0
  51. package/src/index.tsx +27 -0
  52. package/src/usePagerView.ts +148 -0
  53. package/src/utils.tsx +22 -0
@@ -0,0 +1,148 @@
1
+ package com.reactnativepagerview
2
+
3
+ import android.content.Context
4
+ import android.util.AttributeSet
5
+ import android.view.Choreographer
6
+ import android.view.MotionEvent
7
+ import android.view.View
8
+ import android.view.ViewConfiguration
9
+ import android.widget.FrameLayout
10
+ import androidx.viewpager2.widget.ViewPager2
11
+ import androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL
12
+ import com.facebook.react.uimanager.events.NativeGestureUtil
13
+ import kotlin.math.absoluteValue
14
+ import kotlin.math.sign
15
+
16
+ /**
17
+ * Layout to wrap a scrollable component inside a ViewPager2. Provided as a solution to the problem
18
+ * where pages of ViewPager2 have nested scrollable elements that scroll in the same direction as
19
+ * ViewPager2. The scrollable element needs to be the immediate and only child of this host layout.
20
+ *
21
+ * Supports multiple levels of nested PagerViews by re-asserting
22
+ * requestDisallowInterceptTouchEvent after child dispatch.
23
+ */
24
+ class NestedScrollableHost : FrameLayout {
25
+ constructor(context: Context) : super(context)
26
+ constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
27
+ public var initialIndex: Int? = null
28
+ public var didSetInitialIndex = false
29
+ public var pendingRefreshFrameCallback: Choreographer.FrameCallback? = null
30
+ private var touchSlop = 0
31
+ private var initialX = 0f
32
+ private var initialY = 0f
33
+ private var nativeGestureStarted: Boolean = false
34
+ private val parentViewPager: ViewPager2?
35
+ get() {
36
+ var v: View? = parent as? View
37
+ while (v != null && v !is ViewPager2) {
38
+ v = v.parent as? View
39
+ }
40
+ return v as? ViewPager2
41
+ }
42
+
43
+ private val child: View? get() = if (childCount > 0) getChildAt(0) else null
44
+
45
+ init {
46
+ touchSlop = ViewConfiguration.get(context).scaledTouchSlop
47
+ }
48
+
49
+ private fun canChildScroll(orientation: Int, delta: Float): Boolean {
50
+ val direction = -delta.sign.toInt()
51
+ return when (orientation) {
52
+ 0 -> child?.canScrollHorizontally(direction) ?: false
53
+ 1 -> child?.canScrollVertically(direction) ?: false
54
+ else -> throw IllegalArgumentException()
55
+ }
56
+ }
57
+
58
+ override fun dispatchTouchEvent(e: MotionEvent): Boolean {
59
+ val handled = super.dispatchTouchEvent(e)
60
+
61
+ // After the full child dispatch cycle, a deeply-nested NestedScrollableHost may
62
+ // have called requestDisallowInterceptTouchEvent(false) which propagates all the
63
+ // way up, overriding our earlier requestDisallowInterceptTouchEvent(true).
64
+ // Re-assert here if our own ViewPager2 can still scroll in the gesture direction.
65
+ if (e.action == MotionEvent.ACTION_MOVE) {
66
+ val orientation = parentViewPager?.orientation ?: return handled
67
+ val dx = e.x - initialX
68
+ val dy = e.y - initialY
69
+ val isVpHorizontal = orientation == ORIENTATION_HORIZONTAL
70
+ val scaledDx = dx.absoluteValue * if (isVpHorizontal) .5f else 1f
71
+ val scaledDy = dy.absoluteValue * if (isVpHorizontal) 1f else .5f
72
+
73
+ if (scaledDx > touchSlop || scaledDy > touchSlop) {
74
+ if (isVpHorizontal != (scaledDy > scaledDx)) {
75
+ // Parallel gesture — re-assert if our VP can scroll
76
+ if (canChildScroll(orientation, if (isVpHorizontal) dx else dy)) {
77
+ parent.requestDisallowInterceptTouchEvent(true)
78
+ }
79
+ }
80
+ }
81
+ }
82
+
83
+ return handled
84
+ }
85
+
86
+ override fun onInterceptTouchEvent(e: MotionEvent): Boolean {
87
+ handleInterceptTouchEvent(e)
88
+ return super.onInterceptTouchEvent(e)
89
+ }
90
+
91
+ private fun handleInterceptTouchEvent(e: MotionEvent) {
92
+ val orientation = parentViewPager?.orientation
93
+
94
+ if (e.action == MotionEvent.ACTION_DOWN) {
95
+ initialX = e.x
96
+ initialY = e.y
97
+ if (orientation != null) {
98
+ parent.requestDisallowInterceptTouchEvent(true)
99
+ }
100
+ } else if (e.action == MotionEvent.ACTION_MOVE) {
101
+ val dx = e.x - initialX
102
+ val dy = e.y - initialY
103
+ val isVpHorizontal = orientation == ORIENTATION_HORIZONTAL
104
+
105
+ // assuming ViewPager2 touch-slop is 2x touch-slop of child
106
+ val scaledDx = dx.absoluteValue * if (isVpHorizontal) .5f else 1f
107
+ val scaledDy = dy.absoluteValue * if (isVpHorizontal) 1f else .5f
108
+
109
+ if (scaledDx > touchSlop || scaledDy > touchSlop) {
110
+ NativeGestureUtil.notifyNativeGestureStarted(this, e)
111
+ nativeGestureStarted = true
112
+
113
+ if (orientation == null) return
114
+ if (isVpHorizontal == (scaledDy > scaledDx)) {
115
+ // Gesture is perpendicular, allow all parents to intercept
116
+ parent.requestDisallowInterceptTouchEvent(false)
117
+ } else {
118
+ // Gesture is parallel, query child if movement in that direction is possible
119
+ if (canChildScroll(orientation, if (isVpHorizontal) dx else dy)) {
120
+ // Child can scroll, disallow all parents to intercept
121
+ parent.requestDisallowInterceptTouchEvent(true)
122
+ } else {
123
+ // Child cannot scroll, allow all parents to intercept
124
+ parent.requestDisallowInterceptTouchEvent(false)
125
+ }
126
+ }
127
+ }
128
+ }
129
+ }
130
+
131
+ override fun onTouchEvent(e: MotionEvent): Boolean {
132
+ if (e.actionMasked == MotionEvent.ACTION_UP) {
133
+ if (nativeGestureStarted) {
134
+ NativeGestureUtil.notifyNativeGestureEnded(this, e)
135
+ nativeGestureStarted = false
136
+ }
137
+ }
138
+ return super.onTouchEvent(e)
139
+ }
140
+
141
+ override fun onDetachedFromWindow() {
142
+ pendingRefreshFrameCallback?.let { callback ->
143
+ Choreographer.getInstance().removeFrameCallback(callback)
144
+ pendingRefreshFrameCallback = null
145
+ }
146
+ super.onDetachedFromWindow()
147
+ }
148
+ }
@@ -0,0 +1,222 @@
1
+ package com.reactnativepagerview
2
+
3
+ import android.view.View
4
+ import android.view.ViewGroup
5
+ import androidx.viewpager2.widget.ViewPager2
6
+ import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
7
+ import com.facebook.infer.annotation.Assertions
8
+ import com.facebook.react.bridge.ReactContext
9
+ import com.facebook.react.bridge.ReadableArray
10
+ import com.facebook.react.common.MapBuilder
11
+ import com.facebook.react.module.annotations.ReactModule
12
+ import com.facebook.react.uimanager.*
13
+ import com.facebook.react.uimanager.annotations.ReactProp
14
+ import com.facebook.react.viewmanagers.RNCViewPagerManagerDelegate
15
+ import com.facebook.react.viewmanagers.RNCViewPagerManagerInterface
16
+ import com.facebook.soloader.SoLoader
17
+ import com.reactnativepagerview.event.PageScrollEvent
18
+ import com.reactnativepagerview.event.PageScrollStateChangedEvent
19
+ import com.reactnativepagerview.event.PageSelectedEvent
20
+ import com.reactnativepagerview.PagerViewViewManagerImpl.reduceDragSensitivity
21
+
22
+ @ReactModule(name = PagerViewViewManagerImpl.NAME)
23
+ class PagerViewViewManager : ViewGroupManager<NestedScrollableHost>(), RNCViewPagerManagerInterface<NestedScrollableHost> {
24
+ companion object {
25
+ init {
26
+ if (BuildConfig.CODEGEN_MODULE_REGISTRATION != null) {
27
+ SoLoader.loadLibrary(BuildConfig.CODEGEN_MODULE_REGISTRATION)
28
+ }
29
+ }
30
+ }
31
+
32
+ private val mDelegate: ViewManagerDelegate<NestedScrollableHost> = RNCViewPagerManagerDelegate(this)
33
+
34
+ override fun getDelegate() = mDelegate
35
+
36
+ override fun getName(): String {
37
+ return PagerViewViewManagerImpl.NAME
38
+ }
39
+
40
+ override fun receiveCommand(root: NestedScrollableHost, commandId: String, args: ReadableArray?) {
41
+ mDelegate.receiveCommand(root, commandId, args)
42
+ }
43
+
44
+ public override fun createViewInstance(reactContext: ThemedReactContext): NestedScrollableHost {
45
+ val host = NestedScrollableHost(reactContext)
46
+ host.id = View.generateViewId()
47
+ host.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
48
+ host.isSaveEnabled = false
49
+ val vp = ViewPager2(reactContext)
50
+ vp.adapter = ViewPagerAdapter()
51
+ //https://github.com/callstack/react-native-viewpager/issues/183
52
+ vp.isSaveEnabled = false
53
+
54
+ vp.post {
55
+ vp.registerOnPageChangeCallback(object : OnPageChangeCallback() {
56
+ override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
57
+ super.onPageScrolled(position, positionOffset, positionOffsetPixels)
58
+ UIManagerHelper.getEventDispatcherForReactTag(reactContext, host.id)?.dispatchEvent(
59
+ PageScrollEvent(host.id, position, positionOffset)
60
+ )
61
+ }
62
+
63
+ override fun onPageSelected(position: Int) {
64
+ super.onPageSelected(position)
65
+ UIManagerHelper.getEventDispatcherForReactTag(reactContext, host.id)?.dispatchEvent(
66
+ PageSelectedEvent(host.id, position)
67
+ )
68
+ }
69
+
70
+ override fun onPageScrollStateChanged(state: Int) {
71
+ super.onPageScrollStateChanged(state)
72
+ val pageScrollState: String = when (state) {
73
+ ViewPager2.SCROLL_STATE_IDLE -> "idle"
74
+ ViewPager2.SCROLL_STATE_DRAGGING -> "dragging"
75
+ ViewPager2.SCROLL_STATE_SETTLING -> "settling"
76
+ else -> throw IllegalStateException("Unsupported pageScrollState")
77
+ }
78
+ UIManagerHelper.getEventDispatcherForReactTag(reactContext, host.id)?.dispatchEvent(
79
+ PageScrollStateChangedEvent(host.id, pageScrollState)
80
+ )
81
+ }
82
+ })
83
+ UIManagerHelper.getEventDispatcherForReactTag(reactContext, host.id)?.dispatchEvent(
84
+ PageSelectedEvent(host.id, vp.currentItem)
85
+ )
86
+ }
87
+ host.addView(vp)
88
+ return host
89
+ }
90
+
91
+ override fun addView(host: NestedScrollableHost, child: View, index: Int) {
92
+ PagerViewViewManagerImpl.addView(host, child, index)
93
+ }
94
+
95
+ override fun getChildCount(parent: NestedScrollableHost) = PagerViewViewManagerImpl.getChildCount(parent)
96
+
97
+ override fun getChildAt(parent: NestedScrollableHost, index: Int): View {
98
+ return PagerViewViewManagerImpl.getChildAt(parent, index)
99
+ }
100
+
101
+ override fun removeView(parent: NestedScrollableHost, view: View) {
102
+ PagerViewViewManagerImpl.removeView(parent, view)
103
+ }
104
+
105
+ override fun removeAllViews(parent: NestedScrollableHost) {
106
+ PagerViewViewManagerImpl.removeAllViews(parent)
107
+ }
108
+
109
+ override fun removeViewAt(parent: NestedScrollableHost, index: Int) {
110
+ PagerViewViewManagerImpl.removeViewAt(parent, index)
111
+ }
112
+
113
+ override fun needsCustomLayoutForChildren(): Boolean {
114
+ return PagerViewViewManagerImpl.needsCustomLayoutForChildren()
115
+ }
116
+
117
+ @ReactProp(name = "scrollEnabled", defaultBoolean = true)
118
+ override fun setScrollEnabled(view: NestedScrollableHost?, value: Boolean) {
119
+ if (view != null) {
120
+ PagerViewViewManagerImpl.setScrollEnabled(view, value)
121
+ }
122
+ }
123
+
124
+ @ReactProp(name = "layoutDirection")
125
+ override fun setLayoutDirection(view: NestedScrollableHost?, value: String?) {
126
+ if (view != null && value != null) {
127
+ PagerViewViewManagerImpl.setLayoutDirection(view, value)
128
+ }
129
+ }
130
+
131
+ @ReactProp(name = "initialPage", defaultInt = 0)
132
+ override fun setInitialPage(view: NestedScrollableHost?, value: Int) {
133
+ if (view != null) {
134
+ PagerViewViewManagerImpl.setInitialPage(view, value)
135
+ }
136
+ }
137
+
138
+ @ReactProp(name = "orientation")
139
+ override fun setOrientation(view: NestedScrollableHost?, value: String?) {
140
+ if (view != null && value != null) {
141
+ PagerViewViewManagerImpl.setOrientation(view, value)
142
+ }
143
+ }
144
+
145
+ @ReactProp(name = "offscreenPageLimit", defaultInt = ViewPager2.OFFSCREEN_PAGE_LIMIT_DEFAULT)
146
+ override fun setOffscreenPageLimit(view: NestedScrollableHost?, value: Int) {
147
+ if (view != null) {
148
+ PagerViewViewManagerImpl.setOffscreenPageLimit(view, value)
149
+ }
150
+ }
151
+
152
+ @ReactProp(name = "pageMargin", defaultInt = 0)
153
+ override fun setPageMargin(view: NestedScrollableHost?, value: Int) {
154
+ if (view != null) {
155
+ PagerViewViewManagerImpl.setPageMargin(view, value)
156
+ }
157
+ }
158
+
159
+ @ReactProp(name = "overScrollMode")
160
+ override fun setOverScrollMode(view: NestedScrollableHost?, value: String?) {
161
+ if (view != null && value != null) {
162
+ PagerViewViewManagerImpl.setOverScrollMode(view, value)
163
+ }
164
+ }
165
+
166
+ @ReactProp(name = "overdrag")
167
+ override fun setOverdrag(view: NestedScrollableHost?, value: Boolean) {
168
+ return
169
+ }
170
+
171
+ @ReactProp(name = "keyboardDismissMode")
172
+ override fun setKeyboardDismissMode(view: NestedScrollableHost?, value: String?) {
173
+ return
174
+ }
175
+
176
+ @ReactProp(name = "scrollSensitivity", defaultInt = 2)
177
+ override fun setScrollSensitivity(host: NestedScrollableHost, value: Int) {
178
+ val view = PagerViewViewManagerImpl.getViewPager(host)
179
+ view.reduceDragSensitivity(value)
180
+ }
181
+
182
+ @ReactProp(name = "nestedScrollEnabled", defaultBoolean = false)
183
+ override fun setNestedScrollEnabled(view: NestedScrollableHost?, value: Boolean) {
184
+ // No-op on Android: NestedScrollableHost already handles nested scroll
185
+ // coordination natively. Prop registration is required for Fabric codegen
186
+ // completeness (JS prop must reach native layer without error).
187
+ }
188
+
189
+ fun goTo(root: NestedScrollableHost?, selectedPage: Int, scrollWithAnimation: Boolean) {
190
+ if (root == null) {
191
+ return
192
+ }
193
+ val view = PagerViewViewManagerImpl.getViewPager(root)
194
+ Assertions.assertNotNull(view)
195
+ val childCount = view.adapter?.itemCount
196
+ val canScroll = childCount != null && childCount > 0 && selectedPage >= 0 && selectedPage < childCount
197
+ if (canScroll) {
198
+ PagerViewViewManagerImpl.setCurrentItem(view, selectedPage, scrollWithAnimation)
199
+ }
200
+ }
201
+
202
+ override fun setPage(view: NestedScrollableHost?, selectedPage: Int) {
203
+ goTo(view, selectedPage, true)
204
+ }
205
+
206
+ override fun setPageWithoutAnimation(view: NestedScrollableHost?, selectedPage: Int) {
207
+ goTo(view, selectedPage, false)
208
+ }
209
+
210
+ override fun setScrollEnabledImperatively(view: NestedScrollableHost?, scrollEnabled: Boolean) {
211
+ if (view != null) {
212
+ PagerViewViewManagerImpl.setScrollEnabled(view, scrollEnabled)
213
+ }
214
+ }
215
+
216
+ override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Map<String, String>> {
217
+ return MapBuilder.of(
218
+ PageScrollEvent.EVENT_NAME, MapBuilder.of("registrationName", "onPageScroll"),
219
+ PageScrollStateChangedEvent.EVENT_NAME, MapBuilder.of("registrationName", "onPageScrollStateChanged"),
220
+ PageSelectedEvent.EVENT_NAME, MapBuilder.of("registrationName", "onPageSelected"))
221
+ }
222
+ }
@@ -0,0 +1,228 @@
1
+ package com.reactnativepagerview
2
+
3
+ import android.view.View
4
+ import android.view.ViewGroup
5
+ import androidx.viewpager2.widget.ViewPager2
6
+ import com.facebook.react.uimanager.PixelUtil
7
+ import android.view.Choreographer
8
+ import android.util.Log
9
+ import androidx.recyclerview.widget.RecyclerView
10
+ import java.util.WeakHashMap
11
+
12
+ object PagerViewViewManagerImpl {
13
+ private const val TAG = "PagerView"
14
+ const val NAME = "RNCViewPager"
15
+
16
+ fun getViewPager(view: NestedScrollableHost): ViewPager2 {
17
+ if (view.getChildAt(0) is ViewPager2) {
18
+ return view.getChildAt(0) as ViewPager2
19
+ } else {
20
+ throw ClassNotFoundException("Could not retrieve ViewPager2 instance")
21
+ }
22
+ }
23
+
24
+ fun setCurrentItem(view: ViewPager2, selectedTab: Int, scrollSmooth: Boolean) {
25
+ refreshViewChildrenLayout(view)
26
+ view.setCurrentItem(selectedTab, scrollSmooth)
27
+ }
28
+
29
+ private val originalTouchSlopMap = WeakHashMap<ViewPager2, Int>()
30
+
31
+ fun ViewPager2.reduceDragSensitivity(value: Int) {
32
+ try {
33
+ val recyclerViewField = ViewPager2::class.java.getDeclaredField("mRecyclerView")
34
+ recyclerViewField.isAccessible = true
35
+ val recyclerView = recyclerViewField.get(this) as? RecyclerView ?: return
36
+ val touchSlopField = RecyclerView::class.java.getDeclaredField("mTouchSlop")
37
+ touchSlopField.isAccessible = true
38
+ // Store original value per instance to prevent accumulation on re-renders
39
+ val baseSlop = originalTouchSlopMap[this] ?: (touchSlopField.get(recyclerView) as Int).also {
40
+ originalTouchSlopMap[this] = it
41
+ }
42
+ touchSlopField.set(recyclerView, baseSlop * value)
43
+ } catch (e: Exception) {
44
+ Log.w(TAG, "Failed to adjust drag sensitivity", e)
45
+ }
46
+ }
47
+
48
+ fun addView(host: NestedScrollableHost, child: View?, index: Int) {
49
+ if (child == null) {
50
+ return
51
+ }
52
+ val parent = getViewPager(host)
53
+
54
+ (parent.adapter as ViewPagerAdapter?)?.addChild(child, index);
55
+
56
+ if (parent.currentItem == index) {
57
+ // Solves https://github.com/callstack/react-native-pager-view/issues/219
58
+ // Required so ViewPager actually displays first dynamically added child
59
+ // (otherwise a white screen is shown until the next user interaction).
60
+ // https://github.com/facebook/react-native/issues/17968#issuecomment-697136929
61
+ refreshViewChildrenLayout(parent)
62
+ }
63
+
64
+ if (!host.didSetInitialIndex && host.initialIndex == index) {
65
+ host.didSetInitialIndex = true
66
+ setCurrentItem(parent, index, false)
67
+ }
68
+ }
69
+
70
+ fun getChildCount(parent: NestedScrollableHost) = getViewPager(parent).adapter?.itemCount ?: 0
71
+
72
+ fun getChildAt(parent: NestedScrollableHost, index: Int): View {
73
+ val view = getViewPager(parent)
74
+ return (view.adapter as ViewPagerAdapter?)!!.getChildAt(index)
75
+ }
76
+
77
+ fun removeView(parent: NestedScrollableHost, view: View) {
78
+ val pager = getViewPager(parent)
79
+ (pager.adapter as ViewPagerAdapter?)?.removeChild(view)
80
+
81
+ // Required so ViewPager actually animates the removed view right away (otherwise
82
+ // a white screen is shown until the next user interaction).
83
+ // https://github.com/facebook/react-native/issues/17968#issuecomment-697136929
84
+ refreshViewChildrenLayout(pager)
85
+ }
86
+
87
+ fun removeAllViews(parent: NestedScrollableHost) {
88
+ clearPendingRefreshViewChildrenLayout(parent)
89
+ val pager = getViewPager(parent)
90
+ pager.isUserInputEnabled = false
91
+ val adapter = pager.adapter as ViewPagerAdapter?
92
+ adapter?.removeAll()
93
+ }
94
+
95
+ fun removeViewAt(parent: NestedScrollableHost, index: Int) {
96
+ val pager = getViewPager(parent)
97
+ val adapter = pager.adapter as ViewPagerAdapter?
98
+
99
+ val child = adapter?.getChildAt(index)
100
+
101
+ if (child != null && child.parent != null) {
102
+ (child.parent as? ViewGroup)?.removeView(child)
103
+ }
104
+
105
+ adapter?.removeChildAt(index)
106
+
107
+ debouncedRefreshViewChildrenLayout(parent)
108
+ }
109
+
110
+ fun needsCustomLayoutForChildren(): Boolean {
111
+ return true
112
+ }
113
+
114
+ fun setScrollEnabled(host: NestedScrollableHost, value: Boolean) {
115
+ getViewPager(host).isUserInputEnabled = value
116
+ }
117
+
118
+ fun setLayoutDirection(host: NestedScrollableHost, value: String) {
119
+ val view = getViewPager(host)
120
+ when (value) {
121
+ "rtl" -> {
122
+ view.layoutDirection = View.LAYOUT_DIRECTION_RTL
123
+ }
124
+ else -> {
125
+ view.layoutDirection = View.LAYOUT_DIRECTION_LTR
126
+ }
127
+ }
128
+ }
129
+
130
+ fun setInitialPage(host: NestedScrollableHost, value: Int) {
131
+ val view = getViewPager(host)
132
+ //https://github.com/callstack/react-native-pager-view/issues/456
133
+ //Initial index should be set only once.
134
+ if (host.initialIndex === null) {
135
+ host.initialIndex = value
136
+ view.post {
137
+ host.didSetInitialIndex = true
138
+ }
139
+ }
140
+ }
141
+
142
+ fun setOrientation(host: NestedScrollableHost, value: String) {
143
+ getViewPager(host).orientation = if (value == "vertical") ViewPager2.ORIENTATION_VERTICAL else ViewPager2.ORIENTATION_HORIZONTAL
144
+ }
145
+
146
+ fun setOffscreenPageLimit(host: NestedScrollableHost, value: Int) {
147
+ getViewPager(host).offscreenPageLimit = value
148
+ }
149
+
150
+ fun setOverScrollMode(host: NestedScrollableHost, value: String) {
151
+ val child = getViewPager(host).getChildAt(0)
152
+ when (value) {
153
+ "never" -> {
154
+ child.overScrollMode = ViewPager2.OVER_SCROLL_NEVER
155
+ }
156
+ "always" -> {
157
+ child.overScrollMode = ViewPager2.OVER_SCROLL_ALWAYS
158
+ }
159
+ else -> {
160
+ child.overScrollMode = ViewPager2.OVER_SCROLL_IF_CONTENT_SCROLLS
161
+ }
162
+ }
163
+ }
164
+
165
+ fun setPageMargin(host: NestedScrollableHost, margin: Int) {
166
+ val pager = getViewPager(host)
167
+ val pageMargin = PixelUtil.toPixelFromDIP(margin.toDouble()).toInt()
168
+ /**
169
+ * Don't use MarginPageTransformer to be able to support negative margins
170
+ */
171
+ pager.setPageTransformer { page, position ->
172
+ val offset = pageMargin * position
173
+ if (pager.orientation == ViewPager2.ORIENTATION_HORIZONTAL) {
174
+ val isRTL = pager.layoutDirection == View.LAYOUT_DIRECTION_RTL
175
+ page.translationX = if (isRTL) -offset else offset
176
+ } else {
177
+ page.translationY = offset
178
+ }
179
+ }
180
+ }
181
+
182
+ private fun refreshViewChildrenLayout(view: View) {
183
+ view.post {
184
+ val adapter = (view as? ViewPager2)?.adapter as? ViewPagerAdapter
185
+ if (adapter == null || adapter.itemCount == 0) {
186
+ return@post
187
+ }
188
+ if (!view.isAttachedToWindow) {
189
+ return@post
190
+ }
191
+ try {
192
+ view.measure(
193
+ View.MeasureSpec.makeMeasureSpec(view.width, View.MeasureSpec.EXACTLY),
194
+ View.MeasureSpec.makeMeasureSpec(view.height, View.MeasureSpec.EXACTLY))
195
+ view.layout(view.left, view.top, view.right, view.bottom)
196
+ } catch (e: NullPointerException) {
197
+ // Race condition: ViewHolder detached between post and layout execution.
198
+ // Safe to ignore as RecyclerView will re-layout on next frame.
199
+ }
200
+ }
201
+ }
202
+
203
+ private fun clearPendingRefreshViewChildrenLayout(host: NestedScrollableHost) {
204
+ host.pendingRefreshFrameCallback?.let { callback ->
205
+ Choreographer.getInstance().removeFrameCallback(callback)
206
+ host.pendingRefreshFrameCallback = null
207
+ }
208
+ }
209
+
210
+ private fun debouncedRefreshViewChildrenLayout(host: NestedScrollableHost) {
211
+ clearPendingRefreshViewChildrenLayout(host)
212
+ val view = getViewPager(host)
213
+
214
+ // Fixes https://github.com/callstack/react-native-pager-view/issues/946
215
+ val adapter = (view as? ViewPager2)?.adapter as? ViewPagerAdapter
216
+ if (adapter == null || adapter.itemCount == 0) {
217
+ // Do not call refreshViewChildrenLayout on pager unmount
218
+ return
219
+ }
220
+
221
+ val callback = Choreographer.FrameCallback {
222
+ refreshViewChildrenLayout(view)
223
+ host.pendingRefreshFrameCallback = null
224
+ }
225
+ host.pendingRefreshFrameCallback = callback
226
+ Choreographer.getInstance().postFrameCallback(callback)
227
+ }
228
+ }
@@ -0,0 +1,17 @@
1
+ package com.reactnativepagerview
2
+
3
+ import com.facebook.react.ReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.uimanager.ViewManager
7
+
8
+
9
+ class PagerViewPackage : ReactPackage {
10
+ override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
11
+ return emptyList()
12
+ }
13
+
14
+ override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
15
+ return listOf(PagerViewViewManager())
16
+ }
17
+ }