jorundur
jorundur

Reputation: 690

How can I use react-native-gesture-handler to handle react-native-skia touches inside a ScrollView?

I have a ScrollView containing several graphs made with react-native-skia. The graphs are interactable, i.e. I can touch them and move an indicator on the graph along the x-axis of the graph.

My issue is that the whenever the ScrollView events fire (i.e. we scroll up/down), then the graph touch events are ignored which makes for bad UX.

Demo:

Here's a Snack with a reproducible demo: https://snack.expo.dev/@jorundur/supportive-raisins

FYI: For some reason for me the Canvas disappears after 1 second in the Snack, but if you make any change in the file and save (such add a newline somewhere), then it appears. It's fine if you run this locally. But that's some other problem we can ignore for the purpose of this discussion.

Description:

The demo is a react-native ScrollView large enough to make sure we have to scroll, the top component is a graph using react-native-skia. If we drag the cursor in the graph around, then the UX gets bad pretty quickly as the graph touch events seem to be ignored as soon as any vertical scrolling happens. I've tried playing around with ScrollView from react-native-gesture-handler but without luck.

To me, the expected behaviour would be for the graph to be interactable even while scrolling. I.e. if I'm pressing the graph and move my finger diagonally up/down I would expect the ScrollView to scroll and the graph cursor also to change its position accordingly. (I say diagonally since a straight vertical movement wouldn't change the cursor position in this graph.)

Much appreciated if anyone has any ideas on how to solve this! I couldn't work out how to do it via GestureDetector from react-native-gesture-handler like I've heard suggested.

Possible solution (?):

What I think I need to do is remove the onTouch={touchHandler} which I'm using currently in the react-native-skia Canvas component and instead get those touches via gesture detection from react-native-gesture-handler. I then need to make those gestures work simultaneously with the parent ScrollViews scroll event gestures. I've not had any luck implementing that unfortunately.

Upvotes: 4

Views: 11050

Answers (1)

jorundur
jorundur

Reputation: 690

The solution was to do the following:

  • Don't use onTouch on Canvas, instead detect gestures via react-native-gesture-handler
  • Create a gesture and add a ref to it
  • Add the simultaneousHandlers prop to the ScrollView and use the ref there
    • This tells the ScrollView that its events should not be blocked by the touch events from the ref

To reiterate, what I wanted to do was to have the touch events of a ScrollView work simultaneously with touch events from a react-native-skia Canvas child component.

Code (relevant bits):

import * as React from 'react';
import {
  GestureHandlerRootView,
  ScrollView // Note that this is not imported from react-native
} from 'react-native-gesture-handler';

const App = () => {
  // Create ref for the ScrollView to know what other gestures it should work simultaneously with.
  // My use case required pan but this can be swapped for other gestures.
  const panGestureRef = React.useRef<GestureType>(Gesture.Pan()); 

  // The logic that used to be in `onTouch` on the Canvas is now here
  const panGesture = Gesture.Pan()
    .onChange((e) => {
      // Do stuff, also use the other callbacks available as needed
    })
    .withRef(panGestureRef);

  return (
    <GestureHandlerRootView style={{ flex: 1 }}>{/* Don't forget this! It should be at the root of your project. */}
      <ScrollView simultaneousHandlers={[panGestureRef]}>
        <GestureDetector gesture={panGesture}>
          <Canvas style={{ width: 200, height: 200 }}>
            {/* Your interactive react-native-skia magic */}
          </Canvas>
        </GestureDetector>
      </ScrollView>
    </GestureHandlerRootView>
  );
};

export default App;

Upvotes: 5

Related Questions