Support loading and assigning scroll state from scroll view helper (#53999)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53999

Update the `ScrollViewHelper` class to support reading the scroll state from the provided `stateWrapper` and assigning it to the view implementing the `HasScrollState` interface.

This will be used in a future diff to implement scroll state loading on the scroll view component in the scroll state setter function. This enables having one implementation of the state loading for all variants of the scroll view (vertical and horizontal scroll views).

Changelog: [Internal]

Reviewed By: mdvacca

Differential Revision: D83247018

fbshipit-source-id: 44f10edab2341981b85d6ab8b83c7bea96c1e09d
This commit is contained in:
Nick Lefever
2025-10-07 19:20:15 -07:00
committed by meta-codesync[bot]
parent aa492fadab
commit aa85da33a5
4 changed files with 74 additions and 16 deletions
@@ -5636,6 +5636,7 @@ public class com/facebook/react/views/scroll/ReactHorizontalScrollView : android
public fun setOverflowInset (IIII)V
public fun setPagingEnabled (Z)V
public fun setPointerEvents (Lcom/facebook/react/uimanager/PointerEvents;)V
public fun setReactScrollViewScrollState (Lcom/facebook/react/views/scroll/ReactScrollViewHelper$ReactScrollViewScrollState;)V
public fun setRemoveClippedSubviews (Z)V
public fun setScrollEnabled (Z)V
public fun setScrollEventThrottle (I)V
@@ -5775,6 +5776,7 @@ public class com/facebook/react/views/scroll/ReactScrollView : android/widget/Sc
public fun setOverflowInset (IIII)V
public fun setPagingEnabled (Z)V
public fun setPointerEvents (Lcom/facebook/react/uimanager/PointerEvents;)V
public fun setReactScrollViewScrollState (Lcom/facebook/react/views/scroll/ReactScrollViewHelper$ReactScrollViewScrollState;)V
public fun setRemoveClippedSubviews (Z)V
public fun setScrollAwayTopPaddingEnabledUnstable (I)V
public fun setScrollEnabled (Z)V
@@ -5879,6 +5881,7 @@ public abstract interface class com/facebook/react/views/scroll/ReactScrollViewH
public abstract interface class com/facebook/react/views/scroll/ReactScrollViewHelper$HasScrollState {
public abstract fun getReactScrollViewScrollState ()Lcom/facebook/react/views/scroll/ReactScrollViewHelper$ReactScrollViewScrollState;
public abstract fun setReactScrollViewScrollState (Lcom/facebook/react/views/scroll/ReactScrollViewHelper$ReactScrollViewScrollState;)V
}
public abstract interface class com/facebook/react/views/scroll/ReactScrollViewHelper$HasSmoothScroll {
@@ -5896,18 +5899,34 @@ public abstract interface class com/facebook/react/views/scroll/ReactScrollViewH
public final class com/facebook/react/views/scroll/ReactScrollViewHelper$ReactScrollViewScrollState {
public fun <init> ()V
public fun <init> (Landroid/graphics/Point;ILandroid/graphics/Point;ZZFZ)V
public synthetic fun <init> (Landroid/graphics/Point;ILandroid/graphics/Point;ZZFZILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Landroid/graphics/Point;
public final fun component2 ()I
public final fun component3 ()Landroid/graphics/Point;
public final fun component4 ()Z
public final fun component5 ()Z
public final fun component6 ()F
public final fun component7 ()Z
public final fun copy (Landroid/graphics/Point;ILandroid/graphics/Point;ZZFZ)Lcom/facebook/react/views/scroll/ReactScrollViewHelper$ReactScrollViewScrollState;
public static synthetic fun copy$default (Lcom/facebook/react/views/scroll/ReactScrollViewHelper$ReactScrollViewScrollState;Landroid/graphics/Point;ILandroid/graphics/Point;ZZFZILjava/lang/Object;)Lcom/facebook/react/views/scroll/ReactScrollViewHelper$ReactScrollViewScrollState;
public fun equals (Ljava/lang/Object;)Z
public final fun getDecelerationRate ()F
public final fun getFinalAnimatedPositionScroll ()Landroid/graphics/Point;
public final fun getLastStateUpdateScroll ()Landroid/graphics/Point;
public final fun getScrollAwayPaddingTop ()I
public fun hashCode ()I
public final fun isCanceled ()Z
public final fun isFinished ()Z
public final fun isUpdatedByScroll ()Z
public final fun setCanceled (Z)V
public final fun setDecelerationRate (F)V
public final fun setFinalAnimatedPositionScroll (II)Lcom/facebook/react/views/scroll/ReactScrollViewHelper$ReactScrollViewScrollState;
public final fun setFinished (Z)V
public final fun setLastStateUpdateScroll (II)Lcom/facebook/react/views/scroll/ReactScrollViewHelper$ReactScrollViewScrollState;
public final fun setScrollAwayPaddingTop (I)V
public final fun setUpdatedByScroll (Z)V
public fun toString ()Ljava/lang/String;
}
public abstract interface class com/facebook/react/views/scroll/ReactScrollViewHelper$ScrollListener {
@@ -1649,6 +1649,11 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
mStateWrapper = stateWrapper;
}
@Override
public void setReactScrollViewScrollState(ReactScrollViewScrollState scrollState) {
mReactScrollViewScrollState = scrollState;
}
@Override
public ReactScrollViewScrollState getReactScrollViewScrollState() {
return mReactScrollViewScrollState;
@@ -1467,6 +1467,11 @@ public class ReactScrollView extends ScrollView
ReactScrollViewHelper.forceUpdateState(this);
}
@Override
public void setReactScrollViewScrollState(ReactScrollViewScrollState scrollState) {
mReactScrollViewScrollState = scrollState;
}
@Override
public ReactScrollViewScrollState getReactScrollViewScrollState() {
return mReactScrollViewScrollState;
@@ -26,6 +26,7 @@ import com.facebook.react.bridge.WritableNativeMap
import com.facebook.react.common.ReactConstants
import com.facebook.react.fabric.FabricUIManager
import com.facebook.react.uimanager.PixelUtil.toDIPFromPixel
import com.facebook.react.uimanager.PixelUtil.toPixelFromDIP
import com.facebook.react.uimanager.ReactClippingViewGroup
import com.facebook.react.uimanager.StateWrapper
import com.facebook.react.uimanager.UIManagerHelper
@@ -361,6 +362,10 @@ public object ReactScrollViewHelper {
return
}
val scrollState = scrollView.reactScrollViewScrollState
// User driven scrolling should disable scroll state updates coming from Fabric
scrollState.isUpdatedByScroll = true
// Dedupe events to reduce JNI traffic
if (scrollState.lastStateUpdateScroll.equals(scrollX, scrollY)) {
return
@@ -399,6 +404,29 @@ public object ReactScrollViewHelper {
}
}
@JvmStatic
internal fun <T> loadFabricScrollState(scrollView: T, stateWrapper: StateWrapper)
where T : HasScrollState?, T : HasStateWrapper?, T : ViewGroup {
if (scrollView.reactScrollViewScrollState.isUpdatedByScroll) {
return
}
val stateData = stateWrapper.stateData
if (stateData == null) {
return
}
// Assign the data loaded from the shadow node state
val scrollX = toPixelFromDIP(stateData.getDouble(CONTENT_OFFSET_LEFT)).toInt()
val scrollY = toPixelFromDIP(stateData.getDouble(CONTENT_OFFSET_TOP)).toInt()
val scrollAwayPaddingTop = toPixelFromDIP(stateData.getDouble(SCROLL_AWAY_PADDING_TOP)).toInt()
val scrollState =
scrollView.reactScrollViewScrollState.copy(scrollAwayPaddingTop = scrollAwayPaddingTop)
scrollState.setLastStateUpdateScroll(scrollX, scrollY)
scrollView.reactScrollViewScrollState = scrollState
}
@JvmStatic
public fun <T> updateStateOnScrollChanged(scrollView: T, xVelocity: Float, yVelocity: Float)
where
@@ -596,21 +624,22 @@ public object ReactScrollViewHelper {
}
}
public class ReactScrollViewScrollState {
/** Get the position after current animation is finished */
public val finalAnimatedPositionScroll: Point = Point()
/** Get the padding on the top for nav bar */
public var scrollAwayPaddingTop: Int = 0
/** Get the Fabric state of last scroll position */
public val lastStateUpdateScroll: Point = Point(-1, -1)
/** Get true if the previous animation was canceled */
public var isCanceled: Boolean = false
/** Get true if previous animation was finished */
public var isFinished: Boolean = true
/** Get true if previous animation was finished */
public var decelerationRate: Float = 0.985f
public data class ReactScrollViewScrollState(
/** Get the position after current animation is finished */
val finalAnimatedPositionScroll: Point = Point(),
/** Get the padding on the top for nav bar */
var scrollAwayPaddingTop: Int = 0,
/** Get the Fabric state of last scroll position */
val lastStateUpdateScroll: Point = Point(-1, -1),
/** Get true if the previous animation was canceled */
var isCanceled: Boolean = false,
/** Get true if previous animation was finished */
var isFinished: Boolean = true,
/** Get true if previous animation was finished */
var decelerationRate: Float = 0.985f,
/** Get true if the component submitted the state through user scrolling */
var isUpdatedByScroll: Boolean = false,
) {
/** Set the final scroll position after scrolling animation is finished */
public fun setFinalAnimatedPositionScroll(
finalAnimatedPositionScrollX: Int,
@@ -632,7 +661,7 @@ public object ReactScrollViewHelper {
public interface HasScrollState {
/** Get the scroll state for the current ScrollView */
public val reactScrollViewScrollState: ReactScrollViewScrollState
public var reactScrollViewScrollState: ReactScrollViewScrollState
}
public interface HasFlingAnimator {