react-native icon indicating copy to clipboard operation
react-native copied to clipboard

[Android] ScrollView clips children even with overflow: 'visible'

Open RickardZrinski opened this issue 3 years ago • 10 comments

Description

Child elements of ScrollView are clipped when positioned outside of the ScrollView bounds, even when the style and contentContainerStyle props have been given overflow: 'visible'.

React Native version:

System: OS: macOS 10.15.7 CPU: (12) x64 Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz Memory: 538.14 MB / 16.00 GB Shell: 5.7.1 - /bin/zsh Binaries: Node: 12.18.0 - ~/.nvm/versions/node/v12.18.0/bin/node Yarn: Not Found npm: 6.14.4 - ~/.nvm/versions/node/v12.18.0/bin/npm Watchman: 4.9.0 - /usr/local/bin/watchman Managers: CocoaPods: 1.10.1 - /Users/rickard/.rbenv/shims/pod SDKs: iOS SDK: Platforms: iOS 14.2, DriverKit 20.0, macOS 11.0, tvOS 14.2, watchOS 7.1 Android SDK: API Levels: 28, 29, 30 Build Tools: 28.0.3, 29.0.2, 29.0.3, 30.0.0, 30.0.2 System Images: android-30 | Google APIs Intel x86 Atom, android-30 | Google Play Intel x86 Atom Android NDK: Not Found IDEs: Android Studio: 4.0 AI-193.6911.18.40.6514223 Xcode: 12.2/12B45b - /usr/bin/xcodebuild Languages: Java: 11.0.7 - /Users/rickard/.jenv/shims/javac Python: 3.9.1 - /Users/rickard/.pyenv/shims/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

Expected Results

Given that overflow: visible is set on both the style and contentContainerStyle props on the ScrollView the overflowing children of the ScrollView should be visible.

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

https://snack.expo.io/Xw1c66W8R

RickardZrinski avatar Mar 23 '21 13:03 RickardZrinski

I'm seeing the same using react-native 0.64. A bit strange because according to the changelog it should have been fixed in 0.57.2: https://github.com/react-native-community/releases/blob/master/CHANGELOG.md#v0572

mattijsf avatar Jun 06 '21 06:06 mattijsf

Not ideal, for the time being I'm using this workaround to have a scrollable overflowing container:

<FlatList
  data={undefined}
  renderItem={undefined}
  ListHeaderComponent={
    <View style={styles.overflowing}>
      ...
    </View>
  }
/>

mattijsf avatar Jun 06 '21 08:06 mattijsf

Overriding the getClipChildren method to return false in ReactHorizontalScrollView seems to do the trick.

@Override
public boolean getClipChildren() {
  return false;
}
Before After

Here's the JSX:

<ScrollView
  horizontal={true}
  style={{overflow: 'visible'}}
  contentContainerStyle={{
    backgroundColor: 'yellow',
  }}>
  <Pressable
    onPress={() => Alert.alert('Pressed')}
    style={{
      backgroundColor: 'blue',
      height: 100,
      width: 75,
      marginTop: -50,
    }}
  />
  <Text
    style={{
      marginTop: -15,
      backgroundColor: 'transparent',
    }}>
    A very very very very very very very very very very very very very
    very very very very very very long text
  </Text>
</ScrollView>

However I don't see any other usages of getClipChildren in the RN codebase which makes me suspect it's not the right way to solve it.

The ReactViewGroup class does make a call to setClipChildren with a false argument. This was introduced in https://github.com/facebook/react-native/commit/4af4da9089e20aa84bc5660bfb37763556442a4e, which should've solved overflows for ScrollView(s).

public ReactViewGroup(Context context) {
  super(context);
  setClipChildren(false);
}

However, ReactHorizontalScrollView doesn't extend ReactViewGroup.

RickardZrinski avatar Dec 30 '21 23:12 RickardZrinski

There is a problem with ScrollView (plus all components inheriting from it) and overflow style prop, when using the RefreshControl on Android. Using React Native v0.67.3 as of writing this post.

Looking into the ScrollView.js of the react-native module you can find this code:

if (refreshControl) {
      if (Platform.OS === 'ios') {
        // ...
      } else if (Platform.OS === 'android') {
        // On Android wrap the ScrollView with a AndroidSwipeRefreshLayout.
        // Since the ScrollView is wrapped add the style props to the
        // AndroidSwipeRefreshLayout and use flex: 1 for the ScrollView.
        // Note: we should split props.style on the inner and outer props
        // however, the ScrollView still needs the baseStyle to be scrollable
        const {outer, inner} = splitLayoutProps(flattenStyle(props.style));
      
        return React.cloneElement(
          refreshControl,
          {style: StyleSheet.compose(baseStyle, outer)},
          <NativeDirectionalScrollView
            {...props}
            style={StyleSheet.compose(baseStyle, inner)}
            ref={this._setNativeRef}>
            {contentContainer}
          </NativeDirectionalScrollView>,
        );
      }
    }

Which has two problems.

  1. It splits the overflow style prop only to the inner style, resulting in that the outer component does not override overflow and uses its default value scroll. This could be solved by not splitting overflow and letting both inner and outer get that style prop.

  2. It wraps the ScrollView with AndroidSwipeRefreshLayout, which does not seem to support overflow: 'visible', even if you fix problem 1.

Attaching a overflow-test.zip, which shows two FlatLists using overflow: 'visible' next to each other and with an empty space above them. The list to the left (red items) is using RefreshControl and the one to the right (blue items) is not.

Android: Screenshot 2022-03-28 at 12 26 14

iOS: Screenshot 2022-03-28 at 12 32 09

escwald avatar Mar 28 '22 10:03 escwald

Hello, did someone managed to find a fix for this?

liviu-padurariu avatar May 18 '22 12:05 liviu-padurariu

Any news on this issue?

sungsong88 avatar Jun 10 '22 18:06 sungsong88

Also facing same issue 😔

nofacez avatar Oct 13 '22 14:10 nofacez

Also

sxyshirly avatar Oct 15 '22 08:10 sxyshirly

same thing :(

kneza23 avatar Nov 24 '22 09:11 kneza23

@kneza23 @sxyshirly @nofacez

If you are using overflow property inside a scroll view or flatlist, add this prop to your scrollview or flatlist

removeClippedSubviews(false)

On Android the default value is true which obstructs it from rendering extra content.

19BCS1114 avatar Jan 10 '23 13:01 19BCS1114

Same issue :(

gmferraz avatar Jun 29 '23 19:06 gmferraz

@kneza23 @sxyshirly @nofacez

If you are using overflow property inside a scroll view or flatlist, add this prop to your scrollview or flatlist

removeClippedSubviews(false)

On Android the default value is true which obstructs it from rendering extra content.

@19BCS1114 Your solution does not seem to work for the Author's snack: https://snack.expo.io/Xw1c66W8R

Just add the prop removeClippedSubviews={false} to the ScrollView and you'll see it does not change the UI at all. I checked on an Android device too

lgibso34 avatar Jul 17 '23 17:07 lgibso34

having this issue aswell

MCElmo avatar Sep 17 '23 22:09 MCElmo

Anything ever come from this? 3 years later still trying to find a workaround to this very problem 🤔

Andrerm124 avatar Dec 13 '23 17:12 Andrerm124

The same issue is still there. removeClippedSubviews is now false by default and setting it false doesn't do anything.

feng-yu-healthbank avatar Feb 29 '24 01:02 feng-yu-healthbank

In my case, the issue was because I set width: screenWidth in contentContainerStyle and when I remove contentContainerStyle, and add a view inside with that style, it working.

.....
        <ScrollView
          // contentContainerStyle={styles.scrollViewContent} //=> remove this
        >
          <View style={styles.scrollViewContent}> {/*add this*/}
          {/*some contents*/}
          </View>
        </ScrollView>

.....
const styles = StyleSheet.create({
  scrollViewContent: {
    flexGrow: 1,
    width: screenWidth,
    overflow: 'visible',
  },

});

bdtren avatar Mar 06 '24 07:03 bdtren