diff --git a/packages/react-native/Libraries/Components/ScrollView/ScrollViewNativeComponent.js b/packages/react-native/Libraries/Components/ScrollView/ScrollViewNativeComponent.js index 82f9c93baf348c..08a6fee827d4d2 100644 --- a/packages/react-native/Libraries/Components/ScrollView/ScrollViewNativeComponent.js +++ b/packages/react-native/Libraries/Components/ScrollView/ScrollViewNativeComponent.js @@ -39,6 +39,9 @@ export const __INTERNAL_VIEW_CONFIG: PartialViewConfig = }, }, validAttributes: { + contentInset: { + diff: require('../../Utilities/differ/insetsDiffer').default, + }, contentOffset: { diff: require('../../Utilities/differ/pointsDiffer').default, }, 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 71acfa6288b2b5..807391124f32e9 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 @@ -1109,6 +1109,54 @@ public void setContentOffset(ReadableMap value) { } } + public void setContentInset(ReadableMap insets) { + int left = 0; + int top = 0; + int right = 0; + int bottom = 0; + if (insets != null) { + if (insets.hasKey("left")) { + left = (int) PixelUtil.toPixelFromDIP(insets.getDouble("left")); + } + if (insets.hasKey("top")) { + top = (int) PixelUtil.toPixelFromDIP(insets.getDouble("top")); + } + if (insets.hasKey("right")) { + right = (int) PixelUtil.toPixelFromDIP(insets.getDouble("right")); + } + if (insets.hasKey("bottom")) { + bottom = (int) PixelUtil.toPixelFromDIP(insets.getDouble("bottom")); + } + } + + setPadding(left, top, right, bottom); + // simulate iOS behavior + setClipToPadding(false); + + // If clipping of subviews is enabled, update the clipping rect + if (mRemoveClippedSubviews) { + updateClippingRect(); + } + + // Force a layout pass to ensure the new padding is applied. + requestLayout(); + invalidate(); + + // Post a runnable to adjust the scroll position if needed. + // (Using post() ensures this happens after the layout pass.) + post(new Runnable() { + @Override + public void run() { + int maxScrollY = getMaxScrollY(); + // If the current scroll offset is now beyond the new maximum, + // adjust it to the new maximum. + if (getScrollY() > maxScrollY) { + scrollTo(getScrollX(), maxScrollY); + } + } + }); + } + /** * Calls `smoothScrollTo` and updates state. * diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.java index b9260c5ca5a627..0862c8e5083cd4 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.java @@ -327,6 +327,11 @@ public void setContentOffset(ReactScrollView view, ReadableMap value) { view.setContentOffset(value); } + @ReactProp(name = "contentInset") + public void setContentInset(ReactScrollView view, ReadableMap value) { + view.setContentInset(value); + } + @ReactProp(name = "maintainVisibleContentPosition") public void setMaintainVisibleContentPosition(ReactScrollView view, ReadableMap value) { if (value != null) {