react-native 画笔(写字板、手写板)--1

参考链接:https://www.jianshu.com/p/504c639063b3
前提:集成好react-native-svg

以上链接博主大佬用的是ART绘画系统,受此启发我就使用SVG画图了,因为项目里面还有其他操作,例如箭头、圆圈、矩形等类似电脑截图后编辑操作,而ART用法个人不是很熟悉,所以采用SVG,SVG不是很熟悉的可以看一下我之前写的这篇https://www.jianshu.com/p/ef91237a89a4,也可以自己上菜鸟教程简单的练一下再上手,做了个小demo,效果图如下:

441637732888_.pic.jpg

把以下代码复制粘贴到一个新页面即可尝试,至于Util.size.width、Util.size.height是屏幕宽高,换一下就好了。这只是画线功能,其他功能效果(例如圆圈、矩形、箭头等功能)请看另外一篇文章: https://www.jianshu.com/p/06d3bdfae98a

import React, {Component} from 'react';
import {View, Text, StyleSheet, Image, TouchableOpacity, PanResponder, ART, ImageBackground} from 'react-native'
import Util from './common/util'
import Svg, {Path} from "react-native-svg";

class SvgDrawTest extends Component {
    constructor(props) {
        super(props);
        this.allPoint = ''
        this.state = {
            // drawPath: 'M25 10 L98 65 L70 25 L16 77 L11 30 L0 4 L90 50 L50 10 L11 22 L77 95 L20 25'
            drawPath: ''
        }
    }

    componentWillMount() {
        this._panResponderDrawLine = PanResponder.create({
            onStartShouldSetPanResponder: (evt, gestureState) => true,
            onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
            onMoveShouldSetPanResponder: (evt, gestureState) => true,
            onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,

            onPanResponderGrant: (evt, gestureState) => {
                let tempfirstX = evt.nativeEvent.pageX
                let tempFirstY = evt.nativeEvent.pageY
                this.firstPoint = ' M' + tempfirstX + ' ' + tempFirstY
                this.allPoint = this.allPoint + this.firstPoint   //上一次的画的全部的点(this.allPoint),拼接上当前这次画的M点,在svg中M为线的起始点,拼接上后在 onPanResponderMove 中将当前移动的所有点再次拼接,当前和之前的拼接完之后,更新页面线条
            },

            onPanResponderMove: (evt, gestureState) => {
                let pointX = evt.nativeEvent.pageX
                let pointY = evt.nativeEvent.pageY
                // console.log(`X:${pointX}`, `Y:${pointY}`)
                let point = ' L' + pointX + ' ' + pointY
                this.allPoint += point
                console.log('point====', this.allPoint)
                let drawPath = this.allPoint
                this.setState({
                    drawPath
                })
            },

            onPanResponderTerminationRequest: (evt, gestureState) => true,
            onPanResponderRelease: (evt, gestureState) => { },

            onPanResponderTerminate: (evt, gestureState) => { },

            onShouldBlockNativeResponder: (evt, gestureState) => {
                return true;
            },
        })

    }

    clearOut(){
        this.allPoint = ''
        this.setState({
            drawPath:''
        })
    }

    render() {
        return (
            <View>
                <View style={{width:Util.size.width,height:Util.size.height-60, backgroundColor: 'green'}}
                      {...this._panResponderDrawLine.panHandlers}
                >
                    <Svg height="100%" width="100%">
                        <Path
                            d={this.state.drawPath}
                            fill="none"
                            stroke="red"
                            strokeWidth="5"
                        />
                    </Svg>
                </View>
                <TouchableOpacity onPress={()=>this.clearOut()} style={styles.btn}>
                    <Text style={{color:'#333',fontSize:16}}>清空</Text>
                </TouchableOpacity>
            </View>

    );
    }
}

export default SvgDrawTest;
const styles = StyleSheet.create({
    btn:{elevation:5,backgroundColor:'#fff',paddingHorizontal:30,paddingVertical:10,borderRadius:999,justifyContent:'center',alignItems:'center'},
})

在项目里使用实例,效果图如下


192451637727165_.pic.jpg

本来在手势那里,因为画图是在图片范围内,我是尝试用locationX,locationY 而不是pageX、pageY,后来发现
locationX超过范围后那个点会返回对立的那一面,想着解决太麻烦了,所以采用pageX和pageY,如图所见,手势起始点的位置和移动的位置点减去他的边距即可。目前是画笔,后续操作实现了再更新实现代码。

import React, {Component} from 'react';
import {View, Text, Image, StyleSheet, PanResponder,TouchableOpacity} from 'react-native'
import Svg, {Path} from "react-native-svg";

let marginY = 50 + 15 + 5  //头部导航height 50  下方内容padding 15  图片与父盒子距离 5
let marginX = 15 + 5  //  下方内容padding 15  图片与父盒子距离 5

class Comp3 extends Component {
    constructor(props) {
        super(props);
        this.allPoint = ''
        this.state = {
            drawPath: ''
        }
    }

    componentWillMount() {

        this._panResponderDrawLine = PanResponder.create({
            onStartShouldSetPanResponder: (evt, gestureState) => true,
            onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
            onMoveShouldSetPanResponder: (evt, gestureState) => true,
            onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,

            onPanResponderGrant: (evt, gestureState) => {
                let tempfirstX = evt.nativeEvent.pageX.toFixed(0)-marginX
                let tempFirstY = evt.nativeEvent.pageY.toFixed(0)-marginY
                this.firstPoint = ' M' + tempfirstX + ' ' + tempFirstY
                this.allPoint += this.firstPoint   //上一次的画的全部的点(this.allPoint),拼接上当前这次画的M点,在svg中M为线的起始点,拼接上后在 onPanResponderMove 中将当前移动的所有点再次拼接,当前和之前的拼接完之后,更新页面线条
            },

            onPanResponderMove: (evt, gestureState) => {
                let pointX = evt.nativeEvent.pageX.toFixed(0)-marginX
                let pointY = evt.nativeEvent.pageY.toFixed(0)-marginY
                // console.log(`X:${pointX}`, `Y:${pointY}`)
                let point = ` L${pointX} ${pointY}`
                this.allPoint += point
                let drawPath = this.allPoint
                this.setState({
                    drawPath
                })
            },

            onPanResponderTerminationRequest: (evt, gestureState) => true,
            onPanResponderRelease: (evt, gestureState) => { },
            onPanResponderTerminate: (evt, gestureState) => {},
            onShouldBlockNativeResponder: (evt, gestureState) => {
                return true;
            },
        })
    }


    render() {
        return (
            <View style={styles.content_left}>
                <View style={{flex: 1}} {...this._panResponderDrawLine.panHandlers}>
                    <Image
                        source={require('../images/default.jpg')}
                        style={{height: '100%', width: '100%', position: 'absolute'}}
                    />
                    <Svg height="100%" width="100%">
                        <Path
                            d={this.state.drawPath}
                            fill="none"
                            stroke="red"
                        />
                    </Svg>
                </View>

                <View style={styles.camera_bottom}>
                    <View style={[styles.bottom_item, {flex: 1}]}>
                        <Text style={styles.bottom_title}>选择</Text>
                        <View style={styles.bottom_icon}>
                            <View>
                                <Image source={require('../images/opera/icon1.png')}
                                       style={{width: 15, height: 15}}/>
                            </View>
                            <View>
                                <Image source={require('../images/opera/icon2.png')}
                                       style={{width: 15, height: 15}}/>
                            </View>
                        </View>
                    </View>

                    <View style={[styles.bottom_item, {flex: 2}]}>
                        <Text style={styles.bottom_title}>线条选择</Text>
                        <View style={styles.bottom_icon}>
                            <View>
                                <Image source={require('../images/opera/icon3.png')}
                                       style={{width: 15, height: 15}}/>
                            </View>
                            <View>
                                <Image source={require('../images/opera/icon4.png')}
                                       style={{width: 15, height: 15}}/>
                            </View>
                            <View>
                                <Image source={require('../images/opera/icon5.png')}
                                       style={{width: 15, height: 15}}/>
                            </View>
                            <View>
                                <Image source={require('../images/opera/icon6.png')}
                                       style={{width: 15, height: 15}}/>
                            </View>
                        </View>
                    </View>

                    <View style={[styles.bottom_item, {flex: 2}]}>
                        <View style={styles.bottom_icon}>
                            <Text style={styles.bottom_title}>粗细</Text>
                            <Text style={styles.bottom_title}>色彩</Text>
                        </View>
                        <View style={styles.bottom_icon}>
                            <View style={{flexDirection: 'row', alignItems: 'center'}}>
                                <Image source={require('../images/opera/icon7.png')}
                                       style={{width: 60, height: 3}}/>
                                <View>
                                    <Image source={require('../images/opera/triangle.png')}
                                           style={{width: 15, height: 15}}/>
                                </View>
                            </View>

                            <View style={styles.colorSelect}>
                                <View style={{width: 16, height: 16, backgroundColor: 'red', borderRadius: 2}}/>
                                <Image source={require('../images/opera/triangle.png')}
                                       style={{width: 15, height: 15}}/>
                            </View>
                        </View>
                    </View>

                    <View style={[styles.bottom_item, {flex: 2, borderRightWidth: 0}]}>
                        <Text style={styles.bottom_title}>操作</Text>
                        <View style={styles.bottom_icon}>
                            <View style={styles.bottom_btn}>
                                <Text style={{fontSize: 12}}>撤销</Text>
                            </View>
                            <TouchableOpacity onPress={()=>{this.setState({drawPath:''});this.allPoint = ''}} style={styles.bottom_btn}>
                                <Text style={{fontSize: 12}}>清空</Text>
                            </TouchableOpacity>
                            <View style={[styles.bottom_btn, {backgroundColor: '#203990'}]}>
                                <Text style={{fontSize: 12, color: '#fff'}}>保存</Text>
                            </View>
                        </View>
                    </View>

                </View>
            </View>
        );
    }
}

export default Comp3;
const styles = StyleSheet.create({
    /**内容*/
    content_left: {
        backgroundColor: '#fff',
        flex: 3,
        marginRight: 15,
        padding: 5,
    },
    content_left_camera: {
        flex: 1,
    },

    camera_bottom: {
        backgroundColor: '#fff',
        height: '25%',
        flexDirection: 'row',
        alignItems: 'center',
        justifyContent: 'center'
    },

    bottom_item: {
        paddingHorizontal: 10,
        borderRightWidth: 1,
        borderColor: '#eee',
        height: '70%',
        justifyContent: 'space-between'
    },
    bottom_icon: {
        flexDirection: 'row',
        alignItems: 'center',
        justifyContent: 'space-between',
    },
    bottom_title: {fontSize: 12, marginBottom: 10},
    bottom_btn: {
        borderWidth: 1,
        borderRadius: 999,
        width: '30%',
        borderColor: '#A5AAC1',
        justifyContent: 'center',
        alignItems: 'center',
        paddingVertical: 3
    },
    colorSelect: {
        flexDirection: 'row',
        alignItems: 'center',
        borderWidth: 1,
        borderRadius: 5,
        paddingHorizontal: 2,
        paddingVertical: 2,
        borderColor: '#eee'
    },


});

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270

推荐阅读更多精彩内容