Search code examples
javascriptreact-nativeexpogesture-recognition

Expo + React Native: Draw line between coordinates on two type of views


I am currently using this module: https://github.com/mxmzb/react-native-gesture-detector. I want to be able to draw a line from the points created. however, it only seems to output circles.

It has a "Create Gesture" view:

<View style={{ position: "relative", width: "100%", height: "100%" }}>
    <GesturePath
        path={gesture.map(coordinate => {
            if (recorderOffset) {
                return {
                    x: coordinate.x + recorderOffset.x,
                    y: coordinate.y + recorderOffset.y,
                };
            }

            return coordinate;
        })}
        color="green"
        slopRadius={30}
        center={false}
    />
</View>

GesturePath is defined like so:

const GesturePath = ({ path, color, slopRadius, center = true }: GesturePathProps) => {
  const baseStyle: ViewStyle = {
    position: "absolute",
    top: center ? "50%" : 0,
    left: center ? "50%" : 0,
    opacity: 1,
  };

  return (
    <>
      {path.map((point, index) => (
        <Animated.View
          style={Object.assign({}, baseStyle, {
            width: slopRadius,
            height: slopRadius,
            borderRadius: slopRadius,
            backgroundColor: color,
            marginLeft: point.x - slopRadius,
            marginTop: point.y - slopRadius,
          })}
          key={index}
        />
      ))}
    </>
  );
};

When you draw on that view, it outlines the path using dots, like so:

enter image description here

I would like it to be a smooth line and not a series of circles that the above image.


Solution

  • You are going to need something like a Canvas to draw lines instead of pixels (with Views). React Native does not currently come with a Canvas implementation.

    The easiest way to do this in expo is to use the react-native-svg library.

    Using that, you can draw a polyline from your gesture data with the following implementation:

    import Svg, { Polyline } from 'react-native-svg';
    
    const GesturePath = ({ path, color }) => {
      const { width, height } = Dimensions.get('window');
      const points = path.map(p => `${p.x},${p.y}`).join(' ');
      return (
        <Svg height="100%" width="100%" viewBox={`0 0 ${width} ${height}`}>
            <Polyline
              points={points}
              fill="none"
              stroke={color}
              strokeWidth="1"
            />
        </Svg>    
      );
    };
    

    You can also record gestures without the react-native-gesture-detector library by using the in-built React Native PanResponder. Here is an example:

    const GestureRecorder = ({ onPathChanged }) => {
      const pathRef = useRef([]);
    
      const panResponder = useRef(
        PanResponder.create({
          onMoveShouldSetPanResponder: () => true,
          onPanResponderGrant: () => {
            pathRef.current = [];
          },
          onPanResponderMove: (event) => {
            pathRef.current.push({
              x: event.nativeEvent.locationX,
              y: event.nativeEvent.locationY,
            });
            // Update path real-time (A new array must be created
            // so setState recognises the change and re-renders the App):
            onPathChanged([...pathRef.current]);
          },
          onPanResponderRelease: () => {
            onPathChanged(pathRef.current);
          }
        })
      ).current;
    
      return (
        <View
          style={StyleSheet.absoluteFill}
          {...panResponder.panHandlers}
        />
      );
    }
    

    See this snack for a working App tying everything together: https://snack.expo.io/@mtkopone/draw-gesture-path