You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Fix Android MVCP anchor selection when z-index reorders children
When Fabric reorders scroll content children due to z-index, the old
computeTargetView logic picked the first child in hierarchy order whose
end position exceeded the scroll offset. That could anchor to the wrong
item, so height changes kept the bottom edge fixed instead of the top.
Scan all children and select the topmost visible anchor instead. Extend
the RNTester AppendingList example with negative z-index values and a
"Change height at id" control to reproduce the bug.
Test plan (Android):
1. Open RNTester → ScrollView → "smooth bi-directional content loading"
2. Add a few items and scroll so there are items above and below the
current item
3. Use "Change height at id" to change the height of an item in the
middle
4. Verify the top edge of that item stays in place, rather than the
bottom edge jumping
Co-authored-by: Cursor <cursoragent@cursor.com>
Copy file name to clipboardExpand all lines: packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/MaintainVisibleScrollPositionHelper.kt
+20-6Lines changed: 20 additions & 6 deletions
Original file line number
Diff line number
Diff line change
@@ -83,6 +83,7 @@ internal class MaintainVisibleScrollPositionHelper<ScrollViewT>(
83
83
return
84
84
}
85
85
isListening =false
86
+
firstVisibleViewRef =null
86
87
uIManager.removeUIManagerEventListener(this)
87
88
}
88
89
@@ -125,21 +126,34 @@ internal class MaintainVisibleScrollPositionHelper<ScrollViewT>(
125
126
val contentView = contentView ?:return
126
127
127
128
val currentScroll =if (horizontal) scrollView.scrollX else scrollView.scrollY
129
+
var firstVisibleView:View?=null
130
+
// We cannot assume that the views will be in position order because of things like z-index
131
+
// which will change the order of views in their parent. This means we need to iterate through
132
+
// the full children array and find the view with the smallest position that is bigger than
133
+
// the scroll position.
134
+
var firstVisibleViewPosition =Float.MAX_VALUE
128
135
for (i in config.minIndexForVisible until contentView.childCount) {
129
136
val child = contentView.getChildAt(i)
130
137
131
138
// Compute the position of the end of the child
132
139
val position =if (horizontal) child.x + child.width else child.y + child.height
133
140
134
141
// If the child is partially visible or this is the last child, select it as the anchor.
135
-
if (position > currentScroll || i == contentView.childCount -1) {
136
-
firstVisibleViewRef =WeakReference(child)
137
-
val frame =Rect()
138
-
child.getHitRect(frame)
139
-
prevFirstVisibleFrame = frame
140
-
break
142
+
if ((position > currentScroll && position < firstVisibleViewPosition) ||
143
+
(firstVisibleView ==null&& i == contentView.childCount -1)) {
0 commit comments