qr.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494
  1. import React, {Component} from 'react';
  2. import Camera from 'react-native-camera';
  3. import PropTypes from 'prop-types';
  4. import
  5. {
  6. ActivityIndicator,
  7. StyleSheet,
  8. View,
  9. Animated,
  10. Easing,
  11. Text,
  12. Image
  13. } from 'react-native';
  14. /**
  15. * 扫描界面遮罩
  16. * 单独写一个类,方便拷贝使用
  17. */
  18. class QRScannerRectView extends Component {
  19. static defaultProps = {
  20. maskColor: '#0000004D',
  21. cornerColor: '#22ff00',
  22. borderColor: '#000000',
  23. rectHeight: 200,
  24. rectWidth: 200,
  25. borderWidth: 0,
  26. cornerBorderWidth: 4,
  27. cornerBorderLength: 20,
  28. isLoading: false,
  29. cornerOffsetSize: 0,
  30. isCornerOffset: false,
  31. bottomMenuHeight: 0,
  32. scanBarAnimateTime: 2500,
  33. scanBarColor: '#22ff00',
  34. scanBarImage: null,
  35. scanBarHeight: 1.5,
  36. scanBarMargin: 6,
  37. hintText: '将二维码/条码放入框内,即可自动扫描',
  38. hintTextStyle: {color: '#fff', fontSize: 14,backgroundColor:'transparent'},
  39. hintTextPosition: 130,
  40. isShowScanBar:true
  41. };
  42. constructor(props) {
  43. super(props);
  44. this.getBackgroundColor = this.getBackgroundColor.bind(this);
  45. this.getRectSize = this.getRectSize.bind(this);
  46. this.getCornerSize = this.getCornerSize.bind(this);
  47. this.renderLoadingIndicator = this.renderLoadingIndicator.bind(this);
  48. this.state = {
  49. topWidth: 0,
  50. topHeight: 0,
  51. leftWidth: 0,
  52. animatedValue: new Animated.Value(0),
  53. }
  54. }
  55. //获取背景颜色
  56. getBackgroundColor() {
  57. return ({
  58. backgroundColor: this.props.maskColor,
  59. });
  60. }
  61. //获取扫描框背景大小
  62. getRectSize() {
  63. return ({
  64. height: this.props.rectHeight,
  65. width: this.props.rectWidth,
  66. });
  67. }
  68. //获取扫描框边框大小
  69. getBorderSize() {
  70. if (this.props.isCornerOffset) {
  71. return ({
  72. height: this.props.rectHeight - this.props.cornerOffsetSize * 2,
  73. width: this.props.rectWidth - this.props.cornerOffsetSize * 2,
  74. });
  75. } else {
  76. return ({
  77. height: this.props.rectHeight,
  78. width: this.props.rectWidth,
  79. });
  80. }
  81. }
  82. //获取扫描框转角的颜色
  83. getCornerColor() {
  84. return ({
  85. borderColor: this.props.cornerColor,
  86. });
  87. }
  88. //获取扫描框转角的大小
  89. getCornerSize() {
  90. return ({
  91. height: this.props.cornerBorderLength,
  92. width: this.props.cornerBorderLength,
  93. });
  94. }
  95. //获取扫描框大小
  96. getBorderWidth() {
  97. return ({
  98. borderWidth: this.props.borderWidth,
  99. });
  100. }
  101. //获取扫描框颜色
  102. getBorderColor() {
  103. return ({
  104. borderColor: this.props.borderColor,
  105. });
  106. }
  107. //渲染加载动画
  108. renderLoadingIndicator() {
  109. if (!this.props.isLoading) {
  110. return null;
  111. }
  112. return (
  113. <ActivityIndicator
  114. animating={this.props.isLoading}
  115. color={this.props.color}
  116. size='large'
  117. />
  118. );
  119. }
  120. //测量整个扫描组件的大小
  121. measureTotalSize(e) {
  122. let totalSize = e.layout;
  123. this.setState({
  124. topWidth: totalSize.width,
  125. })
  126. }
  127. //测量扫描框的位置
  128. measureRectPosition(e) {
  129. let rectSize = e.layout;
  130. this.setState({
  131. topHeight: rectSize.y,
  132. leftWidth: rectSize.x,
  133. })
  134. }
  135. //获取顶部遮罩高度
  136. getTopMaskHeight() {
  137. if (this.props.isCornerOffset) {
  138. return this.state.topHeight + this.props.rectHeight - this.props.cornerOffsetSize;
  139. } else {
  140. return this.state.topHeight + this.props.rectHeight;
  141. }
  142. }
  143. //获取底部遮罩高度
  144. getBottomMaskHeight() {
  145. if (this.props.isCornerOffset) {
  146. return this.props.rectHeight + this.state.topHeight - this.props.cornerOffsetSize;
  147. } else {
  148. return this.state.topHeight + this.props.rectHeight;
  149. }
  150. }
  151. //获取左右两边遮罩高度
  152. getSideMaskHeight() {
  153. if (this.props.isCornerOffset) {
  154. return this.props.rectHeight - this.props.cornerOffsetSize * 2;
  155. } else {
  156. return this.props.rectHeight;
  157. }
  158. }
  159. //获取左右两边遮罩宽度
  160. getSideMaskWidth() {
  161. if (this.props.isCornerOffset) {
  162. return this.state.leftWidth + this.props.cornerOffsetSize;
  163. } else {
  164. return this.state.leftWidth;
  165. }
  166. }
  167. getBottomMenuHeight() {
  168. return ({
  169. bottom: this.props.bottomMenuHeight,
  170. });
  171. }
  172. getScanBarMargin() {
  173. return ({
  174. marginRight: this.props.scanBarMargin,
  175. marginLeft: this.props.scanBarMargin,
  176. })
  177. }
  178. getScanImageWidth() {
  179. return this.props.rectWidth - this.props.scanBarMargin * 2
  180. }
  181. //绘制扫描线
  182. _renderScanBar() {
  183. if(!this.props.isShowScanBar) return;
  184. if (this.props.scanBarImage) {
  185. return <Image style={ {resizeMode: 'contain', width: this.getScanImageWidth()}}
  186. source={this.props.scanBarImage}/>
  187. } else {
  188. return <View style={[this.getScanBarMargin(), {
  189. backgroundColor: this.props.scanBarColor,
  190. height: this.props.scanBarHeight,
  191. }]}/>
  192. }
  193. }
  194. render() {
  195. const animatedStyle = {
  196. transform: [
  197. {translateY: this.state.animatedValue}
  198. ]
  199. };
  200. return (
  201. <View
  202. onLayout={({nativeEvent: e}) => this.measureTotalSize(e)}
  203. style={[styles.container, this.getBottomMenuHeight()]}>
  204. <View style={[styles.viewfinder, this.getRectSize()]}
  205. onLayout={({nativeEvent: e}) => this.measureRectPosition(e)}
  206. >
  207. {/*扫描框边线*/}
  208. <View style={[
  209. this.getBorderSize(),
  210. this.getBorderColor(),
  211. this.getBorderWidth(),
  212. ]}>
  213. <Animated.View
  214. style={[
  215. animatedStyle,]}>
  216. {this._renderScanBar()}
  217. </Animated.View>
  218. </View>
  219. {/*扫描框转角-左上角*/}
  220. <View style={[
  221. this.getCornerColor(),
  222. this.getCornerSize(),
  223. styles.topLeftCorner,
  224. {
  225. borderLeftWidth: this.props.cornerBorderWidth,
  226. borderTopWidth: this.props.cornerBorderWidth,
  227. }
  228. ]}/>
  229. {/*扫描框转角-右上角*/}
  230. <View style={[
  231. this.getCornerColor(),
  232. this.getCornerSize(),
  233. styles.topRightCorner,
  234. {
  235. borderRightWidth: this.props.cornerBorderWidth,
  236. borderTopWidth: this.props.cornerBorderWidth,
  237. }
  238. ]}/>
  239. {/*加载动画*/}
  240. {this.renderLoadingIndicator()}
  241. {/*扫描框转角-左下角*/}
  242. <View style={[
  243. this.getCornerColor(),
  244. this.getCornerSize(),
  245. styles.bottomLeftCorner,
  246. {
  247. borderLeftWidth: this.props.cornerBorderWidth,
  248. borderBottomWidth: this.props.cornerBorderWidth,
  249. }
  250. ]}/>
  251. {/*扫描框转角-右下角*/}
  252. <View style={[
  253. this.getCornerColor(),
  254. this.getCornerSize(),
  255. styles.bottomRightCorner,
  256. {
  257. borderRightWidth: this.props.cornerBorderWidth,
  258. borderBottomWidth: this.props.cornerBorderWidth,
  259. }
  260. ]}/>
  261. </View>
  262. <View style={[
  263. this.getBackgroundColor(),
  264. styles.topMask,
  265. {
  266. bottom: this.getTopMaskHeight(),
  267. width: this.state.topWidth,
  268. }
  269. ]}/>
  270. <View style={[
  271. this.getBackgroundColor(),
  272. styles.leftMask,
  273. {
  274. height: this.getSideMaskHeight(),
  275. width: this.getSideMaskWidth(),
  276. }
  277. ]}/>
  278. <View style={[
  279. this.getBackgroundColor(),
  280. styles.rightMask,
  281. {
  282. height: this.getSideMaskHeight(),
  283. width: this.getSideMaskWidth(),
  284. }]}/>
  285. <View style={[
  286. this.getBackgroundColor(),
  287. styles.bottomMask,
  288. {
  289. top: this.getBottomMaskHeight(),
  290. width: this.state.topWidth,
  291. }]}/>
  292. <View style={{position: 'absolute', bottom: this.props.hintTextPosition}}>
  293. <Text style={this.props.hintTextStyle}>{this.props.hintText}</Text>
  294. </View>
  295. </View>
  296. );
  297. }
  298. componentDidMount() {
  299. this.scannerLineMove();
  300. }
  301. scannerLineMove() {
  302. this.state.animatedValue.setValue(0); //重置Rotate动画值为0
  303. Animated.timing(this.state.animatedValue, {
  304. toValue: this.props.rectHeight,
  305. duration: this.props.scanBarAnimateTime,
  306. easing: Easing.linear
  307. }).start(() => this.scannerLineMove());
  308. }
  309. }
  310. /**
  311. * 扫描界面
  312. */
  313. export default class QRScannerView extends Component {
  314. static propTypes = {
  315. maskColor: PropTypes.string,
  316. borderColor: PropTypes.string,
  317. cornerColor: PropTypes.string,
  318. borderWidth: PropTypes.number,
  319. cornerBorderWidth: PropTypes.number,
  320. cornerBorderLength: PropTypes.number,
  321. rectHeight: PropTypes.number,
  322. rectWidth: PropTypes.number,
  323. isLoading: PropTypes.bool,
  324. isCornerOffset: PropTypes.bool,//边角是否偏移
  325. cornerOffsetSize: PropTypes.number,
  326. bottomMenuHeight: PropTypes.number,
  327. scanBarAnimateTime: PropTypes.number,
  328. scanBarColor: PropTypes.string,
  329. scanBarImage: PropTypes.any,
  330. scanBarHeight: PropTypes.number,
  331. scanBarMargin: PropTypes.number,
  332. hintText: PropTypes.string,
  333. hintTextStyle: PropTypes.object,
  334. hintTextPosition:PropTypes.number,
  335. renderTopBarView:PropTypes.func,
  336. renderBottomMenuView:PropTypes.func,
  337. isShowScanBar:PropTypes.bool,
  338. bottomMenuStyle:PropTypes.object,
  339. onScanResultReceived:PropTypes.func,
  340. };
  341. constructor(props) {
  342. super(props);
  343. //通过这句代码屏蔽 YellowBox
  344. console.disableYellowBox = true;
  345. }
  346. render() {
  347. return (
  348. <View style={{flex: 1}}>
  349. <Camera
  350. onBarCodeRead={this.props.onScanResultReceived}
  351. style={{flex: 1}}
  352. >
  353. {/*绘制顶部标题栏组件*/}
  354. {this.props.renderTopBarView()}
  355. {/*绘制扫描遮罩*/}
  356. <QRScannerRectView
  357. maskColor={this.props.maskColor}
  358. cornerColor={this.props.cornerColor}
  359. borderColor={this.props.borderColor}
  360. rectHeight={this.props.rectHeight}
  361. rectWidth={this.props.rectWidth}
  362. borderWidth={this.props.borderWidth}
  363. cornerBorderWidth={this.props.cornerBorderWidth}
  364. cornerBorderLength={this.props.cornerBorderLength}
  365. isLoading={this.props.isLoading}
  366. cornerOffsetSize={this.props.cornerOffsetSize}
  367. isCornerOffset={this.props.isCornerOffset}
  368. bottomMenuHeight={this.props.bottomMenuHeight}
  369. scanBarAnimateTime={this.props.scanBarAnimateTime}
  370. scanBarColor={this.props.scanBarColor}
  371. scanBarHeight={this.props.scanBarHeight}
  372. scanBarMargin={this.props.scanBarMargin}
  373. hintText={this.props.hintText}
  374. hintTextStyle={this.props.hintTextStyle}
  375. scanBarImage={this.props.scanBarImage}
  376. hintTextPosition={this.props.hintTextPosition}
  377. isShowScanBar={this.props.isShowScanBar}
  378. />
  379. {/*绘制底部操作栏*/}
  380. <View style={[styles.buttonsContainer, this.props.bottomMenuStyle]}>
  381. {this.props.renderBottomMenuView()}
  382. </View>
  383. </Camera>
  384. </View>
  385. );
  386. }
  387. }
  388. const styles = StyleSheet.create({
  389. buttonsContainer: {
  390. position: 'absolute',
  391. height: 100,
  392. bottom: 0,
  393. left: 0,
  394. right: 0,
  395. },
  396. container: {
  397. alignItems: 'center',
  398. justifyContent: 'center',
  399. position: 'absolute',
  400. top: 0,
  401. right: 0,
  402. left: 0,
  403. },
  404. viewfinder: {
  405. alignItems: 'center',
  406. justifyContent: 'center',
  407. },
  408. topLeftCorner: {
  409. position: 'absolute',
  410. top: 0,
  411. left: 0,
  412. },
  413. topRightCorner: {
  414. position: 'absolute',
  415. top: 0,
  416. right: 0,
  417. },
  418. bottomLeftCorner: {
  419. position: 'absolute',
  420. bottom: 0,
  421. left: 0,
  422. },
  423. bottomRightCorner: {
  424. position: 'absolute',
  425. bottom: 0,
  426. right: 0,
  427. },
  428. topMask: {
  429. position: 'absolute',
  430. top: 0,
  431. },
  432. leftMask: {
  433. position: 'absolute',
  434. left: 0,
  435. },
  436. rightMask: {
  437. position: 'absolute',
  438. right: 0,
  439. },
  440. bottomMask: {
  441. position: 'absolute',
  442. bottom: 0,
  443. }
  444. });