From 454b91e30af8e6d2b1d62e640c20c5df7f0dce51 Mon Sep 17 00:00:00 2001 From: Amber Won Date: Mon, 30 Mar 2026 11:17:04 -0700 Subject: [PATCH] Adjust padding for bottom inset (#56277) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/56277 Changelog: [Internal] For scrollaway, we additionally need to add a bottom inset to the tab's ReactScrollView. We can do so by setting the bottom padding. This diff does that and also refreshes some of the react API/code to work altogether Reviewed By: javache Differential Revision: D97512076 --- .../ReactAndroid/api/ReactAndroid.api | 23 +++++++++++-------- .../views/scroll/ReactNestedScrollView.java | 19 ++++++++------- .../react/views/scroll/ReactScrollView.java | 17 ++++++++------ .../views/scroll/ReactScrollViewHelper.kt | 15 +++++++++++- 4 files changed, 48 insertions(+), 26 deletions(-) diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 8871af16dbc1..4a4acf241636 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -5735,8 +5735,8 @@ public class com/facebook/react/views/scroll/ReactScrollView : android/widget/Sc 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 setScrollAwayTopPaddingEnabledUnstable (IZ)V + public fun setScrollAwayPaddingEnabledUnstable (II)V + public fun setScrollAwayPaddingEnabledUnstable (IIZ)V public fun setScrollEnabled (Z)V public fun setScrollEventThrottle (I)V public fun setScrollPerfTag (Ljava/lang/String;)V @@ -5858,21 +5858,23 @@ 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 fun (Landroid/graphics/Point;IILandroid/graphics/Point;ZZFZ)V + public synthetic fun (Landroid/graphics/Point;IILandroid/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 component3 ()I + public final fun component4 ()Landroid/graphics/Point; 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 final fun component6 ()Z + public final fun component7 ()F + public final fun component8 ()Z + public final fun copy (Landroid/graphics/Point;IILandroid/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;IILandroid/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 getScrollAwayPaddingBottom ()I public final fun getScrollAwayPaddingTop ()I public fun hashCode ()I public final fun isCanceled ()Z @@ -5883,6 +5885,7 @@ public final class com/facebook/react/views/scroll/ReactScrollViewHelper$ReactSc 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 setScrollAwayPaddingBottom (I)V public final fun setScrollAwayPaddingTop (I)V public final fun setUpdatedByScroll (Z)V public fun toString ()Ljava/lang/String; diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactNestedScrollView.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactNestedScrollView.java index f57461bea210..f09d335164fb 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactNestedScrollView.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactNestedScrollView.java @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<> */ /** @@ -1527,11 +1527,12 @@ public void setBorderStyle(@Nullable String style) { * and that you are **not** overriding the NestedScrollView content view to pass in a `translateY` * style. `translateY` must never be set from ReactJS while using this feature! */ - public void setScrollAwayTopPaddingEnabledUnstable(int topPadding) { - setScrollAwayTopPaddingEnabledUnstable(topPadding, true); + public void setScrollAwayPaddingEnabledUnstable(int topPadding, int bottomPadding) { + setScrollAwayPaddingEnabledUnstable(topPadding, bottomPadding, true); } - public void setScrollAwayTopPaddingEnabledUnstable(int topPadding, boolean updateState) { + public void setScrollAwayPaddingEnabledUnstable( + int topPadding, int bottomPadding, boolean updateState) { int count = getChildCount(); Assertions.assertCondition( @@ -1548,17 +1549,18 @@ public void setScrollAwayTopPaddingEnabledUnstable(int topPadding, boolean updat // Add the topPadding value as the bottom padding for the NestedScrollView. // Otherwise, we'll push down the contents of the scroll view down too // far off screen. - setPadding(0, 0, 0, topPadding); + setPadding(0, 0, 0, topPadding + bottomPadding); } if (updateState) { - updateScrollAwayState(topPadding); + updateScrollAwayState(topPadding, bottomPadding); } setRemoveClippedSubviews(mRemoveClippedSubviews); } - private void updateScrollAwayState(int scrollAwayPaddingTop) { + private void updateScrollAwayState(int scrollAwayPaddingTop, int scrollAwayPaddingBottom) { getReactScrollViewScrollState().setScrollAwayPaddingTop(scrollAwayPaddingTop); + getReactScrollViewScrollState().setScrollAwayPaddingBottom(scrollAwayPaddingBottom); ReactScrollViewHelper.forceUpdateState(this); } @@ -1567,7 +1569,8 @@ public void setReactScrollViewScrollState(ReactScrollViewScrollState scrollState mReactScrollViewScrollState = scrollState; if (ReactNativeFeatureFlags.enableViewCulling() || ReactNativeFeatureFlags.useTraitHiddenOnAndroid()) { - setScrollAwayTopPaddingEnabledUnstable(scrollState.getScrollAwayPaddingTop(), false); + setScrollAwayPaddingEnabledUnstable( + scrollState.getScrollAwayPaddingTop(), scrollState.getScrollAwayPaddingBottom(), false); Point scrollPosition = scrollState.getLastStateUpdateScroll(); scrollTo(scrollPosition.x, scrollPosition.y); 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 d2487b03107c..cd775c6741da 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 @@ -1519,11 +1519,12 @@ public void setBorderStyle(@Nullable String style) { * and that you are **not** overriding the ScrollView content view to pass in a `translateY` * style. `translateY` must never be set from ReactJS while using this feature! */ - public void setScrollAwayTopPaddingEnabledUnstable(int topPadding) { - setScrollAwayTopPaddingEnabledUnstable(topPadding, true); + public void setScrollAwayPaddingEnabledUnstable(int topPadding, int bottomPadding) { + setScrollAwayPaddingEnabledUnstable(topPadding, bottomPadding, true); } - public void setScrollAwayTopPaddingEnabledUnstable(int topPadding, boolean updateState) { + public void setScrollAwayPaddingEnabledUnstable( + int topPadding, int bottomPadding, boolean updateState) { int count = getChildCount(); Assertions.assertCondition( @@ -1540,17 +1541,18 @@ public void setScrollAwayTopPaddingEnabledUnstable(int topPadding, boolean updat // Add the topPadding value as the bottom padding for the ScrollView. // Otherwise, we'll push down the contents of the scroll view down too // far off screen. - setPadding(0, 0, 0, topPadding); + setPadding(0, 0, 0, topPadding + bottomPadding); } if (updateState) { - updateScrollAwayState(topPadding); + updateScrollAwayState(topPadding, bottomPadding); } setRemoveClippedSubviews(mRemoveClippedSubviews); } - private void updateScrollAwayState(int scrollAwayPaddingTop) { + private void updateScrollAwayState(int scrollAwayPaddingTop, int scrollAwayPaddingBottom) { getReactScrollViewScrollState().setScrollAwayPaddingTop(scrollAwayPaddingTop); + getReactScrollViewScrollState().setScrollAwayPaddingBottom(scrollAwayPaddingBottom); ReactScrollViewHelper.forceUpdateState(this); } @@ -1559,7 +1561,8 @@ public void setReactScrollViewScrollState(ReactScrollViewScrollState scrollState mReactScrollViewScrollState = scrollState; if (ReactNativeFeatureFlags.enableViewCulling() || ReactNativeFeatureFlags.useTraitHiddenOnAndroid()) { - setScrollAwayTopPaddingEnabledUnstable(scrollState.getScrollAwayPaddingTop(), false); + setScrollAwayPaddingEnabledUnstable( + scrollState.getScrollAwayPaddingTop(), scrollState.getScrollAwayPaddingBottom(), false); Point scrollPosition = scrollState.getLastStateUpdateScroll(); scrollTo(scrollPosition.x, scrollPosition.y); 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 8839a6810022..b5ac1d972947 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 @@ -44,6 +44,7 @@ public object ReactScrollViewHelper { private const val CONTENT_OFFSET_LEFT = "contentOffsetLeft" private const val CONTENT_OFFSET_TOP = "contentOffsetTop" private const val SCROLL_AWAY_PADDING_TOP = "scrollAwayPaddingTop" + private const val SCROLL_AWAY_PADDING_BOTTOM = "scrollAwayPaddingBottom" public const val MOMENTUM_DELAY: Long = 20 public const val OVER_SCROLL_ALWAYS: String = "always" @@ -372,6 +373,7 @@ public object ReactScrollViewHelper { where T : HasScrollState?, T : HasStateWrapper?, T : ViewGroup { val scrollState = scrollView.reactScrollViewScrollState val scrollAwayPaddingTop = scrollState.scrollAwayPaddingTop + val scrollAwayPaddingBottom = scrollState.scrollAwayPaddingBottom val scrollPos = scrollState.lastStateUpdateScroll val scrollX = scrollPos.x val scrollY = scrollPos.y @@ -393,6 +395,10 @@ public object ReactScrollViewHelper { SCROLL_AWAY_PADDING_TOP, toDIPFromPixel(scrollAwayPaddingTop.toFloat()).toDouble(), ) + newStateData.putDouble( + SCROLL_AWAY_PADDING_BOTTOM, + toDIPFromPixel(scrollAwayPaddingBottom.toFloat()).toDouble(), + ) stateWrapper.updateState(newStateData) } } @@ -413,9 +419,14 @@ public object ReactScrollViewHelper { 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 scrollAwayPaddingBottom = + toPixelFromDIP(stateData.getDouble(SCROLL_AWAY_PADDING_BOTTOM)).toInt() val scrollState = - scrollView.reactScrollViewScrollState.copy(scrollAwayPaddingTop = scrollAwayPaddingTop) + scrollView.reactScrollViewScrollState.copy( + scrollAwayPaddingTop = scrollAwayPaddingTop, + scrollAwayPaddingBottom = scrollAwayPaddingBottom, + ) scrollState.setLastStateUpdateScroll(scrollX, scrollY) scrollView.reactScrollViewScrollState = scrollState } @@ -622,6 +633,8 @@ public object ReactScrollViewHelper { val finalAnimatedPositionScroll: Point = Point(), /** Get the padding on the top for nav bar */ var scrollAwayPaddingTop: Int = 0, + /** Get the padding on the bottom for tab bar */ + var scrollAwayPaddingBottom: Int = 0, /** Get the Fabric state of last scroll position */ val lastStateUpdateScroll: Point = Point(-1, -1), /** Get true if the previous animation was canceled */