更新记录
1.0.1(2026-09-18) 下载此版本
添加预览图
1.0.0(2026-09-18) 下载此版本
遮罩覆盖层随便自定义样式
平台兼容性
uni-app(3.8.4)
| Vue2 | Vue3 | Chrome | Safari | app-vue | app-nvue | Android | iOS | 鸿蒙 |
|---|---|---|---|---|---|---|---|---|
| √ | √ | × | × | - | √ | √ | √ | - |
| 微信小程序 | 支付宝小程序 | 抖音小程序 | 百度小程序 | 快手小程序 | 京东小程序 | 鸿蒙元服务 | QQ小程序 | 飞书小程序 | 小红书小程序 | 快应用-华为 | 快应用-联盟 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| - | - | - | - | - | - | - | - | - | - | - | - |
uni-app x(3.8.11)
| Chrome | Safari | Android | iOS | 鸿蒙 | 微信小程序 |
|---|---|---|---|---|---|
| - | - | - | - | - | - |
lj-costomer-camera 自定义相机组件
基于 Camera2 / AVFoundation 实现的 App 端原生相机预览与拍照组件Android 端使用标准基座即可直接运行调试。
适用于证件拍摄(身份证、银行卡、驾照等)、自定义取景框、需要完全接管相机 UI 的场景。
为什么需要它
- 原生预览画面(无 webview 性能损耗)
- 完全自定义的 UI 覆盖层:取景框、遮罩、引导图、提示文案
- 前后摄切换、闪光灯常亮、预览尺寸指定、画面旋转微调
- 拍照结果以文件路径回传,不占内存、不卡帧
平台支持
| 平台 | 状态 | 说明 |
|---|---|---|
| App-Android | ✅ 已验证 | Camera2 + TextureView,零依赖,标准基座可运行 |
| App-iOS | ⚠️ 代码已实现 | |
| H5 / 小程序 | ❌ 不支持 | 原生扩展组件仅在 App 平台可用 |
iOS 端协议方法签名(
AVCapturePhotoCaptureDelegate)需按 HBuilderX 实际编译提示微调,欢迎提交反馈。
环境要求
- HBuilderX 3.6.18+(uni-app 兼容模式 UTS 组件支持)
- 组件只能用在 nvue 页面(uni-app 原生扩展组件的硬性限制,普通 vue 页面无法承载原生 View)
- App 端支持 Vue2 / Vue3 工程
安装
插件市场导入后,项目根目录会出现:
uni_modules/lj-costomer-camera/
├─ package.json
└─ utssdk/
├─ app-android/
│ ├─ index.vue
│ └─ config.json
└─ app-ios/
├─ index.vue
├─ config.json
└─ info.plist
快速开始
1. 新建 nvue 页面
pages/camera/camera.nvue:
<template>
<view class="page">
<!-- 组件必须显式给尺寸,flex 对它不生效 -->
<lj-costomer-camera
class="cam"
:style="camStyle"
:facing="facing"
:flash="flash"
:shoot="shootToken"
:active="pageActive"
@ready="onCamReady"
@error="onCamError"
@photo="onCamPhoto"></lj-costomer-camera>
<!-- 自定义 UI 覆盖层 -->
<view class="overlay">
<view class="band" style="flex: 1;"></view>
<view class="mid-row">
<view class="band" style="flex: 1;"></view>
<view class="frame"></view>
<view class="band" style="flex: 1;"></view>
</view>
<view class="band" style="flex: 1;"></view>
</view>
<view class="shutter" @click="takePhoto"></view>
</view>
</template>
<script>
export default {
data() {
return {
facing: 'back',
flash: 'off',
shootToken: 0,
pageActive: true,
camReady: false,
taking: false,
winW: 750,
winH: 1400
};
},
computed: {
camStyle() {
return { width: this.winW + 'px', height: this.winH + 'px' };
}
},
onLoad() {
const sys = uni.getSystemInfoSync();
this.winW = 750;
this.winH = Math.round(750 * sys.windowHeight / sys.windowWidth);
},
onShow() {
this.pageActive = true;
},
onHide() {
this.pageActive = false;
},
methods: {
// 事件参数被 weex 包在 event.detail 里
detail(e) {
return (e && e.detail) ? e.detail : (e || {});
},
onCamReady() {
this.camReady = true;
},
onCamError(e) {
uni.showToast({ title: this.detail(e).msg || '相机异常', icon: 'none' });
},
onCamPhoto(e) {
if (!this.taking) return;
this.taking = false;
uni.hideLoading();
const res = this.detail(e);
if (res.path) {
console.log('照片路径:', res.path);
// 这里是沙盒绝对路径,用 image 展示时需加 file:// 前缀
}
},
takePhoto() {
if (!this.camReady || this.taking) return;
this.taking = true;
uni.showLoading({ title: '处理中' });
// 令牌递增触发拍照,结果走 @photo 事件
this.shootToken = this.shootToken + 1;
}
}
};
</script>
<style>
.page { flex: 1; background-color: #000000; }
.cam { position: absolute; left: 0px; top: 0px; }
.overlay { position: absolute; left: 0px; top: 0px; right: 0px; bottom: 0px; }
.band { background-color: rgba(0, 0, 0, 0.62); }
.mid-row { flex-direction: row; }
.frame {
width: 660px;
height: 416px;
border-width: 2px;
border-style: dashed;
border-color: rgba(255, 255, 255, 0.9);
}
.shutter {
position: absolute;
left: 315px;
bottom: 60px;
width: 120px;
height: 120px;
border-radius: 60px;
background-color: #ffffff;
}
</style>
2. 注册页面
pages.json:
{
"path": "pages/camera/camera",
"style": {
"navigationBarTitleText": "拍照",
"app-plus": { "titleNView": false }
}
}
3. 权限配置
Android:manifest.json 的 App 权限里勾选「相机(CAMERA)」。
组件内部会自动发起运行时权限申请,无需手动调用。
iOS:插件已内置 NSCameraUsageDescription,无需额外配置。
API
Props
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
facing |
String | back |
摄像头方向:back / front |
flash |
String | off |
闪光灯:on 为常亮(仅后置有效) |
shoot |
Number | 0 |
拍照触发令牌。每次递增触发一次拍照,结果经 photo 事件回传 |
active |
Boolean | true |
页面是否在前台。false 时释放相机,true 时恢复 |
rotate |
Number | 0 |
预览画面额外旋转角度:0 / 90 / 180 / 270。默认 0,由系统处理方向 |
size |
Number | -1 |
预览尺寸索引。-1 自动挑选;0~5 依次为 4096x3072、3280x2464、2880x2160、1920x1440、1440x1080、1280x960 |
⚠️ prop 名称必须使用单单词的全小写形式。nvue 对 UTS 组件不做 kebab-case → camelCase 转换,
size-index这类名字传不进组件(这也是为什么属性叫size而不是sizeIndex)。
Events
| 名称 | 触发时机 | 参数(在 event.detail 中) |
|---|---|---|
ready |
预览会话配置完成 | { msg: 'ready' } |
error |
相机异常 / 权限被拒 / 拍照失败 | { msg: '错误描述' } |
photo |
拍照完成 | { code: 0, path: '文件绝对路径', msg: 'ok' } |
debug |
内部流程日志(排查用) | { msg: '日志内容' } |
注意:nvue 的自定义事件参数统一包在 event.detail 里,取值方式:
onCamPhoto(e) {
const res = (e && e.detail) ? e.detail : e;
console.log(res.path);
}
组件方法(可选)
也支持通过 ref 调用,但不建议:nvue 环境下 this.$refs 有时取不到原生组件,推荐一律使用「props 下发 + 事件上行」。
| 方法 | 说明 |
|---|---|
takePhoto(options, callback) |
拍照,options: { quality: 1-100 } |
startPreview() / stopPreview() |
手动启停预览 |
setFlashMode(mode, callback) |
设置闪光灯 |
switchCamera(pos, callback) |
切换摄像头 |
setPreviewRotate(deg, callback) |
设置预览旋转角度 |
setSizeIndex(index) |
设置预览尺寸索引 |
getStatus(callback) |
读取内部状态(排查用) |
拍照结果说明
- 返回的是沙盒内绝对路径,例如
…/Android/data/<包名>/files/idcard_1730000000000.jpg - 用
<image>展示时建议补file://前缀:'file://' + path - 图片方向会自动旋转为竖屏方向;前置摄像头会做镜像翻转
实现原理
┌─ nvue 页面(UI 层,双端通用)──────────────┐
│ 取景框 / 遮罩 / 按钮,全部用前端绘制 │
├─ UTS 组件(原生层,只负责画面与硬件)──────┤
│ Android:Camera2 + TextureView + getBitmap 抓帧 │
│ iOS: AVCaptureSession + AVCapturePhotoOutput │
└────────────────────────────────────────┘
关键设计:原生层只负责「出画面」和「按快门」,所有 UI 用 nvue 写一份,双端复用。不要把取景框画在原生里,否则要写两遍。
- Android 拍照:用
TextureView.getBitmap()抓取预览帧,避免ImageReader+ 会话重建的复杂度。分辨率等于预览尺寸,证件拍摄足够。 - Android 预览尺寸:优先选 4:3 比例(多数传感器的原生比例,视野更宽),并按设备实际支持列表自动挑选。
- 零第三方依赖:
android.hardware.camera2.*与AVFoundation都是系统框架,因此 Android 端无需自定义基座,标准基座即可热刷新调试。
常见问题(重要,均为实战踩坑)
1. 画面全黑、ready 事件不触发
原因:组件没有拿到尺寸。nvue 中 flex: 1 对 UTS 原生组件无效,尺寸为 0 时无法创建 Surface,相机永远不会启动。
解决:必须显式给尺寸。
<lj-costomer-camera :style="{ width: '750px', height: winH + 'px' }"></lj-costomer-camera>
2. 运行崩溃:WX_RENDER_ERR_TEXTURE_SETBACKGROUND
原因:组件根节点直接返回了 TextureView。weex 会对组件根节点调用 setBackground(),而 TextureView 不允许设置背景 drawable。
解决:插件内部已用 FrameLayout 包裹 TextureView,根节点交给 weex 管理。自定义原生组件时同理,根节点不要用 SurfaceView / TextureView。
3. 崩溃:fireEvent must be called by main thread
原因:相机回调运行在子线程,而 weex 的 $emit 强制要求主线程调用。
解决:插件内部统一通过 Handler(Looper.getMainLooper()) 切回主线程再发事件。自己写 UTS 组件时,任何回调里发事件都要切主线程。
4. this.$refs.xxx 取不到组件,调用方法无反应
原因:nvue 下 $refs 对 UTS 组件不可靠,且调用抛异常时会被静默吞掉(表现为"点了没反应"、loading 卡死)。
解决:改用 props 传参 + 事件回传。需要"触发动作"时用递增令牌模式:
// 页面
this.shootToken = this.shootToken + 1;
// 组件
"shoot": {
handler(newValue: number, oldValue: number) {
if (newValue <= this.lastShootToken) return; // 必须去重
this.lastShootToken = newValue;
capture();
}
}
5. prop 传不进组件
两种原因:
- 名字含大写(如
sizeIndex)—— nvue 不做 kebab 转换,请改用单单词全小写 - 名字与 CSS 属性冲突(如
position)—— 会被样式系统截走,请改名(本插件用facing而非position)
6. 动作被重复执行(拍两张、重启两次)
原因:nvue 的 prop 同步会重复推送同一个值,且首次挂载也会触发 watch(即使 immediate: false)。
解决:必须做令牌去重(见第 4 条),且去重状态要放在能持久存活的对象里(本插件放在原生引擎实例上,不要放模块级变量,watch 处理器里读写不可靠)。
7. Kotlin 编译报错:类型不匹配 / 找不到名称
UTS 的数值类型规则(Android 端):
number编译为 KotlinNumber,与原生接口的Int/Float不匹配,实现接口方法时必须显式声明Int/Float- 循环变量、索引必须显式声明:
for (let i: Int = 0; ...) - 数字字面量不能直接调方法:
500.toLong()会编译失败,需先赋值给变量 - 赋值给
Int字段的局部变量也必须显式声明类型
let i: Int = 0; // 正确
override onError(camera: CameraDevice, error: Int) {} // 正确
const delayMs = 500; // 先赋值
postDelayed(task, delayMs.toLong()); // 再转换
8. 拍照一直卡在"处理中"
原因:photo 事件没有在组件的 emits 里声明,事件被框架丢弃。
解决:UTS 组件只有声明过的事件才能正常发送。
emits: ['ready', 'error', 'photo', 'debug'],
9. 预览视野偏窄 / 看到的范围比系统相机小
原因:部分机型的低分辨率预览走裁切输出,视野比全画幅窄。16:9 尺寸也是 4:3 画幅的上下裁切。
解决:优先使用 4:3 比例,必要时用更大的尺寸(如 2880x2160),或通过 size 属性现场对比。
10. iOS 端运行要求
Windows 环境或无 XCode 的 Mac 必须使用自定义基座(云端打包),Apple 开发者证书 + 真机缺一不可。Android 端因无三方依赖,标准基座即可。
更新日志
1.0.0
- 首版发布
- Android:Camera2 + TextureView 预览、抓帧拍照、前后摄切换、闪光灯常亮、预览尺寸自动挑选
- iOS:AVFoundation 预览与拍照(待真机验证)
- 提供 props / events 双向通信,规避 nvue 下 ref 不可靠问题
反馈
使用中遇到问题,请提供:
- 手机型号 + Android 版本
- HBuilderX 控制台中
[lj-costomer-camera]开头的日志 - 具体现象(黑屏 / 崩溃 / 无响应 / 方向不对)

收藏人数:
下载插件并导入HBuilderX
赞赏(0)
下载 4
赞赏 0
下载 12619144
赞赏 1949
赞赏
京公网安备:11010802035340号