Created
December 20, 2017 13:47
-
-
Save ericnograles/725aba78f67e6fdcd20a20ee15103486 to your computer and use it in GitHub Desktop.
SignatureView for React Native utilizing react-native-svg
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import { View, PanResponder, StyleSheet } from 'react-native'; | |
import Svg, { G, Surface, Path } from 'react-native-svg'; | |
export default class SignatureView extends React.Component { | |
constructor(props, context) { | |
super(props, context); | |
this.state = { | |
currentMax: 1, | |
currentPoints: [], | |
donePaths: [], | |
newPaths: [], | |
reaction: new Reaction() | |
}; | |
this._panResponder = PanResponder.create({ | |
onStartShouldSetPanResponder: (evt, gs) => true, | |
onMoveShouldSetPanResponder: (evt, gs) => true, | |
onPanResponderGrant: (evt, gs) => this.onResponderGrant(evt, gs), | |
onPanResponderMove: (evt, gs) => this.onResponderMove(evt, gs), | |
onPanResponderRelease: (evt, gs) => this.onResponderRelease(evt, gs) | |
}); | |
} | |
onTouch(evt) { | |
let [x, y] = [evt.nativeEvent.pageX, evt.nativeEvent.pageY]; | |
let newCurrentPoints = this.state.currentPoints; | |
newCurrentPoints.push({ x, y }); | |
this.setState({ | |
donePaths: this.state.donePaths, | |
currentPoints: newCurrentPoints, | |
currentMax: this.state.currentMax | |
}); | |
} | |
onResponderGrant(evt) { | |
this.onTouch(evt); | |
} | |
onResponderMove(evt) { | |
this.onTouch(evt); | |
} | |
onResponderRelease() { | |
let newPaths = this.state.donePaths; | |
if (this.state.currentPoints.length > 0) { | |
// Cache the shape object so that we aren't testing | |
// whether or not it changed; too many components? | |
newPaths.push( | |
<Path | |
key={this.state.currentMax} | |
d={this.state.reaction.pointsToSvg(this.state.currentPoints)} | |
stroke="#000000" | |
strokeWidth={4} | |
fill="none" | |
/> | |
); | |
} | |
this.state.reaction.addGesture(this.state.currentPoints); | |
this.setState({ | |
donePaths: newPaths, | |
currentPoints: [], | |
currentMax: this.state.currentMax + 1 | |
}); | |
} | |
_onLayoutContainer = e => { | |
this.state.reaction.setOffset(e.nativeEvent.layout); | |
}; | |
render() { | |
return ( | |
<View | |
onLayout={this._onLayoutContainer} | |
style={[ | |
styles.drawContainer, | |
this.props.containerStyle, | |
{ width: this.props.width, height: this.props.height } | |
]} | |
> | |
<View {...this._panResponder.panHandlers}> | |
<Svg | |
style={styles.drawSurface} | |
width={this.props.width} | |
height={this.props.height} | |
> | |
<G> | |
{this.state.donePaths.map(donePath => donePath)}; | |
<Path | |
key={this.state.currentMax} | |
d={this.state.reaction.pointsToSvg(this.state.currentPoints)} | |
stroke="#000000" | |
strokeWidth={4} | |
fill="none" | |
/> | |
</G> | |
</Svg> | |
{this.props.children} | |
</View> | |
</View> | |
); | |
} | |
} | |
class Reaction { | |
constructor(gestures) { | |
this.gestures = gestures || []; | |
this.reset(); | |
this._offsetX = 0; | |
this._offsetY = 0; | |
} | |
addGesture(points) { | |
if (points.length > 0) { | |
this.gestures.push(points); | |
} | |
} | |
setOffset(options) { | |
this._offsetX = options.x; | |
this._offsetY = options.y; | |
} | |
pointsToSvg(points) { | |
let offsetX = this._offsetX; | |
let offsetY = this._offsetY; | |
if (points.length > 0) { | |
var path = `M${points[0].x - offsetX},${points[0].y - offsetY}`; | |
points.forEach(point => { | |
path = path + ` L${point.x - offsetX},${point.y - offsetY}`; | |
}); | |
return path; | |
} else { | |
return ''; | |
} | |
} | |
replayLength() { | |
return this.replayedGestures.length; | |
} | |
reset() { | |
this.replayedGestures = [[]]; | |
} | |
empty() { | |
return this.gestures.length === 0; | |
} | |
copy() { | |
return new Reaction(this.gestures.slice()); | |
} | |
done() { | |
return ( | |
this.empty() || | |
(this.replayedGestures.length === this.gestures.length && | |
this.lastReplayedGesture().length === | |
this.gestures[this.gestures.length - 1].length) | |
); | |
} | |
lastReplayedGesture() { | |
return this.replayedGestures[this.replayedGestures.length - 1]; | |
} | |
stepGestureLength() { | |
let gestureIndex = this.replayedGestures.length - 1; | |
if (!this.gestures[gestureIndex]) { | |
return; | |
} | |
if ( | |
this.replayedGestures[gestureIndex].length >= | |
this.gestures[gestureIndex].length | |
) { | |
this.replayedGestures.push([]); | |
} | |
} | |
step() { | |
if (this.done()) { | |
return true; | |
} | |
this.stepGestureLength(); | |
let gestureIndex = this.replayedGestures.length - 1; | |
let pointIndex = this.replayedGestures[gestureIndex].length; | |
let point = this.gestures[gestureIndex][pointIndex]; | |
this.replayedGestures[gestureIndex].push(point); | |
return false; | |
} | |
} | |
let styles = StyleSheet.create({ | |
drawContainer: {}, | |
drawSurface: { | |
backgroundColor: 'transparent' | |
} | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment