101

I'm using React Native and I'm having trouble retaining vertical centering of elements as soon as I introduce a ScrollView. To demonstrate, I created 3 apps with React Native Playground. All examples have 2 pages, and they can be toggled by tapping the blue button.

Example 1:

This first example shows blocks centered vertically on page 2. This happens because the container has these styles applied to it:

flex: 1,
flexDirection: 'column',
justifyContent: 'center',

https://rnplay.org/apps/lb5wSQ

However, there's a problem as soon as you switch to page 2, because the entire content of the page doesn't fit, so we need a ScrollView.

Example 2

In this example, I wrap everything inside of a ScrollView. This allows page 2 to scroll, but now I lost the vertical centering of Page 1.

https://rnplay.org/apps/DCgOgA

Example 3

In order to try and get back the vertical centering of Page 1, I apply flex: 1 to the contentContainerStyle of ScrollView. This fixes the vertical centering on page 1, but I'm no longer able to scroll all the way down to the bottom on page 2.

https://rnplay.org/apps/LSgbog

How can I fix this so that I get vertical centering of elements on page 1 yet still get ScrollView to scroll all the way to the bottom on page 2?

Johnny Oshika
  • 54,741
  • 40
  • 181
  • 275

6 Answers6

343

I solved this issue with flexGrow instead of flex

<ScrollView contentContainerStyle={{ flexGrow: 1, justifyContent: 'center' }}>

This makes the content container stretch to fill the ScrollView but does not restrict the maximum height, so you can still scroll correctly when the content extends the bounds of the ScrollView

dimpiax
  • 12,093
  • 5
  • 62
  • 45
Jake Coxon
  • 4,958
  • 1
  • 24
  • 15
64

Here is my approach:

Use an outer container with flex : 1, then the scroll view needs to have {flexGrow : 1, justifyContent : 'center'} using contentContainerStyle, not style.

<View style={styles.mainContainer}>
  <ScrollView
    contentContainerStyle={{flexGrow : 1, justifyContent : 'center'}}>
      <View style={styles.scrollViewContainer}>
        //Put your stuff here, and it will be centered vertical
      </View>
  </ScrollView>
</View> 

Styles:

const styles = StyleSheet.create({scrollView : {
height : Dimensions.get('window').height, }, mainContainer : {
flex : 1 }, scrollViewContainer : { }, })
David Schumann
  • 13,380
  • 9
  • 75
  • 96
Elden Skloss
  • 649
  • 5
  • 2
7

There's a new solution (unfortunately iOS only at the moment) - we can use centerContent prop on Scrollview.

Radek Czemerys
  • 1,060
  • 7
  • 17
5

In order to center ScrollView itself vertically, then you need to have such kind of structure:

<View style={{flex: 1}}>
  <View>
   <ScrollView .../>
  </View>
</View>

Otherwise, if you want to center ScrollView content itself, then you need the following:

<ScrollView contentContainerStyle={{flexGrow : 1, justifyContent : 'center'}} ...>
David
  • 1,365
  • 14
  • 23
4

EDIT: You can now use <ScrollView contentContainerStyle={{flexGrow: 1}}>.

For older version of React Native that do not support flexGrow, use the solution below.


The only way I have found to reliably do this for both iOS and Android is to set minHeight of the inner view to the size of the ScrollView. E.g:

<ScrollView
    style={{flex: 1}}
    contentContainerStyle={{minHeight: this.state.minHeight}}
    onLayout={event => this.setState({minHeight: event.nativeEvent.layout.height})}
>

Note: I have in-lined the styles and functions above for simplicity but you will probably want to use constant references for the unchanging values and memorise the changing style to prevent unnecessary rerenders.

const {minHeight} = this.state;
// Manually memorise changing style or use something like reselect...
if (this.lastMinHeight != minHeight) {
    this.lastMinHeight = minHeight;
    this.contentContainerStyle = {minHeight: minHeight}
}
<ScrollView
    style={styles.scrollViewStyle}
    contentContainerStyle={this.contentContainerStyle}
    onLayout={this.onScrollViewLayout}
>
SteveMellross
  • 3,404
  • 2
  • 20
  • 18
1

Why don't you wrap only second page within a ScrollView?

return (
    <ScrollView >
        <View style={styles.lipsum}>
            {this.renderButton()}
            <Text style={styles.lipsumText}>{lipsumText}</Text>
        </View>
    </ScrollView>
  );

Just modify your first example, it works like a charm :)

David Schumann
  • 13,380
  • 9
  • 75
  • 96
Michał Kisiel
  • 1,050
  • 10
  • 12
  • 1
    I suppose that's an option and it's a great suggestion, but in my real use case, there is a header on top that always appears (with logo, tag line, etc) and it also needs to be part of the ScrollView. Moving the ScrollView to a child view means that the header no longer scrolls or I have to duplicate the header in all the child views. – Johnny Oshika Sep 23 '15 at 14:15