Search code examples
androidreact-nativeandroid-scrollviewhorizontalscrollview

How can I have nested horizontal ScrollViews in React Native?


I saw that React Native offers nested vertical scroll-views from React Native Nested ScrollView Can`t Scroll on Android Device

The problem is that it doesn't seem to work on nested Horizontal ScrollViews on Android. See this code for an example:

https://snack.expo.io/@harrytravelchime/broken-horizontal-scroll

import React from 'react';
import _ from 'lodash';
import { View, ScrollView, StyleSheet, Text, SafeAreaView } from 'react-native';

export default class App extends React.PureComponent {
  render() {
    return (
      <SafeAreaView style={styles.container}>
        <ScrollView
          style={{ height: '100%', width: '100%' }}
          horizontal
          nestedScrollEnabled
        >
          <View style={{ flexDirection: 'row' }}>
            <ScrollView
              style={{ width: 200, height: '100%' }}
              horizontal
              nestedScrollEnabled
            >
              <View style={{ flexDirection: 'row' }}>
                {_.times(200, n => (
                  <View key={1000 + n} style={{ marginRight: 10 }}>
                    <Text>{1000 + n}</Text>
                  </View>
                ))}
              </View>
            </ScrollView>
            {_.times(200, n => (
              <View key={n} style={{ marginRight: 10 }}>
                <Text>{n}</Text>
              </View>
            ))}
          </View>
        </ScrollView>
      </SafeAreaView>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'column',
    justifyContent: 'center',
    alignItems: 'stretch',
    paddingVertical: 50,
  },
});

On the other hand, the same code except with vertical scrolling totally works: https://snack.expo.io/@harrytravelchime/working-vertical-scroll

Is there a way to make nested horizontal scrolling work?


Solution

  • One solution that I've come up with is to have a TouchableWithoutFeedback that tracks any touch by the user within the inner ScrollView. As soon as it detects a touch, it disables scrolling on the outer ScrollView, which would cause the inner ScrollView to receive events.

    The main changes from the above code are:

    1. Added state with outerScrollViewScrollEnabled
    2. When the inner ScrollView is touched via the TouchableWithoutFeedback, change that state
    3. Make the outer ScrollView's scrollEnabled depend on that
    import React from "react";
    import _ from "lodash";
    import {
      View,
      ScrollView,
      StyleSheet,
      Text,
      SafeAreaView,
      TouchableWithoutFeedback
    } from "react-native";
    
    interface State {
      outerScrollViewScrollEnabled: boolean;
    }
    
    export default class App extends React.PureComponent {
      state = { outerScrollViewScrollEnabled: true };
    
      handleInnerPressIn = () => this.setState({ outerScrollViewScrollEnabled: false });
      handleInnerPressOut = () => this.setState({ outerScrollViewScrollEnabled: true });
    
      render() {
        const { outerScrollViewScrollEnabled } = this.state;
    
        return (
          <SafeAreaView style={styles.container}>
            <ScrollView
              style={{ height: "100%", width: "100%" }}
              horizontal
              scrollEnabled={outerScrollViewScrollEnabled}
            >
              <View style={{ flexDirection: "row" }}>
                <ScrollView style={{ width: 200, height: "100%" }} horizontal>
                  <TouchableWithoutFeedback
                    onPressIn={this.handleInnerPressIn}
                    onPressOut={this.handleInnerPressOut}
                  >
                    <View style={{ flexDirection: "row" }}>
                      {_.times(200, n => (
                        <View key={1000 + n} style={{ marginRight: 10 }}>
                          <Text>{1000 + n}</Text>
                        </View>
                      ))}
                    </View>
                  </TouchableWithoutFeedback>
                </ScrollView>
                {_.times(200, n => (
                  <View key={n} style={{ marginRight: 10 }}>
                    <Text>{n}</Text>
                  </View>
                ))}
              </View>
            </ScrollView>
          </SafeAreaView>
        );
      }
    }
    
    const styles = StyleSheet.create({
      container: {
        flex: 1,
        flexDirection: "column",
        justifyContent: "center",
        alignItems: "stretch",
        paddingVertical: 50
      }
    });