77package com.scrollviewenhancer
88
99import android.graphics.Rect
10+ import android.util.Log
1011import android.view.View
1112import com.facebook.infer.annotation.Assertions
1213import com.facebook.react.bridge.*
14+ import com.facebook.react.uimanager.ReactShadowNode
15+ import com.facebook.react.uimanager.UIImplementation.LayoutUpdateListener
1316import com.facebook.react.uimanager.UIManagerHelper
14- import com.facebook.react.uimanager.common.ViewUtil
15- import com.facebook.react.views.scroll.ReactHorizontalScrollView
17+ import com.facebook.react.uimanager.UIManagerModule
1618import com.facebook.react.views.scroll.ReactScrollView
1719import com.facebook.react.views.view.ReactViewGroup
1820import java.lang.ref.WeakReference
1921
2022
21- abstract class MaintainVisibleScrollPositionHelper {
23+ abstract class MVCPHelper {
2224 class Config internal constructor(
2325 val minIndexForVisible : Int ,
2426 val autoScrollToTopThreshold : Int?
@@ -37,9 +39,9 @@ abstract class MaintainVisibleScrollPositionHelper {
3739 fun setConfig (config : Config ? ) {
3840 mConfig = config
3941 }
40- abstract fun start (): Unit
41- abstract fun stop (): Unit
42- abstract fun updateScrollPosition (): Unit
42+ abstract fun start ()
43+ abstract fun stop ()
44+ abstract fun updateScrollPosition ()
4345}
4446
4547
@@ -48,173 +50,54 @@ abstract class MaintainVisibleScrollPositionHelper {
4850 *
4951 * This uses UIManager to listen to updates and capture position of items before and after layout.
5052 */
51- class MaintainVisibleScrollPositionHelperV (
53+ class MaintainVisibleScrollPositionHelper (
5254 private val mScrollView : ReactScrollView ,
5355 private val mHorizontal : Boolean = false
54- ): UIManagerListener, MaintainVisibleScrollPositionHelper() {
56+ ): MVCPHelper(), UIManagerListener, LayoutUpdateListener {
5557 private var mFirstVisibleView: WeakReference <View >? = null
5658 private var mPrevFirstVisibleFrame: Rect ? = null
5759 private var mListening: Boolean = false
5860
59- /* *
60- * Start listening to view hierarchy updates. Should be called when this is created.
61- */
62- override fun start () {
63- if (mListening) {
64- return
65- }
66- mListening = true
67- uIManagerModule.addUIManagerEventListener(this )
68- }
69-
70- /* *
71- * Stop listening to view hierarchy updates. Should be called before this is destroyed.
72- */
73- override fun stop () {
74- if (! mListening) {
75- return
76- }
77- mListening = false
78- uIManagerModule.removeUIManagerEventListener(this )
79- }
61+ private val contentView: ReactViewGroup ?
62+ get() = mScrollView.getChildAt(0 ) as ? ReactViewGroup
8063
81- /* *
82- * Update the scroll position of the managed ScrollView. This should be called after layout
83- * has been updated.
84- */
85- override fun updateScrollPosition () {
86- if ((mConfig == null
87- ) || (mFirstVisibleView == null
88- ) || (mPrevFirstVisibleFrame == null )
89- ) {
90- return
91- }
92- val firstVisibleView: View ? = mFirstVisibleView!! .get()
93- val newFrame: Rect = Rect ()
94- firstVisibleView!! .getHitRect(newFrame)
95- if (mHorizontal) {
96- val deltaX: Int = newFrame.left - mPrevFirstVisibleFrame!! .left
97- if (deltaX != 0 ) {
98- val scrollX: Int = mScrollView.scrollX
99- mScrollView.scrollTo(scrollX + deltaX, mScrollView.scrollY)
100- mPrevFirstVisibleFrame = newFrame
101- if (mConfig!! .autoScrollToTopThreshold != null && scrollX <= mConfig!! .autoScrollToTopThreshold!! ) {
102- mScrollView.reactSmoothScrollTo(0 , mScrollView.scrollY)
103- }
104- }
105- } else {
106- val deltaY: Int = newFrame.top - mPrevFirstVisibleFrame!! .top
107- if (deltaY != 0 ) {
108- val scrollY: Int = mScrollView.scrollY
109- mScrollView.scrollTo(mScrollView.scrollX, scrollY + deltaY)
110- mPrevFirstVisibleFrame = newFrame
111- if (mConfig!! .autoScrollToTopThreshold != null && scrollY <= mConfig!! .autoScrollToTopThreshold!! ) {
112- mScrollView.reactSmoothScrollTo(mScrollView.scrollX, 0 )
113- }
114- }
115- }
116- }
117-
118- private val contentView: ReactViewGroup
119- get() = mScrollView.getChildAt(0 ) as ReactViewGroup
120-
121- private val uIManagerModule: UIManager
64+ private val uiManager: UIManager
12265 get() = Assertions .assertNotNull(
123- UIManagerHelper .getUIManager(
124- mScrollView.context as ReactContext ,
125- ViewUtil .getUIManagerType(mScrollView.id)
126- )
66+ UIManagerHelper .getUIManagerForReactTag(mScrollView.context as ReactContext , mScrollView.id)
12767 )
12868
129- private fun computeTargetView () {
130- if (mConfig == null ) {
131- return
132- }
133- val contentView: ReactViewGroup ? = contentView
134- if (contentView == null ) {
135- return
136- }
137- val currentScroll: Int = if (mHorizontal) mScrollView!! .scrollX else mScrollView!! .scrollY
138- for (i in mConfig!! .minIndexForVisible until contentView.childCount) {
139- val child: View = contentView.getChildAt(i)
140- val position: Float = if (mHorizontal) child.x else child.y
141- if (position > currentScroll || i == contentView.childCount - 1 ) {
142- mFirstVisibleView = WeakReference (child)
143- val frame: Rect = Rect ()
144- child.getHitRect(frame)
145- mPrevFirstVisibleFrame = frame
146- break
147- }
148- }
149- }
150-
151- // UIManagerListener
152- override fun willDispatchViewUpdates (uiManager : UIManager ) {
153- UiThreadUtil .runOnUiThread(
154- object : Runnable {
155- override fun run () {
156- computeTargetView()
157- }
158- })
159- }
160-
161- override fun didDispatchMountItems (uiManager : UIManager ) {
162- // noop
163- }
164-
165- override fun didScheduleMountItems (uiManager : UIManager ) {
166- // noop
167- }
168- }
169-
170- /* *
171- * Manage state for the maintainVisibleContentPosition prop.
172- *
173- * This uses UIManager to listen to updates and capture position of items before and after layout.
174- */
175- class MaintainVisibleScrollPositionHelperH (
176- private val mScrollView : ReactHorizontalScrollView ,
177- private val mHorizontal : Boolean = false
178- ): UIManagerListener, MaintainVisibleScrollPositionHelper() {
179- private var mFirstVisibleView: WeakReference <View >? = null
180- private var mPrevFirstVisibleFrame: Rect ? = null
181- private var mListening: Boolean = false
182-
18369 /* *
18470 * Start listening to view hierarchy updates. Should be called when this is created.
18571 */
18672 override fun start () {
187- if (mListening) {
188- return
189- }
73+ if (mListening) return
74+
19075 mListening = true
191- uIManagerModule.addUIManagerEventListener(this )
76+ uiManager.addUIManagerEventListener(this )
77+ (uiManager as ? UIManagerModule )?.uiImplementation?.setLayoutUpdateListener(this )
19278 }
19379
19480 /* *
19581 * Stop listening to view hierarchy updates. Should be called before this is destroyed.
19682 */
19783 override fun stop () {
198- if (! mListening) {
199- return
200- }
84+ if (! mListening) return
85+
20186 mListening = false
202- uIManagerModule .removeUIManagerEventListener(this )
87+ uiManager .removeUIManagerEventListener(this )
20388 }
20489
20590 /* *
20691 * Update the scroll position of the managed ScrollView. This should be called after layout
20792 * has been updated.
20893 */
20994 override fun updateScrollPosition () {
210- if ((mConfig == null
211- ) || (mFirstVisibleView == null
212- ) || (mPrevFirstVisibleFrame == null )
213- ) {
95+ if ((mConfig == null ) || (mFirstVisibleView == null ) || (mPrevFirstVisibleFrame == null )) {
21496 return
21597 }
98+
21699 val firstVisibleView: View ? = mFirstVisibleView!! .get()
217- val newFrame: Rect = Rect ()
100+ val newFrame = Rect ()
218101 firstVisibleView!! .getHitRect(newFrame)
219102 if (mHorizontal) {
220103 val deltaX: Int = newFrame.left - mPrevFirstVisibleFrame!! .left
@@ -239,47 +122,28 @@ class MaintainVisibleScrollPositionHelperH(
239122 }
240123 }
241124
242- private val contentView: ReactViewGroup
243- get() = mScrollView.getChildAt(0 ) as ReactViewGroup
244-
245- private val uIManagerModule: UIManager
246- get() = Assertions .assertNotNull(
247- UIManagerHelper .getUIManager(
248- mScrollView.context as ReactContext ,
249- ViewUtil .getUIManagerType(mScrollView.id)
250- )
251- )
252-
253125 private fun computeTargetView () {
254- if (mConfig == null ) {
255- return
256- }
257- val contentView: ReactViewGroup ? = contentView
258- if (contentView == null ) {
259- return
260- }
261- val currentScroll: Int = if (mHorizontal) mScrollView!! .scrollX else mScrollView!! .scrollY
262- for (i in mConfig!! .minIndexForVisible until contentView.childCount) {
263- val child: View = contentView.getChildAt(i)
264- val position: Float = if (mHorizontal) child.x else child.y
265- if (position > currentScroll || i == contentView.childCount - 1 ) {
266- mFirstVisibleView = WeakReference (child)
267- val frame: Rect = Rect ()
268- child.getHitRect(frame)
269- mPrevFirstVisibleFrame = frame
270- break
126+ if (mConfig == null ) return
127+
128+ contentView?.let { contentView ->
129+ val currentScroll: Int = if (mHorizontal) mScrollView.scrollX else mScrollView.scrollY
130+ for (i in mConfig!! .minIndexForVisible until contentView.childCount) {
131+ val child: View = contentView.getChildAt(i)
132+ val position: Float = if (mHorizontal) child.x else child.y
133+ if (position > currentScroll || i == contentView.childCount - 1 ) {
134+ mFirstVisibleView = WeakReference (child)
135+ val frame = Rect ()
136+ child.getHitRect(frame)
137+ mPrevFirstVisibleFrame = frame
138+ break
139+ }
271140 }
272141 }
273142 }
274143
275144 // UIManagerListener
276145 override fun willDispatchViewUpdates (uiManager : UIManager ) {
277- UiThreadUtil .runOnUiThread(
278- object : Runnable {
279- override fun run () {
280- computeTargetView()
281- }
282- })
146+ computeTargetView()
283147 }
284148
285149 override fun didDispatchMountItems (uiManager : UIManager ) {
@@ -289,4 +153,9 @@ class MaintainVisibleScrollPositionHelperH(
289153 override fun didScheduleMountItems (uiManager : UIManager ) {
290154 // noop
291155 }
156+
157+ // LayoutUpdateListener
158+ override fun onLayoutUpdated (p0 : ReactShadowNode <out ReactShadowNode <* >>? ) {
159+ updateScrollPosition()
160+ }
292161}
0 commit comments