react-native-skia
react-native-skia copied to clipboard
onTouch will trigger outside touch event
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.
for now, i wrap canvas in the react-native-gesture-handler
's <GestureDetector />
Can you provide a code snippet and a description of the behaviour you would like to achieve so we can take a look?
@wcandillon I'm experiencing the same thing. Here's a reproducible demo:
-
npx create-expo-app my-app
-
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.
Same for me.
If I use the Skia Component which is using useTouchHandler
inside of ScrollView
, ScrollView's vertical scroll event cancels useTouchHander onActive
.
@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?
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.
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