Skip to content

Commit 59e69c7

Browse files
committed
Android: preserve NestedScrollView fling nested-scroll lifecycle
1 parent 1a46df5 commit 59e69c7

2 files changed

Lines changed: 45 additions & 16 deletions

File tree

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactNestedScrollView.kt

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<2f9a2ea0c223b08d9d1ea4517fd9671b>>
7+
* @generated SignedSource<<d6cc97a31d9a0fa0ca18d66cba6d5be9>>
88
*/
99

1010
/**
@@ -706,21 +706,6 @@ constructor(context: Context, private val fpsListener: FpsListener? = null) :
706706

707707
if (pagingEnabled) {
708708
flingAndSnap(correctedVelocityY)
709-
} else if (scroller != null) {
710-
val scrollWindowHeight = height - paddingBottom - paddingTop
711-
scroller.fling(
712-
scrollX, // startX
713-
scrollY, // startY
714-
0, // velocityX
715-
correctedVelocityY, // velocityY
716-
0, // minX
717-
0, // maxX
718-
0, // minY
719-
Int.MAX_VALUE, // maxY
720-
0, // overX
721-
scrollWindowHeight / 2, // overY
722-
)
723-
postInvalidateOnAnimation()
724709
} else {
725710
super.fling(correctedVelocityY)
726711
}

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/generate-nested-scroll-view.js

100755100644
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,46 @@ function replaceCopyrightHeader(content, sourceFile) {
103103
return content.replace(COPYRIGHT_HEADER_PATTERN, generatedHeader(sourceFile));
104104
}
105105

106+
/**
107+
* Keep NestedScrollView in charge of the non-paging fling lifecycle.
108+
*
109+
* ReactScrollView intentionally drives its reflected OverScroller directly. For
110+
* the generated AndroidX variant, doing the same bypasses NestedScrollView.fling()
111+
* and therefore its TYPE_NON_TOUCH nested-scroll lifecycle.
112+
*/
113+
function replaceNestedScrollViewFling(content) {
114+
const scrollerFlingBranch = ` } else if (scroller != null) {
115+
val scrollWindowHeight = height - paddingBottom - paddingTop
116+
scroller.fling(
117+
scrollX, // startX
118+
scrollY, // startY
119+
0, // velocityX
120+
correctedVelocityY, // velocityY
121+
0, // minX
122+
0, // maxX
123+
0, // minY
124+
Int.MAX_VALUE, // maxY
125+
0, // overX
126+
scrollWindowHeight / 2, // overY
127+
)
128+
postInvalidateOnAnimation()
129+
} else {
130+
super.fling(correctedVelocityY)
131+
}`;
132+
const nestedScrollFlingBranch = ` } else {
133+
super.fling(correctedVelocityY)
134+
}`;
135+
const occurrenceCount = content.split(scrollerFlingBranch).length - 1;
136+
137+
if (occurrenceCount !== 1) {
138+
throw new Error(
139+
`Expected exactly one ReactScrollView fling scroller branch; found ${occurrenceCount}.`,
140+
);
141+
}
142+
143+
return content.replace(scrollerFlingBranch, nestedScrollFlingBranch);
144+
}
145+
106146
function removePublicModifiers(content) {
107147
return content.replace(KOTLIN_DECLARATION_PATTERN, '');
108148
}
@@ -128,6 +168,10 @@ function transformScrollView(content) {
128168
// Replace ReactScrollView with ReactNestedScrollView
129169
content = replaceClassNames(content);
130170

171+
// Unlike android.widget.ScrollView, AndroidX NestedScrollView owns the nested-scroll
172+
// lifecycle of a fling. Preserve that lifecycle in the generated variant.
173+
content = replaceNestedScrollViewFling(content);
174+
131175
// Make the class internal to keep it out of the public API
132176
content = content.replace(
133177
'public open class ReactNestedScrollView',

0 commit comments

Comments
 (0)