@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.
- package/LICENSE +21 -0
- package/README.md +306 -0
- package/android/Android.mk +45 -0
- package/android/build.gradle +237 -0
- package/android/debug.keystore +0 -0
- package/android/gradle.properties +5 -0
- package/android/registration.cpp +18 -0
- package/android/src/main/AndroidManifest.xml +4 -0
- package/android/src/main/java/com/reactnativepagerview/NestedScrollableHost.kt +148 -0
- package/android/src/main/java/com/reactnativepagerview/PagerViewViewManager.kt +222 -0
- package/android/src/main/java/com/reactnativepagerview/PagerViewViewManagerImpl.kt +228 -0
- package/android/src/main/java/com/reactnativepagerview/PagerViewViewPackage.kt +17 -0
- package/android/src/main/java/com/reactnativepagerview/ViewPagerAdapter.kt +121 -0
- package/android/src/main/java/com/reactnativepagerview/ViewPagerViewHolder.kt +21 -0
- package/android/src/main/java/com/reactnativepagerview/event/PageScrollEvent.kt +47 -0
- package/android/src/main/java/com/reactnativepagerview/event/PageScrollStateChangedEvent.kt +34 -0
- package/android/src/main/java/com/reactnativepagerview/event/PageSelectedEvent.kt +38 -0
- package/ios/PagerView.xcodeproj/project.pbxproj +274 -0
- package/ios/RCTOnPageScrollEvent.h +14 -0
- package/ios/RCTOnPageScrollEvent.m +60 -0
- package/ios/RNCPagerViewComponentView.h +11 -0
- package/ios/RNCPagerViewComponentView.mm +704 -0
- package/lib/module/PagerView.js +136 -0
- package/lib/module/PagerView.js.map +1 -0
- package/lib/module/PagerViewNativeComponent.ts +82 -0
- package/lib/module/codegen-types.d.js +2 -0
- package/lib/module/codegen-types.d.js.map +1 -0
- package/lib/module/index.js +6 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/usePagerView.js +106 -0
- package/lib/module/usePagerView.js.map +1 -0
- package/lib/module/utils.js +27 -0
- package/lib/module/utils.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/PagerView.d.ts +70 -0
- package/lib/typescript/src/PagerView.d.ts.map +1 -0
- package/lib/typescript/src/PagerViewNativeComponent.d.ts +51 -0
- package/lib/typescript/src/PagerViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +10 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/usePagerView.d.ts +38 -0
- package/lib/typescript/src/usePagerView.d.ts.map +1 -0
- package/lib/typescript/src/utils.d.ts +3 -0
- package/lib/typescript/src/utils.d.ts.map +1 -0
- package/package.json +101 -0
- package/react-native-pager-view.podspec +20 -0
- package/src/PagerView.tsx +170 -0
- package/src/PagerViewNativeComponent.ts +82 -0
- package/src/codegen-types.d.ts +28 -0
- package/src/index.tsx +27 -0
- package/src/usePagerView.ts +148 -0
- 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
|
+
}
|