更新记录
1.6.0(2026-10-28)
功能
startScan新增autoClose参数(默认true):传false时识别成功不再自动关闭扫码页,而是停掉相机、把画面定格在最后一帧,等开发者自行关闭- 新增
closeScan()API:关闭当前打开着的扫码页,配合autoClose: false在success回调里调用;页面不存在时静默返回,不会二次触发任何回调
实现说明
- 手动关闭(
autoClose: false):三端都在发成功回调之前「定格 + 停相机」——Android 用TextureView.getBitmap()抓一帧盖成全屏ImageView再stopRepeating();iOS 用AVCaptureVideoPreviewLayer.render(in:)截图后stopRunning();鸿蒙复用点选层的createPixelMapFromSurface抓帧落盘 +customScan.stop()。关闭入口统一为closeScan():Android 经ScanResultBus.setCloseHandler回调 Activityfinish(),iOS 用strongController.closeManually()dismiss,鸿蒙经SCAN_DIALOG.closeRequest()→closeDialog();三端都只关页面、不再触发任何回调 - 定格态仍保留「X / 关闭」按钮:JS 忘记调
closeScan()时用户还能自己退出去(鸿蒙端同时把返回键也接到同一入口)
1.5.0(2026-10-25)
修复
- 修正 Android / iOS 返回的
format不实:Android 的 ZXing 兜底命中后固定上报QR_CODE,iOS 的系统扫码(AVCaptureMetadataOutput)通道也固定上报QR_CODE,扫条形码会被当成二维码返回,且同一张码走不同通道结果还会不一致。现按真实码制映射(与鸿蒙、iOS Vision 通道共用同一张映射表) - 修正 Android ZXing 兜底只认二维码:兜底解码器的
POSSIBLE_FORMATS原先硬写QR_CODE且用的是QRCodeReader,formats配成条形码时 ML Kit 一失手,兜底完全帮不上忙(等于没有兜底)。现改用MultiFormatReader,尝试的码制由formats配置推导 - Android / iOS 多码点选返回的
format不再固定QR_CODE:点选层此前只携带内容与坐标,用户点中的是条形码也会被报成二维码。现点选层带上每个码的真实码制,与鸿蒙端PickItem行为一致 - 鸿蒙多码点选层「定格底图」卡顿:裁边原来把整幅像素又读了一遍(720×1280 RGBA ≈3.7MB)并逐行逐列全量扫描,点选层弹出时会在 UI 线程顿一下。现只读中间两条各 16 像素宽的带子判定四条黑边,读取量降到约 128KB
- iOS 扫码页发热 / 掉帧:帧回调里每帧都
CIContext()新建一个上下文(内含 GPU 上下文与着色器缓存),代价比图像转换本身还大。现改为全局复用同一个CIContext - 修复 Android / iOS 多码点选层「箭头不居中」:标记原先用
TextView(iOS 是UIButton标题)显示「→」字符,圆圈位置本身是对的,但字符受includeFontPadding/ 字体上下留白不对称、以及字形左右 bearing 影响,视觉上总偏向一边。现两端都改为自绘箭头(AndroidArrowMarkerView、iOSPickMarkerView):绿圆 + 白描边 + 横杆与箭头,全部按半径等比、以圆心为几何中心,跨机型一致;顺带把箭头放大(横杆 0.92r → 1.16r,描边 2 → 2.5)
实现说明
- 实时多码定格底图的裁边取样:黑边一定贯穿整幅,所以不再读整帧——只读中间 16 列(判上下黑边)与中间 16 行(判左右黑边)两条带子,某行/列采样点 ≥92% 偏黑即判为补黑(原为 98%;样本从整行降到 16 个后 98% 等价于「全黑」,一点噪点就会把黑边判成内容);四条边都从边缘往里扫、命中即停;裁出的框小于原图 40% 判定为暗场景误裁,改用整幅;
readPixels异常时降级为整幅。读取量从 720×1280 RGBA ≈3.7MB 降到约 128KB - Android 兜底解码:ML Kit 漏检时用 ZXing
MultiFormatReader再试(全图 / 反色 / 中心裁切 / 对比度拉伸),尝试的码制由formats配置推导;跑在独立的mlkit-scan-fallback线程上——相机请求与ImageReader回调都排在相机 Looper 上,一次兜底几十毫秒就会让预览掉帧
1.4.0(2026-10-16)
功能
- 新增鸿蒙(App-Harmony)支持,
package.jsonplatforms 加入app-harmony - 鸿蒙端同样是自研全屏页:黑色背景、无扫码框、绿色扫描线、右上角相册、多码定格点选、手电筒、双指缩放,交互与 Android / iOS 对齐
- 鸿蒙扫描线与 Android / iOS 逐参对齐:径向渐变光带(
rx = 0.72w、ry = 0.22h,只露椭圆顶部0.25ry那条弧,颜色#6400FF6A → #5A00FF6A → #2400FF6E → 透明,2400ms 线性无限循环),不再是固定长度的实心细线 - 14 种识别格式在鸿蒙端全覆盖(含
AZTEC,ScanKit 的AZTEC_CODE是真实存在的枚举值) - 鸿蒙端
decodeImage支持相册选图返回的content://URI(自动读入沙箱后解码)
修复
- 鸿蒙实时多码点选不再透出相机预览:改为「定格底图 + 位置标记」。此前底图是半透明遮罩,多码点选时背景里的相机画面还在动,看着像定格没生效;现在点选层背景一律不透明黑底
- 修复鸿蒙相册多码标记位置偏移:
FrozenPickLayer算图片显示矩形时宽高比取错了倒数,导致横图在竖屏容器里ratioH恒为 1、标记纵向被拉满整屏 - 修复鸿蒙多码点选层「取消」按钮点了没反应(漏了
onClick) - 鸿蒙返回键行为对齐 Android / iOS:多码点选层打开时只关点选层、退回实时扫码,再按一次才关闭整个扫码页
- 修复鸿蒙扫码页被「返回键直接关闭」时 UTS 侧 Promise 一直挂着:补
onDidDisappear→cancel兜底(幂等) - 修复鸿蒙相机预览不满屏(上下各留约 22% 屏高的黑边):
XComponent的 surface 比例原先只按「9/16、3/4、1 里最接近屏幕比例者」猜,选出的 9/16 恰好是本机相机给不出的档位,ScanKit 只能把预览等比缩小塞进 surface。现在先用CameraKit查相机真实支持的预览比例,且当屏幕比所有档位都窄时直接取 1:1;实测黑边完全消失 - 修复鸿蒙实时多码点选层底图与实时预览对不上(底图没铺满、标记位置也对不准):定格底图原先按「整幅 surface 缓冲裁成容器比例」裁,但缓冲是相机裸帧——里面既有补黑的无效区,也有屏幕外看不到的区域,裁完既带黑边、取景范围也和屏幕不一致。现改为按实时预览的同一套几何反推「屏幕可见范围」:先找出缓冲里的有效内容区,算它在 surface 上等比 contain 的显示矩形,再按屏幕可见范围在该矩形里的占比裁图。裁完底图与实时预览逐像素对齐(实测最佳位移 dx=0、dy=0,相关度 0.98)
- 丢弃完全落在屏幕外的识别结果:
XComponent为了保住预览比例会比屏幕大(溢出部分由窗口裁掉、用户看不到),而 ScanKit 解码的是整幅 surface,会识别出屏幕外的码,出现「屏幕上没看见码却被扫出来」。现在按「矩形与屏幕可见区是否还有交集」过滤,只露一半的码仍算可见;过滤放在「本轮已见集合」之前,避免该码被记成已见、挪进屏幕后反而被去重跳过
实现说明
- 预览 + 识别:ScanKit
customScanXComponent的 surface 交给customScan.start(viewControl, cb),相机预览与多码识别都由它完成,不再是「自建相机管线」customScan.start一次只回调一轮,单码时用 350ms 确认窗口持续rescan收帧,避免「同屏两个码只识别出一个」XComponent按相机预览宽高比居中布局,与官方 SDK 做法一致;这样scanCodeRect的坐标系与预览画面严格 1:1,标记不会整体偏移。比例优先用CameraKit(getSupportedCameras+getSupportedOutputCapability().previewProfiles,均不需要 CAMERA 权限)查到的真实档位、取最接近屏幕比例者;屏幕比所有档位都窄时(绝大多数手机)直接取最大档位 1:1 —— 此时「surface 比例 ≥ 真实预览比例」恒成立,ScanKit 等比 contain 后必然铺满,且可见画面与「surface 恰好等于真实比例」时完全一致。相机上报的能力未必等于它真正渲染的预览比例(本机模拟器只上报 16:9、实际渲染 1:1),故需要这条兜底- 手电筒
openFlashLight/closeFlashLight,缩放setZoom/getZoom
- 实时多码的定格底图:
image.createPixelMapFromSurface抓XComponent的 surface → 顺时针旋转 90° 转正 → 找出「相机有效内容区」(四周 98% 纯黑的行/列视为补黑;裁出的框小于原图 40% 判定为暗场景误裁,改用整幅)→ 按「内容区在 surface 上等比 contain 的显示矩形」换算出屏幕可见范围的占比、据此裁图 → JPEG 写入沙箱 tempDir。这样裁出的底图正好等于屏幕上真正看到的那块,宽高比也正好等于容器,点选层用ImageFit.Contain即可铺满,标记直接沿用容器归一化坐标,与相册多码共用同一套落位逻辑 - 相册:
@kit.CoreFileKit的PhotoViewPicker - 相册识别失败只 toast 并继续扫码,
decodeImageAPI 不弹 toast,错误只走 fail - 全屏 dialog:
promptAction.openCustomDialog+ComponentContentonWillDismiss拦截返回键 / 侧滑,多码点选层打开时只关点选层onDidDisappear兜底补cancel,避免交互式关闭时 UTS 侧 Promise 挂死
注意
- 鸿蒙端
decodeImage的多码数量受 ScanKit 限制:单帧最多返回 4 个,超过的部分不会返回(Android / iOS 无此限制)
平台兼容性
uni-app(5.0)
| Vue2 | Vue2插件版本 | Vue3 | Vue3插件版本 | Chrome | Safari | app-vue | app-vue插件版本 | app-nvue | app-nvue插件版本 | Android | Android插件版本 | iOS | iOS插件版本 | 鸿蒙 | 鸿蒙插件版本 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| √ | 1.6.0 | √ | 1.6.0 | - | - | √ | 1.6.0 | √ | 1.6.0 | √ | 1.6.0 | √ | 1.6.0 | √ | 1.6.0 |
| 微信小程序 | 支付宝小程序 | 抖音小程序 | 百度小程序 | 快手小程序 | 京东小程序 | 鸿蒙元服务 | QQ小程序 | 飞书小程序 | 小红书小程序 | 快应用-华为 | 快应用-联盟 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| - | - | - | - | - | - | - | - | - | - | - | - |
uni-app x(5.0)
| Chrome | Safari | Android | Android插件版本 | iOS | iOS插件版本 | 鸿蒙 | 鸿蒙插件版本 | 微信小程序 |
|---|---|---|---|---|---|---|---|---|
| - | - | √ | 1.6.0 | √ | 1.6.0 | √ | 1.6.0 | - |
其他
| 多语言 | 暗黑模式 | 宽屏模式 | 蒸汽模式 |
|---|---|---|---|
| × | × | × | √ |
mlkit-scancode
App 端全屏扫码插件。Android 使用 Camera2 取帧,再用 ML Kit 识别,识别不到时用 ZXing 补一次。iOS 使用 AVFoundation,同时走 Vision 和系统扫码接口。鸿蒙使用 ScanKit 的 customScan,预览与多码识别一并完成。
页面没有扫码框。右上角带「相册」按钮,可从相册选图识别。同屏多个二维码时先点选,再返回选中的内容。
平台支持
| 平台 | 实现方式 | 最低版本 |
|---|---|---|
| App-Android | Camera2 + ML Kit + ZXing | Android 5.0(API 21) |
| App-iOS | AVFoundation + Vision + 系统扫码 | iOS 12.0 |
| App-Harmony | ScanKit customScan(预览 + 多码识别) |
HarmonyOS 5.0(API 12) |
三端都是自研全屏页,交互一致:无扫码框、右上角相册、多码点选、手电筒、双指缩放。
鸿蒙端说明:预览与识别都交给 ScanKit 的 customScan —— 把 XComponent 的 surface 交给 customScan.start(viewControl, cb),相机预览与多码识别由它一并完成,不再是「自建相机管线」。start 一次只回调一轮,拿到结果后必须 rescan 才能继续收帧;单码时用 350ms 的确认窗口持续收帧,避免「同屏两个码只识别出一个」。实时多码点选时用 image.createPixelMapFromSurface 抓一帧定格底图当背景。
本插件只支持 App。Web、小程序请继续使用 uni.scanCode。
不要在插件里再引入 CameraX。基座的 Camera、Barcode 模块已经带有一套 CameraX,版本更高时会在打开扫码页时崩溃。
安装
插件在项目内,从 @/uni_modules/mlkit-scancode 导入。
修改原生代码后,必须重新打自定义调试基座或云打包。标准基座不会加载这里的原生实现。
首次扫码会申请相机权限。Android 需要 CAMERA,iOS 需要在 manifest.json 中配置 NSCameraUsageDescription,鸿蒙的权限已声明在插件的 utssdk/app-harmony/module.json5 里,无需额外配置。
鸿蒙端首次运行前,需要在 manifest.json 里补上 app-harmony 配置段(工程目前只有 app-plus),并在 HBuilderX 的 DCloud 控制台配置好 AppHarmonyOS 的 appid 与签名,然后真机运行到 HarmonyOS。
使用
扫码(打开扫码页)
import { startScan } from '@/uni_modules/mlkit-scancode'
startScan({
tip: '将二维码对准屏幕,即可自动扫描',
formats: ['QR_CODE'], // 常见取值见「支持的识别格式」
vibrate: true,
success: (res) => {
console.log('扫码结果:', res.result, res.format)
},
fail: (err) => {
console.error('扫码失败:', err.errCode, err.errMsg)
},
complete: (res) => {
console.log('扫码操作完成')
},
})
业务入口可以走 src/utils/openScanCode.js。它在 App 端调用本插件,其他端回退 uni.scanCode,返回值仍是扫码文本。
图片识别(不打开扫码页)
import { decodeImage } from '@/uni_modules/mlkit-scancode'
decodeImage({
path: 'file:///storage/emulated/0/DCIM/xxx.jpg',
formats: ['QR_CODE'],
success: (res) => {
console.log('识别结果:', res.result, res.format)
},
fail: (err) => {
console.error('识别失败:', err.errCode, err.errMsg)
},
})
path 支持 file:// URI、绝对路径,以及各端的沙箱虚拟路径(_doc/、_user/、internal://、sandbox:// 会自动拼到沙箱目录)。不支持 http:// / https:// 网络图片,请先下载再传入。鸿蒙端相册选图返回的 content:// URI 会自动读入沙箱再解码,可以直接传。
API
startScan(options)
StartScanOptions
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| tip | string | 否 | 将二维码对准屏幕,即可自动扫描 | 扫码页提示文案 |
| formats | string[] | 否 | ['QR_CODE'] |
识别格式。不传只认二维码,支持的值见下节 |
| vibrate | boolean | 否 | true | 成功后是否震动 |
| album | boolean | 否 | true | 是否显示右上角「相册」按钮,允许从相册选图识别 |
| autoClose | boolean | 否 | true | 扫到码后是否自动关闭扫码页。传 false 时页面停在最后一帧,需在 success 里调 closeScan() 关闭 |
| success | (res) => void | 否 | - | 成功回调。多个码时,回调的是用户点选的那一个 |
| fail | (err) => void | 否 | - | 失败回调 |
| complete | (res) => void | 否 | - | 成功或失败都会触发 |
支持的识别格式
formats 传字符串数组,三端行为对齐;不区分大小写,逗号或数组元素均可(内部会做 trim/去重)。
| 值 | Android | iOS | Harmony | 备注 |
|---|---|---|---|---|
QR_CODE 或 QR |
✔ | ✔ | ✔ | 二维码,默认格式 |
AZTEC |
✔ | ✔ | ✔ | 二维矩阵码 |
CODABAR |
✔ | ✔ | ✔ | iOS 需 15.0+ |
CODE_39 |
✔ | ✔ | ✔ | 一维码 |
CODE_93 |
✔ | ✔ | ✔ | 一维码 |
CODE_128 |
✔ | ✔ | ✔ | 一维码 |
DATA_MATRIX |
✔ | ✔ | ✔ | 二维矩阵码 |
EAN_8 |
✔ | ✔ | ✔ | 一维码 |
EAN_13 |
✔ | ✔ | ✔ | 一维码 |
ITF |
✔ | ✔ | ✔ | iOS 走 i2of5,鸿蒙对应 ITF14 |
PDF417 |
✔ | ✔ | ✔ | 二维条码 |
UPC_A |
✔ | ✔ | ✔ | iOS 由 EAN_13 覆盖 |
UPC_E |
✔ | ✔ | ✔ | 一维码 |
ALL / ALL_FORMATS |
✔ | ✔ | ✔ | 一次性打开全部格式 |
回调里 format 字段返回的是上表第一列里的名字,多码同屏时按用户点选的那个返回(该码自身的码制,不再固定 QR_CODE);识别到但拿不到坐标、走「点选回退」列表时同样返回各码自己的码制。
ScanSuccess
| 字段 | 类型 | 说明 |
|---|---|---|
| result | string | 扫码内容 |
| format | string | 格式,例如 QR_CODE |
| errMsg | string | startScan:ok |
错误码
| 错误码 | 说明 |
|---|---|
| 9010001 | 无法打开扫码页 |
| 9010002 | 相机权限被拒绝 |
| 9010003 | 用户取消 |
| 9010004 | 没有识别到内容 |
| 9010005 | 图片读取失败(decodeImage 专用) |
取消不要当成无效二维码提示。openScanCode.js 用 isScanCanceled 判断 9010003。
closeScan()
关闭当前打开着的扫码页。配合 startScan({ autoClose: false }) 使用:识别成功后页面不会自动关闭,你可以在 success 里先做校验 / 上传 / 弹自己的 UI,处理完再调 closeScan()。
import { startScan, closeScan } from '@/uni_modules/mlkit-scancode'
startScan({
autoClose: false,
success: async (res) => {
// 页面停在识别成功的那一帧,相机已停止,可以放心做异步处理
await submitCode(res.result)
closeScan()
},
})
- 页面不存在(已关闭 / 从没打开)时静默返回,不触发
fail/cancel - 只关页面,不会二次触发
success/fail/complete - 定格态下不再继续识别;要重新扫请先
closeScan()再调一次startScan decodeImage不打开页面,与本方法无关
注意:定格期间扫码页仍在前台(Android 是独立 Activity、iOS 是全屏 present 的 VC、鸿蒙是 CustomDialog),
uni.showModal/uni.showLoading这类页面级 UI 会被它遮住,用户也点不到。需要给用户看进度,请先把状态记在自己页面的 data 里,关页后再展示(demo 页就是这么做的)。
decodeImage(options)
从图片路径解码条码内容,不打开扫码页。图片里有多个码时会全部返回。
DecodeImageOptions
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| path | string | 是 | 图片路径。支持 file:// URI、绝对路径。_doc/ 路径请先转换 |
| formats | string[] | 否 | 识别格式,默认 ['QR_CODE'] |
| success | (res) => void | 否 | 成功回调。图片中有多个码时,results 返回全部 |
| fail | (err) => void | 否 | 失败回调 |
| complete | (res) => void | 否 | 成功或失败都会触发 |
DecodeImageSuccess
| 字段 | 类型 | 说明 |
|---|---|---|
| result | string | 第一个码的内容,等价于 results[0].result |
| format | string | 第一个码的格式,等价于 results[0].format |
| results | ScanCodeItem[] | 图片中识别到的全部码,已按内容去重 |
| errMsg | string | 固定为 decodeImage:ok |
results 中每项为 { result: string, format: string }。
鸿蒙端的
results受 ScanKit 限制:单帧最多返回 4 个码,超过的部分不会返回。Android / iOS 无此限制。
decodeImage({
path,
success: (res) => {
console.log(res.results.length) // 码的个数
console.log(res.results[0].result) // 第一个码的内容
},
fail: (err) => console.log(err),
})
错误码
| 错误码 | 说明 |
|---|---|
| 9010001 | 无法获取页面 |
| 9010004 | 未识别到内容 |
| 9010005 | 图片读取失败 |
目录
uni_modules/mlkit-scancode/
├── package.json
├── readme.md
├── changelog.md
└── utssdk/
├── interface.uts
├── unierror.uts
├── app-android/
├── app-ios/
└── app-harmony/
许可
MIT

收藏人数:
购买源码授权版(
试用
使用 HBuilder 导入示例项目
赞赏(0)
下载 108
赞赏 0
下载 12736062
赞赏 1959
赞赏
京公网安备:11010802035340号