成品扫一扫页
<Scanner> 是开箱即用的成品扫码界面(聚焦款,浅色)。底层用 <HmsScanView> 出相机画面,取景框 / 工具栏 / 结果卡全用 @unif/react-native-design 的主题令牌与组件绘制。
它自带 ThemeProvider、权限流和状态机,可直接作为一个路由整屏接入;放进宿主已有的 ThemeProvider 里也兼容。
ToastHost 由宿主按需在 App 根部挂载。
<Scanner> vs <HmsScanView>要现成的扫一扫页 → 用 <Scanner>(本页)。要完全自绘 UI(自定义取景框 / 工具栏布局)→ 用底层 <HmsScanView>。
内部状态机
权限流:
init ─ 已授权 → scan(取景)
├─ 未授权 → denied(无权限遮罩)→ 去系统设置 → 返回后自动重查
└─ helper reject → error(fatal)→ 重试 → init
识别流:
scan → detecting(识别中)
├─ 解析成功 → success(浮层确认卡)→ 确认 / 重扫 → scan
├─ autoConfirm + onConfirm 正常返回 → done(暂停终态)
└─ 未识别 / 解析或 onConfirm 抛错 → fail(未识别弹层)→ 重扫 → scan
view error:
scan / detecting ─ E_NO_CAMERA_PERMISSION → denied
├─ 其他 fatal error → error → 重试 → init
└─ E_NO_RESULT → 只上报,phase 不变
<Scanner> 挂载时自动请求相机权限:已授权直接进入取景;永久拒绝则展示引导去系统设置的遮罩(denied),从系统设置返回后会自动重新查询。一次扫一个 —— 扫到 results[0] 即进入 detecting;手动确认或重扫后复位到 scan。autoConfirm 仅在传入 onConfirm 时调用回调后进入 done,未传回调则显示结果卡;相机保持暂停且不会自动重扫。onConfirm 同步抛错会被 resolveProduct 那一层的 catch 收成 fail,不会进入 done。相机 view error 分三路:E_NO_RESULT 仅作 soft error 回调上报;E_NO_CAMERA_PERMISSION 进入 denied 并卸载相机 view;其余 fatal view error 进入可重试的 error。权限 helper reject 也进入 error。
商品解析 resolveProduct
扫到条码后,<Scanner> 把 ScanResult 交给 resolveProduct,由你解析成商品信息(用于浮层确认卡):
<Scanner
resolveProduct={async (result) => {
const product = await api.lookupByBarcode(result.value);
if (!product) return null; // null = 未识别 → 进入 fail 重扫弹层
return {
name: product.name, // 仅 name 必填
brand: product.brand,
price: `¥${product.price}`,
spec: product.spec,
stockShort: product.stockText,
};
}}
/>
- 返回
null/undefined或抛错,均视为「未识别」,进入fail重扫弹层。 - 不传
resolveProduct时,默认以扫到的原文(result.value)作为商品名。 - 返回的
ScanProduct中只有name必填,其余(brand/price/spec/stockShort/brandChar/priceCaption等)可缺省;barcode缺省时自动取扫到的value。完整字段见 API → ScanProduct。
确认回调 onConfirm
用户在浮层确认卡点「确定」时触发,签名 (product, result):
<Scanner
onConfirm={(product, result) => {
navigation.navigate('Order', {
barcode: result.value, // 扫码原始内容
product, // resolveProduct 返回的商品
});
}}
/>
手动点「确定」会调用
onConfirm并复位回取景态;提示与导航由宿主在onConfirm中处理。
相册扫码 pickImage
库不内置图片选择器(遵循 RN 惯例):传了 pickImage 才显示「相册」按钮,不传则隐藏。宿主用自己的选择器选图,返回本地 uri(取消返回 null),<Scanner> 内部对它调 decodeImage:
import { launchImageLibrary } from 'react-native-image-picker';
<Scanner
pickImage={async () => {
const res = await launchImageLibrary({ mediaType: 'photo' });
return res.assets?.[0]?.uri ?? null; // 取消返回 null
}}
/>
// ❌ Incorrect:没传 pickImage 却期望出现相册按钮
<Scanner onConfirm={...} /> // 工具栏不会有"相册"
// ✅ Correct:传了 pickImage,相册按钮才显示
<Scanner onConfirm={...} pickImage={async () => /* 选图返回本地 uri */} />
相册识图走
decodeImage,它只接受本地 uri、不下载远程 URL。react-native-image-picker返回的就是本地路径,可直接用;细节见图片识别。
手电筒 showTorch
底部工具栏默认显示手电筒按钮(showTorch 默认 true),手电状态由库内自管:
- Android —— 可编程控制,稳定可用。
- iOS —— HMS 未提供公开手电 API,本库通过
AVCaptureDevice尽力而为,不保证点亮。
手电按钮最终以底层 onTorchStatus.on 的真实点亮状态回写;available 的含义仍按平台不同:Android 是环境暗光提示,iOS 是设备是否有手电硬件。
不想在 iOS 上呈现一个可能无效的按钮,可关掉:
import { Platform } from 'react-native';
<Scanner showTorch={Platform.OS === 'android'} />
详见平台差异 → 手电筒。
安全区 topInset / bottomInset
<Scanner> 默认 topInset=54 / bottomInset=34。用 react-native-safe-area-context 时,把 insets.top/bottom 传入更精准:
import { useSafeAreaInsets } from 'react-native-safe-area-context';
function ScanScreen() {
const insets = useSafeAreaInsets();
return (
<Scanner
topInset={insets.top}
bottomInset={insets.bottom}
onClose={() => navigation.goBack()}
// ...
/>
);
}
限定码制 formats
formats 限定识别哪些码制,不传 = 识别全部 14 种:
<Scanner
formats={['QR_CODE', 'EAN_13', 'EAN_8']}
// ...
/>
限定到业务真正需要的码制(如只扫商品条码),能减少误识、提升识别速度。全部码制清单见 API → BarcodeFormat。
自定义提示文案 hintText
取景态默认提示「将条码 / 二维码放入框内,自动扫描」,可用 hintText 覆盖:
<Scanner hintText="对准商品上的条形码" />
相关
- API 参考 → Scanner —— 完整 props 表
- 指南 → 底层 headless 组件 —— 需要自定义 UI 时用
<HmsScanView> - 指南 → 图片识别 ——
decodeImage单独使用 - 指南 → 权限处理 ——
<Scanner>自动处理;手动场景的 API