react-native 画笔(写字板、手写板)--1
2021-11-24 本文已影响0人
物联白菜
参考链接:https://www.jianshu.com/p/504c639063b3
前提:集成好react-native-svg
以上链接博主大佬用的是ART绘画系统,受此启发我就使用SVG画图了,因为项目里面还有其他操作,例如箭头、圆圈、矩形等类似电脑截图后编辑操作,而ART用法个人不是很熟悉,所以采用SVG,SVG不是很熟悉的可以看一下我之前写的这篇https://www.jianshu.com/p/ef91237a89a4,也可以自己上菜鸟教程简单的练一下再上手,做了个小demo,效果图如下:
把以下代码复制粘贴到一个新页面即可尝试,至于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'
},
});