diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 0d2d365d92b..4367487af7f 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -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 ()V + public fun (Landroid/graphics/Point;ILandroid/graphics/Point;ZZFZ)V + public synthetic fun (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 { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java index 63491c9d1c5..10a701a73f0 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java @@ -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; diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java index 69b5347f04c..d6951ed8436 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java @@ -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; diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.kt index 4621ccaf8ae..7e86543794f 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.kt @@ -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 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 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 {