Skip to content

Horizontal FlatList with pages and ScrollView per page. How to prioritise vertical scrolling over horizontal #590

@rgommezz

Description

@rgommezz

Hi folks!

I am dealing with a situation where I have a horizontal FlatList with multiple pages, and every page is a vertical ScrollView.

From UX perspective, I'd like to prioritise the vertical swiping over the horizontal. However, both have the same sensitiveness and, as a result, some times the gesture responder system detects a horizontal swipe when you wanna scroll down, being a bit frustrating for the user.

1st attempt

My initial idea was to wrap the horizontal FlatList with a PanGestureHandler. The scroll on the FlatList would be initially disabled and it's not enabled until a certain threshold has been fulfilled (by using activeOffsetX).

The way to re-enable it is by using setNativeProps on the FlatList when the pan gesture becomes active. Here is a simpler version of my code:

    componentDidMount() {
      this.list.setNativeProps({
        scrollEnabled: false,
	  });
    }

    render() {
         <PanGestureHandler
          activeOffsetX={[-20, 20]}
          onHandlerStateChange={() => {
			if (nativeEvent.state === State.ACTIVE) {
			  this.list.setNativeProps({
			    scrollEnabled: true,
			  });
    		}
          }}
        >
          <Animated.View>
            <FlatList
              pagingEnabled
              showsHorizontalScrollIndicator={false}
              horizontal
              data={data}
              ref={list => {
                this.list = list;
              }}
              renderItem={({ item }) => (
                <Item item={item} />
              )}
              onMomentumScrollEnd={() => {
                this.list.setNativeProps({ scrollEnabled: false })
              }
            />
          </Animated.View>
       </PanGestureHandler>
    }

The problem is that when I call setNativeProps to enable scrolling on the FlatList, it won't activate it as long as the pan gesture is still active. When I finish my original horizontal swipe gesture, then I am able to swipe through the pages.

On top of my head, that could be caused by two reasons:

  1. When setNativeProps is executed, the native thread won't process it until the gesture finishes or it's cancelled because it's busy.
  2. There can't be 2 simultaneous gestures happening at the same time with the above implementation.

2nd attempt

In order to try to overcome the 2nd possible reason (and blindly assuming 1st reason it's not an issue), I decided to use a NativeViewGestureHandler, for what I mean importing the FlatList provided by this library and leverage simultaneousHandlers. Unfortunately the end result was the same.

import {
  PanGestureHandler,
  State,
  FlatList,
} from 'react-native-gesture-handler';

....

    componentDidMount() {
      this.list.setNativeProps({
        scrollEnabled: false,
	  });
    }

    render() {
       <PanGestureHandler
          activeOffsetX={[-20, 20]}
          onHandlerStateChange={() => {
			if (nativeEvent.state === State.ACTIVE) {
			  this.list.setNativeProps({
			    scrollEnabled: true,
			  });
              // The below call didn't make any difference
              this.panWrapper.setNativeProps({
                enabled: false,
              });
    		}
          }}
          ref={ref => {
            this.panWrapper = ref;
          }}
        >
          <Animated.View>
            <FlatList
              pagingEnabled
              simultaneousHandlers={this.panWrapper}
              showsHorizontalScrollIndicator={false}
              horizontal
              data={data}
              ref={list => {
                this.list = list;
              }}
              renderItem={({ item }) => (
                <Item item={item} />
              )}
              onMomentumScrollEnd={() => {
                this.list.setNativeProps({ scrollEnabled: false })
                // The below call didn't make any difference
                this.panWrapper.setNativeProps({
                  enabled: true,
                });
              }
            />
          </Animated.View>
       </PanGestureHandler>
    }

Final thoughts

Is there something from the library point of view I could use to accomplish the desire feature?

Looking at the API, ideally I could maybe use waitFor to make the FlatList wait until the PanGesture leaves the BEGIN state, which is imposed by activeOffsetX and also set simultaneousHandlers on the FlatList, so that both can happen at the same time, without the need of any of the aforementioned setNativeProps calls and being all the system declarative.

However, since the FlatList is the inner most component, the gesture is captured first by it and the PanGestureHandler is ignored, no matter the properties I set, so I don't see any way of avoiding switching scrollEnabled between true and false imperatively.

It may also be the case that the underlying platforms don't allow two similar gesture handlers to be active at the same time, as opposed to simultaneously zooming in and rotating a photo on a Photo Preview component.

Thanks for this awesome library and looking forward to hearing your thoughts!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions