@@ -6,7 +6,6 @@ import androidx.recyclerview.widget.RecyclerView
66import androidx.viewpager2.widget.ViewPager2
77import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
88import com.facebook.infer.annotation.Assertions
9- import com.facebook.react.bridge.ReactContext
109import com.facebook.react.bridge.ReadableArray
1110import com.facebook.react.common.MapBuilder
1211import com.facebook.react.module.annotations.ReactModule
@@ -19,9 +18,10 @@ import com.reactnativepagerview.event.PageScrollEvent
1918import com.reactnativepagerview.event.PageScrollStateChangedEvent
2019import com.reactnativepagerview.event.PageSelectedEvent
2120
22-
2321@ReactModule(name = PagerViewViewManagerImpl .NAME )
24- class PagerViewViewManager : ViewGroupManager <NestedScrollableHost >(), RNCViewPagerManagerInterface<NestedScrollableHost> {
22+ class PagerViewViewManager :
23+ ViewGroupManager <NestedScrollableHost >(),
24+ RNCViewPagerManagerInterface <NestedScrollableHost > {
2525 companion object {
2626 init {
2727 if (BuildConfig .CODEGEN_MODULE_REGISTRATION != null ) {
@@ -30,60 +30,89 @@ class PagerViewViewManager : ViewGroupManager<NestedScrollableHost>(), RNCViewPa
3030 }
3131 }
3232
33- private val mDelegate: ViewManagerDelegate <NestedScrollableHost > = RNCViewPagerManagerDelegate (this )
33+ private val mDelegate: ViewManagerDelegate <NestedScrollableHost > =
34+ RNCViewPagerManagerDelegate (this )
3435
3536 override fun getDelegate () = mDelegate
3637
3738 override fun getName (): String {
3839 return PagerViewViewManagerImpl .NAME
3940 }
4041
41- override fun receiveCommand (root : NestedScrollableHost , commandId : String , args : ReadableArray ? ) {
42+ override fun receiveCommand (
43+ root : NestedScrollableHost ,
44+ commandId : String ,
45+ args : ReadableArray ?
46+ ) {
4247 mDelegate.receiveCommand(root, commandId, args)
4348 }
4449
4550 public override fun createViewInstance (reactContext : ThemedReactContext ): NestedScrollableHost {
4651 val host = NestedScrollableHost (reactContext)
4752 host.id = View .generateViewId()
48- host.layoutParams = ViewGroup .LayoutParams (ViewGroup .LayoutParams .MATCH_PARENT , ViewGroup .LayoutParams .MATCH_PARENT )
53+ host.layoutParams =
54+ ViewGroup .LayoutParams (
55+ ViewGroup .LayoutParams .MATCH_PARENT ,
56+ ViewGroup .LayoutParams .MATCH_PARENT
57+ )
4958 host.isSaveEnabled = false
5059 val vp = ViewPager2 (reactContext)
60+
61+ // Access private mRecyclerView field using reflection to disable animations
62+ val recyclerViewField = ViewPager2 ::class .java.getDeclaredField(" mRecyclerView" )
63+ recyclerViewField.isAccessible = true
64+ val recyclerView = recyclerViewField.get(vp) as RecyclerView
65+
66+ // Disable all animations to prevent layout change issues
67+ recyclerView.itemAnimator = null
68+ recyclerView.layoutTransition = null
69+
5170 vp.adapter = ViewPagerAdapter ()
52- // https://github.com/callstack/react-native-viewpager/issues/183
71+ // https://github.com/callstack/react-native-viewpager/issues/183
5372 vp.isSaveEnabled = false
5473
5574 vp.post {
56- vp.registerOnPageChangeCallback(object : OnPageChangeCallback () {
57- override fun onPageScrolled (position : Int , positionOffset : Float , positionOffsetPixels : Int ) {
58- super .onPageScrolled(position, positionOffset, positionOffsetPixels)
59- UIManagerHelper .getEventDispatcherForReactTag(reactContext, host.id)?.dispatchEvent(
60- PageScrollEvent (host.id, position, positionOffset)
61- )
62- }
63-
64- override fun onPageSelected (position : Int ) {
65- super .onPageSelected(position)
66- UIManagerHelper .getEventDispatcherForReactTag(reactContext, host.id)?.dispatchEvent(
67- PageSelectedEvent (host.id, position)
68- )
69- }
70-
71- override fun onPageScrollStateChanged (state : Int ) {
72- super .onPageScrollStateChanged(state)
73- val pageScrollState: String = when (state) {
74- ViewPager2 .SCROLL_STATE_IDLE -> " idle"
75- ViewPager2 .SCROLL_STATE_DRAGGING -> " dragging"
76- ViewPager2 .SCROLL_STATE_SETTLING -> " settling"
77- else -> throw IllegalStateException (" Unsupported pageScrollState" )
75+ vp.registerOnPageChangeCallback(
76+ object : OnPageChangeCallback () {
77+ override fun onPageScrolled (
78+ position : Int ,
79+ positionOffset : Float ,
80+ positionOffsetPixels : Int
81+ ) {
82+ super .onPageScrolled(position, positionOffset, positionOffsetPixels)
83+ UIManagerHelper .getEventDispatcherForReactTag(reactContext, host.id)
84+ ?.dispatchEvent(
85+ PageScrollEvent (host.id, position, positionOffset)
86+ )
87+ }
88+
89+ override fun onPageSelected (position : Int ) {
90+ super .onPageSelected(position)
91+ UIManagerHelper .getEventDispatcherForReactTag(reactContext, host.id)
92+ ?.dispatchEvent(PageSelectedEvent (host.id, position))
93+ }
94+
95+ override fun onPageScrollStateChanged (state : Int ) {
96+ super .onPageScrollStateChanged(state)
97+ val pageScrollState: String =
98+ when (state) {
99+ ViewPager2 .SCROLL_STATE_IDLE -> " idle"
100+ ViewPager2 .SCROLL_STATE_DRAGGING -> " dragging"
101+ ViewPager2 .SCROLL_STATE_SETTLING -> " settling"
102+ else ->
103+ throw IllegalStateException (
104+ " Unsupported pageScrollState"
105+ )
106+ }
107+ UIManagerHelper .getEventDispatcherForReactTag(reactContext, host.id)
108+ ?.dispatchEvent(
109+ PageScrollStateChangedEvent (host.id, pageScrollState)
110+ )
111+ }
78112 }
79- UIManagerHelper .getEventDispatcherForReactTag(reactContext, host.id)?.dispatchEvent(
80- PageScrollStateChangedEvent (host.id, pageScrollState)
81- )
82- }
83- })
84- UIManagerHelper .getEventDispatcherForReactTag(reactContext, host.id)?.dispatchEvent(
85- PageSelectedEvent (host.id, vp.currentItem)
86113 )
114+ UIManagerHelper .getEventDispatcherForReactTag(reactContext, host.id)
115+ ?.dispatchEvent(PageSelectedEvent (host.id, vp.currentItem))
87116 }
88117 host.addView(vp)
89118 return host
@@ -95,16 +124,78 @@ class PagerViewViewManager : ViewGroupManager<NestedScrollableHost>(), RNCViewPa
95124 }
96125
97126 override fun onDropViewInstance (view : NestedScrollableHost ) {
98- val vp = view.getChildAt(0 ) as ? ViewPager2
99- val recyclerView = vp?.getChildAt(0 ) as ? RecyclerView
100- recyclerView?.stopScroll()
101127 try {
102- // Clear adapter to prevent post-teardown fling callbacks.
103- // setAdapter(null) internally calls removeAndRecycleAllViews which
104- // can throw if views are still attached during mid-scroll teardown.
105- recyclerView?.adapter = null
106- } catch (_: IllegalArgumentException ) {
107- // Safe to ignore during teardown — view is being destroyed
128+ val viewPager = PagerViewViewManagerImpl .getViewPager(view)
129+ // Access private mRecyclerView field using reflection
130+ val recyclerViewField = ViewPager2 ::class .java.getDeclaredField(" mRecyclerView" )
131+ recyclerViewField.isAccessible = true
132+ val recyclerView = recyclerViewField.get(viewPager) as RecyclerView
133+
134+ // Stop any scroll/drag in progress first
135+ recyclerView.stopScroll()
136+
137+ // Suppress layout to prevent any pending layout operations from executing
138+ recyclerView.suppressLayout(true )
139+
140+ // Access and stop the ViewFlinger directly via reflection - do this early
141+ try {
142+ val viewFlingerField = RecyclerView ::class .java.getDeclaredField(" mViewFlinger" )
143+ viewFlingerField.isAccessible = true
144+ val viewFlinger = viewFlingerField.get(recyclerView) as ? Runnable
145+ viewFlinger?.let {
146+ recyclerView.removeCallbacks(it)
147+ // Also try to stop it via reflection if it has a stop method
148+ try {
149+ val stopMethod = viewFlinger.javaClass.getDeclaredMethod(" stop" )
150+ stopMethod.isAccessible = true
151+ stopMethod.invoke(viewFlinger)
152+ } catch (ignored: Exception ) {}
153+ }
154+ } catch (ignored: Exception ) {
155+ // ViewFlinger reflection may fail on some versions
156+ }
157+
158+ // Clear the recycler's scrap views before clearing adapter
159+ try {
160+ val recyclerField = RecyclerView ::class .java.getDeclaredField(" mRecycler" )
161+ recyclerField.isAccessible = true
162+ val recycler = recyclerField.get(recyclerView)
163+
164+ // Clear scrap heaps
165+ val clearScrapMethod = recycler.javaClass.getDeclaredMethod(" clear" )
166+ clearScrapMethod.isAccessible = true
167+ clearScrapMethod.invoke(recycler)
168+ } catch (ignored: Exception ) {
169+ // Recycler reflection may fail on some versions
170+ }
171+
172+ // Clear any pending animations and layout transitions
173+ recyclerView.clearAnimation()
174+ recyclerView.itemAnimator = null
175+ recyclerView.layoutTransition = null
176+ recyclerView.recycledViewPool.clear()
177+
178+ // Remove all pending operations and choreographer callbacks
179+ recyclerView.removeCallbacks(null )
180+
181+ // Try to clear any pending adapter updates
182+ try {
183+ val adapterHelperField = RecyclerView ::class .java.getDeclaredField(" mAdapterHelper" )
184+ adapterHelperField.isAccessible = true
185+ val adapterHelper = adapterHelperField.get(recyclerView)
186+ val resetMethod = adapterHelper.javaClass.getDeclaredMethod(" reset" )
187+ resetMethod.isAccessible = true
188+ resetMethod.invoke(adapterHelper)
189+ } catch (ignored: Exception ) {}
190+
191+ // Clear the adapter to prevent recycling during teardown
192+ viewPager.adapter = null
193+
194+ // Unsuppress layout after clearing adapter (won't do anything as view is being
195+ // destroyed)
196+ recyclerView.suppressLayout(false )
197+ } catch (e: Exception ) {
198+ // View might already be in an invalid state
108199 }
109200 super .onDropViewInstance(view)
110201 }
@@ -113,7 +204,8 @@ class PagerViewViewManager : ViewGroupManager<NestedScrollableHost>(), RNCViewPa
113204 PagerViewViewManagerImpl .addView(host, child, index)
114205 }
115206
116- override fun getChildCount (parent : NestedScrollableHost ) = PagerViewViewManagerImpl .getChildCount(parent)
207+ override fun getChildCount (parent : NestedScrollableHost ) =
208+ PagerViewViewManagerImpl .getChildCount(parent)
117209
118210 override fun getChildAt (parent : NestedScrollableHost , index : Int ): View {
119211 return PagerViewViewManagerImpl .getChildAt(parent, index)
@@ -204,7 +296,11 @@ class PagerViewViewManager : ViewGroupManager<NestedScrollableHost>(), RNCViewPa
204296 val view = PagerViewViewManagerImpl .getViewPager(root)
205297 Assertions .assertNotNull(view)
206298 val childCount = view.adapter?.itemCount
207- val canScroll = childCount != null && childCount > 0 && selectedPage >= 0 && selectedPage < childCount
299+ val canScroll =
300+ childCount != null &&
301+ childCount > 0 &&
302+ selectedPage >= 0 &&
303+ selectedPage < childCount
208304 if (canScroll) {
209305 PagerViewViewManagerImpl .setCurrentItem(view, selectedPage, scrollWithAnimation)
210306 }
@@ -224,10 +320,15 @@ class PagerViewViewManager : ViewGroupManager<NestedScrollableHost>(), RNCViewPa
224320 }
225321 }
226322
227- override fun getExportedCustomDirectEventTypeConstants (): MutableMap <String , Map <String , String >> {
323+ override fun getExportedCustomDirectEventTypeConstants ():
324+ MutableMap <String , Map <String , String >> {
228325 return MapBuilder .of(
229- PageScrollEvent .EVENT_NAME , MapBuilder .of(" registrationName" , " onPageScroll" ),
230- PageScrollStateChangedEvent .EVENT_NAME , MapBuilder .of(" registrationName" , " onPageScrollStateChanged" ),
231- PageSelectedEvent .EVENT_NAME , MapBuilder .of(" registrationName" , " onPageSelected" ))
326+ PageScrollEvent .EVENT_NAME ,
327+ MapBuilder .of(" registrationName" , " onPageScroll" ),
328+ PageScrollStateChangedEvent .EVENT_NAME ,
329+ MapBuilder .of(" registrationName" , " onPageScrollStateChanged" ),
330+ PageSelectedEvent .EVENT_NAME ,
331+ MapBuilder .of(" registrationName" , " onPageSelected" )
332+ )
232333 }
233334}
0 commit comments