React Native Art 开发案例

474 阅读3分钟

废话少说先上效果

1657785525404.gif

使用的第三方库

  1. @react-native-community/art 图形绘制
  2. react-native-gesture-handler 手势处理

关键代码

下面代码展示的如何通过四个点的坐标绘制一个距形。


//点的直径
const POINT_WIDTH = 40;

//点的半径
const POINT_RADIUS = POINT_WIDTH/2;

//屏幕宽度
const screenWidth = Dimensions.get('screen').width;

//View 容器高度
const HEIGHT = 200;

//坐标不可以紧贴最外层View,否则绘制左上角触摸点的时候会出现绘制不全的问题。
//左上角坐标
const [leftTop, setLeftTop] = useState({x: POINT_RADIUS, y: POINT_RADIUS});

//右上角坐标
const [rightTop, setRightTop] = useState({x: screenWidth - POINT_RADIUS, y: POINT_RADIUS});

//左下角坐标
const [leftBottom, setLeftBottom] = useState({x: POINT_RADIUS, y: HEIGHT - POINT_RADIUS});

//右下角坐标
const [rightBottom, setRightBottom] = useState({x: screenWidth - POINT_RADIUS, y: HEIGHT - POINT_RADIUS});

//渲染点组成的边框
const renderLine = () => {
    let react = Path()
        .moveTo(leftTop.x - POINT_RADIUS, leftTop.y - POINT_RADIUS)
        .lineTo(rightTop.x + POINT_RADIUS , rightTop.y - POINT_RADIUS)
        .lineTo(rightBottom.x + POINT_RADIUS, rightBottom.y + POINT_RADIUS)
        .lineTo(leftBottom.x - POINT_RADIUS, leftBottom.y + POINT_RADIUS)
        .close() // close 封闭
    return(
        <Surface width={screenWidth} height={HEIGHT}>
            <Shape
                d={react}
                strokeWidth={0}
                fill="rgba(207, 21, 21, 0.29)"
            />
        </Surface>
    )
}

接着我们借助 PanGestureHandler 这个手势处理组建,去监听用户在四边形四个顶点的滑动事件,去改变四个顶点的坐标,这样我们就可以得到我们最后想要的效果了。

完整代码



import React, {useState, useEffect, useRef} from "react";
import {View, StyleSheet, Dimensions, Text} from "react-native";
import {Surface, Shape, Path} from '@react-native-community/art';
import {PanGestureHandler, State, GestureHandlerRootView} from "react-native-gesture-handler"

//点的直径
const POINT_WIDTH = 40;

//点的半径
const POINT_RADIUS = POINT_WIDTH/2;

//屏幕宽度
const screenWidth = Dimensions.get('screen').width;

//View 容器高度
const HEIGHT = 200;


const CutView = () => {
    //坐标不可以紧贴最外层View,否则绘制左上角触摸点的时候会出现绘制不全的问题。
    //左上角坐标
    const [leftTop, setLeftTop] = useState({x: POINT_RADIUS, y: POINT_RADIUS});

    //右上角坐标
    const [rightTop, setRightTop] = useState({x: screenWidth - POINT_RADIUS, y: POINT_RADIUS});

    //左下角坐标
    const [leftBottom, setLeftBottom] = useState({x: POINT_RADIUS, y: HEIGHT - POINT_RADIUS});

    //右下角坐标
    const [rightBottom, setRightBottom] = useState({x: screenWidth - POINT_RADIUS, y: HEIGHT - POINT_RADIUS});

    //当前组件距离页面顶部的距离
    const [pageY, setPageY] = useState(0);

    const container = useRef(null);

    //处理左上角点滚动
    const onLeftTop = ({nativeEvent}) => {
        let {absoluteX, absoluteY} = nativeEvent;
        let y = absoluteY - pageY;

        //计算可以滚动的范围
        if(absoluteX <= POINT_RADIUS){
            absoluteX = POINT_RADIUS;
        }
        if(absoluteX >= rightTop.x - POINT_WIDTH){
            absoluteX = rightTop.x - POINT_WIDTH;
        }
        if(y <= POINT_RADIUS){
            y = POINT_RADIUS
        }
        if(y>= leftBottom.y - POINT_WIDTH){
            y = leftBottom.y - POINT_WIDTH
        }

        setLeftTop({
            x: absoluteX,
            y
        })
        setLeftBottom({
            x: absoluteX,
            y: leftBottom.y
        })
        setRightTop({
            x: rightTop.x,
            y
        })
    }

    //处理右上角滚动
    const onRightTop = ({nativeEvent}) => {
        let {absoluteX, absoluteY} = nativeEvent;
        let y = absoluteY - pageY;

        //计算可以滚动的范围
        if(absoluteX >= screenWidth - POINT_RADIUS){
            absoluteX = screenWidth - POINT_RADIUS;
        }
        if(absoluteX <= leftTop.x + POINT_WIDTH){
            absoluteX = leftTop.x + POINT_WIDTH;
        }
        if(y <= POINT_RADIUS){
            y = POINT_RADIUS;
        }
        if(y >= rightBottom.y - POINT_WIDTH){
            y = rightBottom.y - POINT_WIDTH;
        }

        setRightTop({
            x: absoluteX,
            y
        })
        setLeftTop({
            x: leftTop.x,
            y
        })
        setRightBottom({
            x: absoluteX,
            y: rightBottom.y
        })
    }

    //处理左下角滚动
    const onLeftBottom = ({nativeEvent}) => {
        let {absoluteX, absoluteY} = nativeEvent;
        let y = absoluteY - pageY;

        //计算可以滚动的范围
        if(absoluteX <= POINT_RADIUS){
            absoluteX = POINT_RADIUS;
        }
        if(absoluteX >= rightBottom.x - POINT_WIDTH){
            absoluteX = rightBottom.x - POINT_WIDTH;
        }

        if(y <= leftTop.y + POINT_WIDTH){
            y = leftTop.y + POINT_WIDTH;
        }
        if(y >= HEIGHT - POINT_RADIUS){
            y = HEIGHT - POINT_RADIUS;
        }

        setLeftBottom({
            x: absoluteX,
            y
        })
        setLeftTop({
            x: absoluteX,
            y: leftTop.y
        })
        setRightBottom({
            x: rightBottom.x,
            y
        })
    }

    //处理右下角滚动
    const onRightBottom = ({nativeEvent}) => {
        let {absoluteX, absoluteY} = nativeEvent;
        let y = absoluteY - pageY;

        //计算可以滚动的范围
        if(absoluteX <= leftBottom.x + POINT_WIDTH){
            absoluteX = leftBottom.x + POINT_WIDTH;
        }
        if(absoluteX >= screenWidth - POINT_RADIUS){
            absoluteX = screenWidth - POINT_RADIUS;
        }
        if(y <= rightTop.y + POINT_WIDTH){
            y = rightTop.y + POINT_WIDTH;
        }
        if(y >= HEIGHT - POINT_RADIUS){
            y = HEIGHT - POINT_RADIUS;
        }

        setRightBottom({
            x: absoluteX,
            y
        })
        setRightTop({
            x: absoluteX,
            y: rightTop.y
        })
        setLeftBottom({
            x: leftBottom.x,
            y
        })
    }

    //渲染左上角的点
    const renderLeftTop = () => {
        //由于位置是在左上角所以画点的时候要重新计算下
        let left = leftTop.x - POINT_RADIUS;
        let top = leftTop.y - POINT_RADIUS;
        return(
            <PanGestureHandler
                onGestureEvent={onLeftTop}
            >
                <View
                    style={[styles.point,
                        {width: POINT_WIDTH, height: POINT_WIDTH, borderRadius: POINT_WIDTH/2},
                        {left, top}
                    ]}
                />
            </PanGestureHandler>
        )
    }

    //渲染右上角的点
    const renderRightTop = () => {
        //由于位置是在左上角所以画点的时候要重新计算下
        let left = rightTop.x - POINT_RADIUS;
        let top = rightTop.y - POINT_RADIUS;
        return(
            <PanGestureHandler
                onGestureEvent={onRightTop}
            >
                <View
                    style={[styles.point,
                        {width: POINT_WIDTH, height: POINT_WIDTH, borderRadius: POINT_WIDTH/2},
                        {left, top}
                    ]}
                />
            </PanGestureHandler>
        )
    }

    //渲染左下角的点
    const renderLeftBottom = () => {
        //由于位置是在左上角所以画点的时候要重新计算下
        let left = leftBottom.x - POINT_RADIUS;
        let top = leftBottom.y - POINT_RADIUS;
        return(
            <PanGestureHandler
                onGestureEvent={onLeftBottom}
            >
                <View
                    style={[styles.point,
                        {width: POINT_WIDTH, height: POINT_WIDTH, borderRadius: POINT_WIDTH/2},
                        {left, top}
                    ]}
                />
            </PanGestureHandler>
        )
    }

    //渲染右下角的点
    const renderRightBottom = () => {
        //由于位置是在左上角所以画点的时候要重新计算下
        let left = rightBottom.x - POINT_RADIUS;
        let  top = rightBottom.y - POINT_RADIUS;
        return(
            <PanGestureHandler
                onGestureEvent={onRightBottom}
            >
                <View
                    style={[styles.point,
                        {width: POINT_WIDTH, height: POINT_WIDTH, borderRadius: POINT_WIDTH/2},
                        {left, top}
                    ]}
                />
            </PanGestureHandler>
        )
    }

    //渲染点组成的边框
    const renderLine = () => {
        let react = Path()
            .moveTo(leftTop.x - POINT_RADIUS, leftTop.y - POINT_RADIUS)
            .lineTo(rightTop.x + POINT_RADIUS , rightTop.y - POINT_RADIUS)
            .lineTo(rightBottom.x + POINT_RADIUS, rightBottom.y + POINT_RADIUS)
            .lineTo(leftBottom.x - POINT_RADIUS, leftBottom.y + POINT_RADIUS)
            .close() // close 封闭
        return(
            <Surface width={screenWidth} height={HEIGHT}>
                <Shape
                    d={react}
                    strokeWidth={0}
                    fill="rgba(207, 21, 21, 0.29)"
                />
            </Surface>
        )
    }

    return(
        <View
            ref={container}
            style={styles.container}
            onLayout={()=>{
                container?.current?.measure((x,y,width,height,pageX, pageY)=>{
                    setPageY(pageY);
                })
            }}
        >
            {renderLine()}
            {renderLeftTop()}
            {renderRightTop()}
            {renderLeftBottom()}
            {renderRightBottom()}
        </View>
    )
}

export default CutView;

const styles = StyleSheet.create({
    container: {
        width: screenWidth,
        height: HEIGHT,
        backgroundColor: 'black'
    },

    point: {
        position: 'absolute',
        backgroundColor: 'red'
    }
})