react-native: Android Scrollview ContentInset/ContentOffset do not behave properly.

Please provide all the information requested. Issues that do not follow this format are likely to stall.

Description

Please provide a clear and concise description of what the bug is. Include screenshots if needed. Please test using the latest React Native release to make sure your issue has not already been fixed: https://reactnative.dev/docs/upgrading.html

Content Offset and Content Inset do not work for Android devices on React Native ScrollViews

React Native version:

Run react-native info in your terminal and copy the results here.

System: OS: macOS 10.15.7 CPU: (8) x64 Intel® Core™ i7-3770 CPU @ 3.40GHz Memory: 91.34 MB / 16.00 GB Shell: 3.2.57 - /bin/bash Binaries: Node: 8.15.1 - /usr/local/bin/node Yarn: Not Found npm: 6.13.7 - /usr/local/bin/npm Watchman: 4.9.0 - /usr/local/bin/watchman Managers: CocoaPods: 1.9.3 - /Users/Dellybro/.rvm/gems/ruby-2.6.3/bin/pod SDKs: iOS SDK: Platforms: iOS 14.1, DriverKit 19.0, macOS 10.15, tvOS 14.0, watchOS 7.0 Android SDK: Not Found IDEs: Android Studio: 3.5 AI-191.8026.42.35.5900203 Xcode: 12.1/12A7403 - /usr/bin/xcodebuild Languages: Java: 11.0.2 - /usr/bin/javac Python: 2.7.16 - /usr/bin/python npmPackages: @react-native-community/cli: Not Found react: 16.13.1 => 16.13.1 react-native: 0.63.3 => 0.63.3 react-native-macos: Not Found npmGlobalPackages: react-native: Not Found

Steps To Reproduce

Provide a detailed list of steps that reproduce the issue.

  1. Create a horizontal scrollview and attempt to use content inset and content offset. Nothing will happen

Expected Results

Describe what you expected to happen.

I would expect the same results as iOS with contentInset is moves the start and contentOffset is moves the initial starting value

Snack, code example, screenshot, or link to a repository:

Please provide a Snack (https://snack.expo.io/), a link to a repository on GitHub, or provide a minimal code example that reproduces the problem. You may provide a screenshot of the application if you think it is relevant to your bug report. Here are some tips for providing a minimal example: https://stackoverflow.com/help/mcve

https://stackoverflow.com/questions/65014040/react-native-android-scrollview-content-inset-content-offset-not-applied

Please review this stackoverflow question which has all of the information about my current situation.

Snack Expo link https://snack.expo.io/gW4iCCXOD

About this issue

  • Original URL
  • State: open
  • Created 4 years ago
  • Reactions: 10
  • Comments: 17 (2 by maintainers)

Most upvoted comments

@fabriziobertoglio1987 It seems to only fix contentOffset but not contentInset. Can confirm contentInset is not working at all on Android.

contentInset is still not working on Android. alternatively, you can pass style to contentContainerStyle. e.g. : contentContainerStyle={{ paddingBottom: 150 }}

Unfortunately this is still an issue

This is a workaround if someone is interested.

Components fix

    useEffect(()=> {
        if (scrollviewRef.current)
        scrollviewRef.current.scrollTo({...contenOffset, animated:false})
    },[contenOffset, scrollviewRef.current])

OR

react-native ScrollView Fix, this fix is for react-native: 0.68.2

edit node_modules\react-native\Libraries\Components\ScrollView

  componentDidUpdate(prevProps: Props) {
    const prevContentInsetTop = prevProps.contentInset
      ? prevProps.contentInset.top
      : 0;
    const newContentInsetTop = this.props.contentInset
      ? this.props.contentInset.top
      : 0;
    if (prevContentInsetTop !== newContentInsetTop) {
      this._scrollAnimatedValue.setOffset(newContentInsetTop || 0);
    }

    if (prevProps.contentOffset && prevProps.contentOffset.y != undefined && this.props.contentOffset && this.props.contentOffset.y != undefined && this.scrollTo) {
      this.scrollTo({...this.props.contentOffset, animated: false})
    }
    this._updateAnimatedNodeAttachment();
  }
                function Wrapper(props, ref) {
  return <ScrollView {...props} onLayout={(item) => {
                  if (props.contentOffset && props.contentOffset.y !== undefined && item.currentTarget.scrollTo) {
                    item.currentTarget.scrollTo({...props.contentOffset, animated: false})
                  }
                  if (props.onLayout)
                    props.onLayout(item)
                }} scrollViewRef={ref} />;
}

Fixed on https://github.com/facebook/react-native/commit/be260b9f479a3b55ee43d2959d2c49fd3c1eb4ac

patch-package for 0.68.2 (Expo SDK 45 users):

Patch
diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java
index e9b9941..1afbb25 100644
--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java
+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java
@@ -254,8 +254,8 @@ public class ReactScrollView extends ScrollView
   @Override
   protected void onLayout(boolean changed, int l, int t, int r, int b) {
     // Call with the present values in order to re-layout if necessary
-    // If a "pending" value has been set, we restore that value.
-    // That value gets cleared by reactScrollTo.
+     // If a "pending" content offset value has been set, we restore that value.
+    // Upon call to scrollTo, the "pending" values will be re-set.
     int scrollToX =
         pendingContentOffsetX != UNSET_CONTENT_OFFSET ? pendingContentOffsetX : getScrollX();
     int scrollToY =
@@ -954,23 +954,21 @@ public class ReactScrollView extends ScrollView
   }
 
   /**
-   * Calls `reactScrollTo` and updates state.
+   * Calls `updateFabricScrollState` and updates state.
    *
-   * <p>`reactScrollTo` changes `contentOffset` and we need to keep `contentOffset` in sync between
-   * scroll view and state. Calling raw `reactScrollTo` doesn't update state.
-   *
-   * <p>Note that while we can override scrollTo, we *cannot* override `smoothScrollTo` because it
-   * is final. See `reactSmoothScrollTo`.
+   * <p>`scrollTo` changes `contentOffset` and we need to keep `contentOffset` in sync between
+   * scroll view and state. Calling ScrollView's `scrollTo` doesn't update state.
    */
   @Override
   public void scrollTo(int x, int y) {
     super.scrollTo(x, y);
-    // The final scroll position might be different from (x, y). For example, we may need to scroll
-    // to the last item in the list, but that item cannot be move to the start position of the view.
-    final int actualX = getScrollX();
-    final int actualY = getScrollY();
-    ReactScrollViewHelper.updateFabricScrollState(this, actualX, actualY);
-    setPendingContentOffsets(actualX, actualY);
+    ReactScrollViewHelper.updateFabricScrollState(this);
+    setPendingContentOffsets(x, y);
+  }
+
+  private boolean isContentReady() {
+    View child = getChildAt(0);
+    return child != null && child.getWidth() != 0 && child.getHeight() != 0;
   }
 
   /**
@@ -981,8 +979,7 @@ public class ReactScrollView extends ScrollView
    * @param y
    */
   private void setPendingContentOffsets(int x, int y) {
-    View child = getChildAt(0);
-    if (child != null && child.getWidth() != 0 && child.getHeight() != 0) {
+    if (isContentReady()) {
       pendingContentOffsetX = UNSET_CONTENT_OFFSET;
       pendingContentOffsetY = UNSET_CONTENT_OFFSET;
     } else {

The same for me