react-native-skia icon indicating copy to clipboard operation
react-native-skia copied to clipboard

onTouch will trigger outside touch event

Open see2ever opened this issue 2 years ago • 2 comments

when i wrap it in a scrollview, it will trigger the scrollview scroll. and i've tried use onStartShouldSetPanResponder=() => true something like this to stop touch event propogation, but it won't work. Please give me some advices, Thank you.

see2ever avatar Jul 13 '22 04:07 see2ever

for now, i wrap canvas in the react-native-gesture-handler's <GestureDetector />

see2ever avatar Jul 15 '22 07:07 see2ever

Can you provide a code snippet and a description of the behaviour you would like to achieve so we can take a look?

wcandillon avatar Jul 15 '22 07:07 wcandillon

@wcandillon I'm experiencing the same thing. Here's a reproducible demo:

  1. npx create-expo-app my-app
  2. npx expo install @shopify/react-native-skia

package.json now looks like this:

{
  "name": "my-app",
  "version": "1.0.0",
  "main": "node_modules/expo/AppEntry.js",
  "scripts": {
    "start": "expo start",
    "android": "expo start --android",
    "ios": "expo start --ios",
    "web": "expo start --web"
  },
  "dependencies": {
    "expo": "~46.0.9",
    "expo-status-bar": "~1.4.0",
    "react": "18.0.0",
    "react-native": "0.69.4",
    "@shopify/react-native-skia": "0.1.141"
  },
  "devDependencies": {
    "@babel/core": "^7.12.9"
  },
  "private": true
}

Change App.js to the following (this is adapted from the Slider graph in example/src/Examples/Graphs):

import { ScrollView, StyleSheet, Text, View } from "react-native";

import {
  Skia,
  Group,
  useComputedValue,
  useValue,
  Line,
  Canvas,
  Circle,
  Fill,
  LinearGradient,
  Path,
  useTouchHandler,
  Text as SkiaText,
  vec,
} from "@shopify/react-native-skia";
import React, { useMemo } from "react";

export default function App() {
  return (
    <View style={styles.container}>
      <ScrollView>
        <Slider height={400} width={400} />
        <View style={styles.box} />
        <View style={styles.box} />
        <View style={styles.box} />
      </ScrollView>
    </View>
  );
}

const Slider = ({ height, width }) => {
  const path = useMemo(
    () => createGraphPath(width, height, 60, false),
    [height, width]
  );

  const touchPos = useValue(
    getPointAtPositionInPath(width / 2, width, 60, path)
  );

  const touchHandler = useTouchHandler({
    onActive: ({ x }) =>
      (touchPos.current = getPointAtPositionInPath(x, width, 60, path)),
  });

  const lineP1 = useComputedValue(
    () => vec(touchPos.current.x, touchPos.current.y + 14),
    [touchPos]
  );
  const lineP2 = useComputedValue(
    () => vec(touchPos.current.x, height),
    [touchPos]
  );

  return (
    <View style={{ height, marginBottom: 10 }}>
      <Canvas style={styles.graph} onTouch={touchHandler}>
        <Fill color="black" />
        <Path
          path={path}
          strokeWidth={4}
          style="stroke"
          strokeJoin="round"
          strokeCap="round"
        >
          <LinearGradient
            start={vec(0, height * 0.5)}
            end={vec(width * 0.5, height * 0.5)}
            colors={["black", "#DA4167"]}
          />
        </Path>
        <Group color="#fff">
          <Circle c={touchPos} r={10} />
          <Circle color="#DA4167" c={touchPos} r={7.5} />
          <Line p1={lineP1} p2={lineP2} />
        </Group>
      </Canvas>
      <Text>Touch and drag to move center point</Text>
    </View>
  );
};

const getPointAtPositionInPath = (x, width, steps, path) => {
  const index = Math.max(0, Math.floor(x / (width / steps)));
  const fraction = (x / (width / steps)) % 1;
  const p1 = path.getPoint(index);
  if (index < path.countPoints() - 1) {
    const p2 = path.getPoint(index + 1);
    // Interpolate between p1 and p2
    return {
      x: p1.x + (p2.x - p1.x) * fraction,
      y: p1.y + (p2.y - p1.y) * fraction,
    };
  }
  return p1;
};

const createGraphPath = (width, height, steps, round = true) => {
  const retVal = Skia.Path.Make();
  let y = height / 2;
  retVal.moveTo(0, y);
  const prevPt = { x: 0, y };
  for (let i = 0; i < width; i += width / steps) {
    // increase y by a random amount between -10 and 10
    y += Math.random() * 30 - 15;
    y = Math.max(height * 0.2, Math.min(y, height * 0.7));

    if (round && i > 0) {
      const xMid = (prevPt.x + i) / 2;
      const yMid = (prevPt.y + y) / 2;
      retVal.quadTo(prevPt.x, prevPt.y, xMid, yMid);
      prevPt.x = i;
      prevPt.y = y;
    } else {
      retVal.lineTo(i, y);
    }
  }
  return retVal;
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
    alignItems: "center",
    justifyContent: "center",
    marginTop: 50,
  },
  box: {
    height: 400,
    width: 400,
    backgroundColor: "blue",
    margin: 4,
  },
  graph: {
    flex: 1,
  },
});

This 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.)

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

FYI: For some reason 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 locally. But that's some other problem we can ignore for the purpose of this discussion.

Jorundur avatar Aug 26 '22 10:08 Jorundur

Same for me.

If I use the Skia Component which is using useTouchHandler inside of ScrollView, ScrollView's vertical scroll event cancels useTouchHander onActive.

ngima avatar Sep 07 '22 05:09 ngima

@see2ever

for now, i wrap canvas in the react-native-gesture-handler's GestureDetector

Can you add some sample code for how you managed to solve this via GestureDetector?

Jorundur avatar Sep 08 '22 16:09 Jorundur

Here I would really recommend to use react-native-gesture-handler for such a use-case. If you think there is a bug on our side or you would like to make a feature request for us to support such a use-case please feel free to reopen this issue.

wcandillon avatar Sep 12 '22 07:09 wcandillon

For future reference, I found a solution in the end and describe it here: https://github.com/Shopify/react-native-skia/discussions/898#discussioncomment-3636831

Jorundur avatar Sep 13 '22 16:09 Jorundur