qr.js 15 KB

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