react-native-barcode-mask
Version:
A barcode and QR code scan layout for react-native applications with customizable styling
292 lines (274 loc) • 8.15 kB
JavaScript
import React from 'react';
import { Animated, StyleSheet, View } from 'react-native';
import PropTypes from 'prop-types';
const styles = StyleSheet.create({
container: {
alignItems: 'center',
justifyContent: 'center',
...StyleSheet.absoluteFillObject,
},
finder: {
alignItems: 'center',
justifyContent: 'center',
},
topLeftEdge: {
position: 'absolute',
top: 0,
left: 0,
},
topRightEdge: {
position: 'absolute',
top: 0,
right: 0,
},
bottomLeftEdge: {
position: 'absolute',
bottom: 0,
left: 0,
},
bottomRightEdge: {
position: 'absolute',
bottom: 0,
right: 0,
},
maskOuter: {
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
alignItems: 'center',
justifyContent: 'space-around',
},
maskInner: {
backgroundColor: 'transparent',
},
maskRow: {
width: '100%',
},
maskCenter: {
display: 'flex',
flexDirection: 'row',
},
animatedLine: {
position: 'absolute',
elevation: 4,
zIndex: 0,
},
});
class BarcodeMask extends React.Component {
constructor(props) {
super(props);
this.state = {
edgeRadiusOffset: props.edgeRadius ? -Math.abs(props.edgeRadius / 3) : 0
};
}
componentDidMount() {
this._startLineAnimation();
}
componentWillUnmount() {
if (this.animation) {
this.animation.stop();
}
}
_startLineAnimation = () => {
const intervalId = setInterval(() => {
const { finderLayout, intervalId } = this.state;
if (finderLayout && finderLayout.height > 0) {
this._animateLoop();
clearInterval(intervalId);
}
}, 500);
this.setState({
intervalId,
});
};
_animateLoop = () => {
const {
animatedLineOrientation,
lineAnimationDuration,
useNativeDriver
} = this.props;
const { lineTravelWindowDistance } = this.state;
const isHorizontal = animatedLineOrientation !== 'vertical';
const propertyToChange = isHorizontal ? 'top' : 'left';
const startValue = -lineTravelWindowDistance;
const endValue = lineTravelWindowDistance;
this.animation = Animated.loop(
Animated.sequence([
Animated.timing(this.state[propertyToChange], {
toValue: endValue,
duration: lineAnimationDuration,
useNativeDriver
}),
Animated.timing(this.state[propertyToChange], {
toValue: startValue,
duration: lineAnimationDuration,
useNativeDriver
})
])
);
this.animation.start();
}
_applyMaskFrameStyle = () => {
const { backgroundColor, outerMaskOpacity } = this.props;
return { backgroundColor, opacity: outerMaskOpacity, flex: 1 };
};
_renderEdge = (edgePosition) => {
const { edgeWidth, edgeHeight, edgeColor, edgeBorderWidth, edgeRadius } = this.props;
const { edgeRadiusOffset } = this.state;
const defaultStyle = {
width: edgeWidth,
height: edgeHeight,
borderColor: edgeColor
};
const edgeBorderStyle = {
topRight: {
borderRightWidth: edgeBorderWidth,
borderTopWidth: edgeBorderWidth,
borderTopRightRadius: edgeRadius,
top: edgeRadiusOffset,
right: edgeRadiusOffset,
},
topLeft: {
borderLeftWidth: edgeBorderWidth,
borderTopWidth: edgeBorderWidth,
borderTopLeftRadius: edgeRadius,
top: edgeRadiusOffset,
left: edgeRadiusOffset
},
bottomRight: {
borderRightWidth: edgeBorderWidth,
borderBottomWidth: edgeBorderWidth,
borderBottomRightRadius: edgeRadius,
bottom: edgeRadiusOffset,
right: edgeRadiusOffset
},
bottomLeft: {
borderLeftWidth: edgeBorderWidth,
borderBottomWidth: edgeBorderWidth,
borderBottomLeftRadius: edgeRadius,
bottom: edgeRadiusOffset,
left: edgeRadiusOffset,
},
};
return <View style={[defaultStyle, styles[edgePosition + 'Edge'], edgeBorderStyle[edgePosition]]} />;
};
_calculateLineTravelWindowDistance({ layout, isHorizontalOrientation }) {
return (((isHorizontalOrientation ? layout.height : layout.width) - 10)/2);
}
_onFinderLayoutMeasured = ({ nativeEvent }) => {
const { animatedLineOrientation, onLayoutMeasured } = this.props;
const { layout } = nativeEvent;
const isHorizontal = animatedLineOrientation !== 'vertical';
const travelDistance = this._calculateLineTravelWindowDistance({
layout,
isHorizontalOrientation: isHorizontal,
});
this.setState({
top: new Animated.Value(-travelDistance),
left: new Animated.Value(-travelDistance),
lineTravelWindowDistance: travelDistance,
finderLayout: layout,
})
if (onLayoutMeasured) {
onLayoutMeasured({ nativeEvent });
}
}
render() {
const {
width,
height,
showAnimatedLine,
animatedLineColor,
animatedLineWidth,
animatedLineHeight,
animatedLineOrientation,
edgeBorderWidth
} = this.props;
const animatedLineStyle = {
backgroundColor: animatedLineColor,
height: animatedLineHeight,
maxHeight: height,
width: animatedLineWidth,
maxWidth: width,
margin: edgeBorderWidth
};
const { finderLayout, top, left } = this.state;
if (finderLayout && animatedLineOrientation !== 'vertical') {
animatedLineStyle.transform = [{
translateY: top
}]
} else if (finderLayout) {
animatedLineStyle.transform = [{
translateX: left
}]
}
return (
<View style={[styles.container]}>
<View
style={[ styles.finder, { width, height } ]}
onLayout={this._onFinderLayoutMeasured}
>
{this._renderEdge('topLeft')}
{this._renderEdge('topRight')}
{this._renderEdge('bottomLeft')}
{this._renderEdge('bottomRight')}
{showAnimatedLine && (
<Animated.View
style={[ styles.animatedLine, animatedLineStyle ]}
/>
)}
</View>
<View style={styles.maskOuter}>
<View style={[styles.maskRow, this._applyMaskFrameStyle()]} />
<View style={[{ height }, styles.maskCenter]} >
<View style={[this._applyMaskFrameStyle()]} />
<View style={[ styles.maskInner, { width, height } ]} />
<View style={[this._applyMaskFrameStyle()]} />
</View>
<View style={[styles.maskRow, this._applyMaskFrameStyle()]} />
</View>
</View>
);
}
}
const propTypes = {
width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
edgeWidth: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
edgeHeight: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
edgeColor: PropTypes.string,
edgeBorderWidth: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
edgeRadius: PropTypes.number,
backgroundColor: PropTypes.string,
outerMaskOpacity: PropTypes.number,
showAnimatedLine: PropTypes.bool,
animatedLineColor: PropTypes.string,
animatedLineHeight: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
animatedLineWidth: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
lineAnimationDuration: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
animatedLineOrientation: PropTypes.string,
useNativeDriver: PropTypes.bool,
onLayoutMeasured: PropTypes.func
};
const defaultProps = {
width: 280,
height: 230,
edgeWidth: 20,
edgeHeight: 20,
edgeColor: '#FFF',
edgeBorderWidth: 4,
backgroundColor: 'rgb(0, 0, 0)',
outerMaskOpacity: 0.6,
showAnimatedLine: true,
animatedLineColor: '#FFF',
animatedLineHeight: 2,
animatedLineWidth: '85%',
lineAnimationDuration: 5000,
animatedLineOrientation: 'horizontal',
useNativeDriver: true
};
BarcodeMask.propTypes = propTypes;
BarcodeMask.defaultProps = defaultProps;
export default BarcodeMask;