前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版

React Native相机扫码实战从权限配置到扫描线动画

首页2019-10-02 11:30:12Front-End
RNReact扫码

给运动手表做绑定功能,最自然的交互就是扫机身上那个二维码。听起来是个一天能搞完的活,实际做下来在原生配置上卡了大半天,Android 编译报 missingDimensionStrategy 相关的错,权限给了相机还是黑屏,扫到码之后弹框弹了三四次。这些坑一个都不在业务代码里。

这篇把当时用 react-native-camera 做扫码页的完整过程拆开讲,从原生工程配置、权限声明,一路讲到遮罩布局、扫描线动画和重复扫码的去抖处理。更多详情可以看官方文档 https://github.com/react-native-community/react-native-camera 。

在本篇文章中,我们将从浅入深,和大家一起学习以下知识:

  • 一个扫码页到底要解决哪几件事,为什么它比看上去麻烦
  • react-native-camera 的原生配置,missingDimensionStrategy 那一行是干什么的
  • AndroidManifest.xml 里哪些权限是扫码必需的,哪些是顺带声明的
  • 用四块半透明遮罩围出中间的扫码区,这种布局怎么搭
  • 用 Animated 做上下往复的扫描线,以及动画循环的写法
  • 扫到码之后为什么会连续触发多次,怎么做去抖
  • 取景框 ViewFinder 组件的封装,四个角是怎么画出来的
  • 这个库现在已经归档了,社区转向了什么

先看做出来的效果。

React Native 扫码页效果,中间为扫码取景框,四周为半透明遮罩

# 一、扫码页要解决的四件事

动手之前先把要做的事列清楚,代码结构就不会乱。

第一件是把相机预览铺满屏幕。react-native-camera 提供的 RNCamera 组件本身就是一个 View,给它设宽高就能显示预览画面。

第二件是识别二维码。这个库内置了条码识别能力,只要指定要识别的码制类型,识别到就通过回调把结果给你。这一步几乎不用自己写逻辑。

第三件是画界面。相机预览是铺满的,但用户需要一个明确的「把码放这里」的视觉引导。常见做法是在预览上盖一层半透明黑色遮罩,中间挖一个方形的洞,洞里画四个角的取景框,再加一条上下移动的扫描线。这部分是工作量最大的。

第四件是处理扫码结果。识别是持续进行的,同一个码在一秒内会被识别很多次,直接弹框会弹一堆。要做去重和状态控制。

四件事里,第三第四件才是真正花时间的地方。

# 二、装包和原生配置

装完包之后要把原生模块链接进工程。

react-native link react-native-camera
@前端进阶之旅: 代码已经复制到剪贴板

这条命令会自动修改 Android 和 iOS 的原生工程文件,把库的代码加进构建流程。需要说明的是,react-native link 这套手动链接机制在后来的版本里已经被自动链接(autolinking)取代了,装完包直接重新编译就行,不需要再执行 link。老项目升级上来的时候如果两套机制并存,反而会因为重复注册报错。

接着改 Android 的构建配置。

// 配置andriod/app/src/build.gradle 

defaultConfig {
    applicationId "com.jtyapps"
    minSdkVersion rootProject.ext.minSdkVersion
    targetSdkVersion rootProject.ext.targetSdkVersion
    versionCode 1
    versionName "1.0"
    ndk {
        abiFilters "armeabi-v7a", "x86"
    }
    // 添加这里
    missingDimensionStrategy 'react-native-camera', 'general'
}

// 配置andriod/gradle/wrapper/gradle-wrapper.properties
// 本教程使用的是这个版本
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
@前端进阶之旅: 代码已经复制到剪贴板

missingDimensionStrategy 这一行值得解释一下,因为不加它编译一定报错,而报错信息又看不出所以然。

react-native-camera 这个库在内部定义了一个叫 react-native-camera 的构建维度(flavor dimension),下面有两个取值。一个是 general,用的是 Android 自带的条码识别;另一个是 mlkit,接的是 Google 的机器学习套件,识别更强但要引入 Google Play 服务。Gradle 在合并依赖时发现你的主工程没有声明这个维度,就不知道该选哪个变体,于是直接报错。missingDimensionStrategy 的作用就是告诉它「我这边没这个维度,你就按 general 来」。

国内发行的应用一般选 general,因为 mlkit 依赖 Google Play 服务,国行手机上大多没有。

abiFilters 那两行是限制打包进 APK 的 CPU 架构。只留 armeabi-v7a 和 x86 能显著减小包体积,但要注意 arm64-v8a 被排除掉了,在 64 位设备上会以兼容模式运行 32 位库。这在当年问题不大,现在部分应用市场对 64 位支持是有硬性要求的,实际项目里得按上架要求来配。

gradle-4.10.1 这个版本是当时的选择,现在肯定要跟着 Android Gradle Plugin 的要求走,以官方文档为准。

# 三、权限清单

权限声明在 andriod/app/src/main/AndroidManifest.xml 里。

  <uses-permission android:name="android.permission.BLUETOOTH"/>
  <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
  <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
  <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
  <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
  <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
  <uses-permission android:name="android.permission.INTERNET"/>
  <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
  <uses-permission android:name="android.permission.CAMERA"/> 
  <uses-permission android:name="android.permission.VIBRATE"/>
  <uses-feature android:name="android.hardware.camera" android:required="false"/>
  <uses-feature android:name="android.hardware.camera.front" android:required="false"/>
@前端进阶之旅: 代码已经复制到剪贴板

这一长串里,扫码真正需要的只有三条。CAMERA 是相机权限,缺了它 RNCamera 会一直黑屏。VIBRATE 用来在扫码成功时震一下,给用户一个明确的反馈。两条 uses-feature 声明相机是可选特性,required="false" 很重要,写成 true 的话没有摄像头的设备在应用市场里会直接看不到你的应用。

剩下那些是这个项目里其他功能带的。蓝牙和定位是心率带那部分需要的(这块的完整实现在 React Native 蓝牙连接心率带设备里),网络状态是断网检测用的,存储权限是保存图片用的。

顺带修一处原文里的拼写错误。原来写的是 READ_EXTERNAL_STORAGEE,末尾多了一个 E,正确的常量名是 READ_EXTERNAL_STORAGE。上面代码块里我已经改过来了。这个错误的表现是权限静默失效,Android 不认识这个字符串就当没声明,运行时申请这个权限会一直失败,很容易查半天以为是机型问题。

另外要提醒的是,Android 6.0 之后 CAMERA 属于危险权限,光在清单里声明不够,运行时还得动态申请。原代码里用的是 RNCamera 组件自带的 permissionDialogTitle 和 permissionDialogMessage 两个属性,由组件内部去申请。这两个属性在后续版本里被 androidCameraPermissionOptions 这类新写法替代了,具体以你用的版本文档为准。

iOS 那边不用配这么多,只要在 Info.plist 里加上 NSCameraUsageDescription 说明用途,文案会显示在系统的权限弹框里,审核也会看这句话写得是否合理。

# 四、扫码页的整体结构

先看渲染部分的骨架。

<View style={styles.allContainer}>
    <RNCamera
        barCodeTypes={[RNCamera.Constants.BarCodeType.qr]}
        onBarCodeRead={this.barcodeReceived.bind(this)}
        onCameraReady={() => { console.log('ready') }}
        permissionDialogTitle={'Permission to use camera'}
        permissionDialogMessage={'We need your permission to use your camera phone'}
        style={styles.cameraStyle}
    >
        {/* 顶部导航条(半透明黑) */}
        {/* 中间留白(半透明黑) */}
        {/* 扫码行:左遮罩 + 取景框 + 右遮罩 */}
        {/* 底部提示(半透明黑) */}
    </RNCamera> 
</View>
@前端进阶之旅: 代码已经复制到剪贴板

RNCamera 铺满全屏,所有 UI 都作为它的子元素叠在预览画面之上。这里的关键点是 barCodeTypes,只声明 qr 一种码制。识别的码制越少性能越好,如果还要扫条形码就把对应的类型加进数组。

onBarCodeRead 是识别回调,识别到就调用,参数里的 e.data 是解析出来的字符串内容。

fe
  • 一、扫码页要解决的四件事
  • 二、装包和原生配置
  • 三、权限清单
  • 四、扫码页的整体结构
  • 五、扫描线动画
  • 六、扫到码之后的去抖
  • 七、取景框组件
  • 八、页面怎么进
  • 九、这个库现在的情况
  • 总结
  • 参考

← React Native图片宽高与字体的平台适配实战React Native蓝牙连接心率带设备实战 →