22

I have a ScrollView that has a top section with one background colour and a bottom section with another different colour.

When a user scrolls past the content and the view bounces (elastic over-extend), how could I make it so the background is consistent with either the top or the bottom, depending on the scroll direction?

David Schumann
  • 13,380
  • 9
  • 75
  • 96
Alex Curtis
  • 5,659
  • 3
  • 28
  • 31

5 Answers5

39

I wouldn't play with the contentInset and contentOffset of the ScrollView as if your content changes, it might change the position of your scrollview.

You can do something very simple by just adding a View at the very top of your ScrollView:

// const spacerHeight = 1000;

<ScrollView>
  {Platform.OS === 'ios' && (
    <View 
      style={{
        backgroundColor: 'red',
        height: spacerHeight,
        position: 'absolute',
        top: -spacerHeight,
        left: 0,
        right: 0,
      }} 
    />
  )}
</ScrollView>
Lukas
  • 1,346
  • 7
  • 24
  • 49
alexmngn
  • 9,107
  • 19
  • 70
  • 130
  • Much simpler, thank you. I tried this after implementing the accepted answer and this feels much better. The accepted answer inexplicably caused some of my content inside the ScrollView to disappear. I think this should be the accepted answer. – Nic Nov 13 '19 at 17:17
  • 1
    As a huge added bonus with this method, you can put content in the bounce area (in my case I added a gradient. It looks so nice). – Nic Nov 13 '19 at 17:25
  • 1
    Although, this does not work with the RefreshControl when used with FlatList? – Luke Brandon Farrell Dec 27 '19 at 14:47
  • 4
    Beware that this absolute view will cover up the refresh indicator... I ended up putting it on the bottom instead – Greg T May 12 '20 at 19:04
  • simply amazing you save my day thanks – mychar Dec 02 '21 at 04:25
  • @GregT What do you mean by "on the bottom instead"? What's the difference in code between your version and the version in this answer? – alexander Apr 24 '23 at 14:10
  • @alexander This was years ago, but I set "bottom: -spacerHeight" rather than top. It was interfering with the refresh indicator. If you don't set refreshControl on your ScrollView then it shouldn't matter. – Greg T Apr 25 '23 at 20:34
  • 2
    @GregT Yeah I know! Thanks for answering anyway! I ended up using `zIndex: 1` for the `RefreshControl` – alexander Apr 26 '23 at 06:23
14

On iOS, you can render a spacer View on top of the ScrollView, and use contentInset to render it "off-screen", contentOffset to set the initial scroll position to offset the inset:

render() {
  const isIos = Platform.OS === 'ios'
  const SPACER_SIZE = 1000; //arbitrary size
  const TOP_COLOR = 'white';
  const BOTTOM_COLOR = 'papayawhip';
  return (
    <ScrollView
      style={{backgroundColor: isIos ? BOTTOM_COLOR : TOP_COLOR }}
      contentContainerStyle={{backgroundColor: TOP_COLOR}}
      contentInset={{top: -SPACER_SIZE}}
      contentOffset={{y: SPACER_SIZE}}>

      {isIos && <View style={{height: SPACER_SIZE}} />}
      //...your content here

    </ScrollView>
  );
}

Because contentInset and contentOffset are iOS only, this example is conditioned to degrade gracefully on Android.

Beau Smith
  • 33,433
  • 13
  • 94
  • 101
jevakallio
  • 35,324
  • 3
  • 105
  • 112
  • hey is there any way to change the color of the scrollbar or scroll indicator that appears on right side of the list. in case of verticle scrolling ? – Revansiddh Jan 08 '18 at 10:17
  • I may have spoke too soon… this only works on iOS. I had to condition this solution to only apply on iOS. – Beau Smith Jul 18 '18 at 02:58
  • This solution did not work so well for me due to insets/offsets messing up with my contentContainerStyle flexGrow 1 => the content did not grow properly. Check this other solution which is perhaps easier to apply into multiple places: https://stackoverflow.com/a/57597156/82609 – Sebastien Lorber Aug 21 '19 at 18:16
10

The accepted solution did not work well for me because I need to put flexGrow: 1 on the contentContainerStyle. Using insets/offsets didn't make the content grow the way I want, otherwise it worked not so bad.

I have another solution to suggest: putting a bicolor background layer under a transparent ScrollView, and add colors to your scrollview content. This way, on ios bounce, the bicolor layer under the scrollview will reveal itself.

Here's what I mean by bicolor layer (here the scrollview is empty and transparent)

enter image description here

Now if I put back the ScrollView children (which if a body with blank background, and a footer with yellow background), I get this:

enter image description here

As long as you don't bounce more than 50% of the scrollview height, you will see the appropriate background color.

Here's a component you can use to wrap your scrollview.

const AppScrollViewIOSBounceColorsWrapper = ({
  topBounceColor,
  bottomBounceColor,
  children,
  ...props
}) => {
  return (
    <View {...props} style={[{ position: 'relative' }, props.style]}>
      {children}
      <View
        style={{
          position: 'absolute',
          top: 0,
          left: 0,
          width: '100%',
          height: '100%',
          zIndex: -1, // appear under the scrollview
        }}
      >
        <View
          style={{ flex: 1, backgroundColor: topBounceColor }}
        />
        <View
          style={{ flex: 1, backgroundColor: bottomBounceColor }}
        />
      </View>
    </View>
  );
};

And here's how you use it:

  <AppScrollViewIOSBounceColorsWrapper
    style={{flex: 1}}
    topBounceColor="white"
    bottomBounceColor="yellowLancey"
  >
    <ScrollView style={{flex: 1}}>
      <WhiteBackgroundBody/>
      <YellowBackgroundFooter />
    </AppScrollView>
  </AppScrollViewIOSBounceColorsWrapper>

Make sure to NOT set a background color to the scrollview, otherwise the bicolor layer will never reveal itself (backgroundColor on contentContainerStyle is fine)

Sebastien Lorber
  • 89,644
  • 67
  • 288
  • 419
5

This is, I think the most stupid simple way i found to do it:

<ScrollView style={{backgroundColor: '#000000'}}>
  [...]
  <View style={{position: "absolute", bottom: -600, left: 0, right: 0, backgroundColor: '#FFFFFF', height: 600}}/>
</ScrollView>

You may adjust the height/bottom absolute value to your likings depending on how far you think the user could scroll.

I personally implemented that into a <ScrollBottom color={"white"}/> component for ease of use in all my ScrollViews

ttranche
  • 51
  • 1
  • 3
3

For me, the simplest solution is modification based on Sebastien Lorber answer which doesn't include wrapping, just calling it before (or after) ScrollView component:

Create component:

interface IScrollViewBackgroundLayer {
  topBounceColor: string;
  bottomBounceColor: string;
}

export const ScrollViewBackgroundLayer = ({
  topBounceColor,
  bottomBounceColor,
}: IScrollViewBackgroundLayer): ReactElement => (
  <View
    style={{
      position: 'absolute',
      top: 0,
      left: 0,
      width: '100%',
      height: '100%',
      zIndex: -1, // appear under the scrollview
    }}>
    <View style={{ flex: 1, backgroundColor: topBounceColor }} />
    <View style={{ flex: 1, backgroundColor: bottomBounceColor }} />
  </View>
);

and use it like this:

  return (
    <SafeAreaView style={styles.container}>
      <ScrollViewBackgroundLayer topBounceColor={topBounceColor} bottomBounceColor={bottomBounceColor} />
      <ScrollView>
       ...
      </ScrollView>
    </SafeAreaView>
Milan Rakos
  • 1,705
  • 17
  • 24