更新记录

1.1(2026-01-21) 下载此版本

v1.1 增加调用原生插件的 getCameraList 方法获取摄像头列表,nvue相机区域可根据id显示不同的摄像头区域

1.0(2025-09-08) 下载此版本

自定义安卓端相机插件,再Nvue中使用,可配合subNvue实现再vue中自定义区域显示相机区域。


平台兼容性

uni-app(4.07)

Vue2 Vue3 Chrome Safari app-vue app-nvue Android iOS 鸿蒙
- - 6.0 - -
微信小程序 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 小红书小程序 快应用-华为 快应用-联盟
- - - - - - - - - - - -

其他

多语言 暗黑模式 宽屏模式
× ×

LDEpiiCamera

2025.09.08 更新内容

Android DCamera 是个人自定义 安卓端 nVue、subNvue 页面 开发的原生相机插件。支持相机自动初始化、摄像头默认为前置摄像头(可联系自定义配置)、拍照、录像、缩放控制及横竖屏切换,适配 Android 6.0+ 系统,提供稳定的相机操作体验。

特色和优势

1、支持vue2、vue3

2、支持nvue中使用,也可通过app-vue配合subNvue使用,如需代码示例可联系坐着QQ

注意

manifest.json 中 App常用其他设置 minSdkVersion设置为23

只需要将插件加入到页面,然后云打包或打包自定义基座

(注意:原生插件必须要在nvue的页面,nvue和vue基本功能一直,只是存在一些差异,比如css的用法等)

调用开放的API接口(拍照、开始/停止录像、设置摄像区域缩放值),再插件可自定义宽高相机初始化默认显示适配的分辨率比例,可提供参数进行默认横竖屏预览相机区域。

、核心功能清单

✅ 相机自动初始化,无需手动调用 open/close 方法(如需要手动控制可联系作者调整)

✅ 拍照功能,自动保存至本地相册并返回路径

✅ 视频录制(支持开始 / 停止回调,返回视频路径)

✅ 0-1 范围缩放控制 this.$refs.cameraRef.setZoomValue(0-1);

✅ 横竖屏动态切换(竖屏 portrait / 横屏 landscape)

✅ 完善的状态监听(相机就绪、错误、拍照 / 录像完成)

✅ 优化资源管理,可解决多次进入vue页面(subNvue加载相机组件预览问题)

、基础使用示例

四、基础使用示例(nvue 页面、如需subNvue请自行查看相关文档或者联系作者QQ) 模板结构(Camera.nvue)


<template>
    <view class="main">
        <!-- 原生相机组件:支持动态切换摄像头 -->
        <DCamera 
            :cameraId="0" 
            ref="cameraRef" 
            :style="{width:width + 'px',height:height + 'px'}" 
            class="camera-view"
            :zoom="currentZoom" 
            screenOrientation="landscape"
            @cameraReady="handleCameraReady"
            @cameraError="handleCameraError" 
            @pictureTaken="handlePictureTaken" 
            @recordStart="handleRecordStart"
            @recordEnd="handleRecordEnd" 
        />

        <!-- 功能按钮区域:新增【切换摄像头】按钮 -->
        <!-- <view class="btn-group">
            <button @click="getAllCameraList" class="btn">获取所有摄像头列表</button>
            <button @click="handleSwitchCamera" class="btn" :disabled="!cameraReady || cameraCount <= 1">
                切换摄像头(当前:{{selectedCameraId === 0 ? '后置' : '前置'}})
            </button>
            <button @click="handleTakePhoto" class="btn" :disabled="!cameraReady">拍照</button>
            <button @click="handleToggleRecord" class="btn" :disabled="!cameraReady" :style="isRecording ? {background: '#ff3b30'} : {background: '#007aff'}">
                {{ isRecording ? '停止录制' : '开始录制' }}
            </button>
        </view> -->

        <!-- <view class="flex_col">
            <view class="camera-list" v-if="cameraList.length > 0">
                <text class="title">系统摄像头列表(共 {{cameraCount}} 个)</text>
                <view class="camera-item" v-for="(item, index) in cameraList" :key="index" @click="selectCamera(item.cameraId)">
                    <text>ID:{{item.cameraId}} | 名称:{{item.name}} | 类型:{{item.facing}}</text>
                </view>
            </view>
            <view class="status-info">
                <text>当前选中摄像头ID:{{selectedCameraId}}</text>
                <text>当前摄像头类型:{{selectedCameraId === 0 ? '后置摄像头' : '前置摄像头'}}</text>
                <text>相机状态:{{cameraReady ? '已就绪' : '未就绪'}}</text>
                <text>录制状态:{{isRecording ? '正在录制' : '未录制'}}</text>
                <text>文件路径:{{fileUrl || '无'}}</text>
            </view>
        </view> -->
    </view>
</template>

<script>
    export default {
        data() {
            return {
                height: 365,
                width: 490,
                currentZoom: 0,
                selectedCameraId: 0, // 默认选中第一个摄像头(后置,ID=0)
                cameraCount: 0, // 摄像头总数
                cameraList: [], // 摄像头列表
                cameraReady: false, // 相机就绪状态
                isRecording: false, // 录制状态
                fileUrl: '' // 拍照/录像文件路径
            }
        },
        onLoad() {
            // 建立通信
            uni.$on('updateContainerRect', (data) => {
                const { type } = data;

                if (type === 'init') {
                    this.width = data.width;
                    this.height = data.height;
                    this.cameraReady = true;

                    // this.$nextTick(() => {
                    //  this.getAllCameraList();
                    // });
                } else if (type === 'startRecord') {
                    this.handleToggleRecord()
                } else if (type === 'stopRecord') {
                    this.handleToggleRecord()
                }
            })

        },
        beforeDestroy() {
            uni.$off('updateContainerRect');

            // 页面销毁时停止录制并释放资源
            if (this.isRecording && this.$refs.cameraRef) {
                this.$refs.cameraRef.stopRecording();
            }

            this.cameraReady = false;
            this.isRecording = false;
        },
        methods: {
            // 1. 获取系统所有摄像头列表(先获取列表,才能准确切换)
            getAllCameraList() {
                // 调用原生插件的 getCameraList 方法
                this.$refs.cameraRef.getCameraList((res) => {
                    if (res.success) {
                        console.log("获取摄像头列表成功", res);
                        this.cameraCount = res.cameraCount;
                        this.cameraList = res.cameraList;
                        uni.showToast({
                            title: `共获取 ${res.cameraCount} 个摄像头`,
                            icon: 'success'
                        });
                    } else {
                        console.error("获取摄像头列表失败", res);
                        uni.showToast({
                            title: res.error,
                            icon: 'none'
                        });
                    }
                });
            },
            // 一键切换摄像头
            handleSwitchCamera() {
                // 先判断摄像头总数,若只有1个,无法切换
                if (this.cameraCount <= 1) {
                    uni.showToast({
                        title: '当前设备仅支持1个摄像头',
                        icon: 'none'
                    });
                    return;
                }

                // 切换逻辑:循环切换(0 → 1 → 0 / 支持多摄像头:0→1→2→0)
                let newCameraId = this.selectedCameraId + 1;
                if (newCameraId >= this.cameraCount) {
                    newCameraId = 0; // 超出总数,回到第一个摄像头
                }

                // 赋值新的摄像头ID(原生插件会自动重新初始化相机)
                this.selectedCameraId = newCameraId;
                uni.showToast({
                    title: `已切换到${newCameraId === 0 ? '后置' : '前置'}摄像头`,
                    icon: 'success'
                });

                // 切换后,录制状态自动重置(避免跨摄像头录制异常)
                if (this.isRecording) {
                    this.$refs.cameraRef.stopRecording();
                    this.isRecording = false;
                }
            },
            // 手动选择指定摄像头(原有功能,配合列表使用)
            selectCamera(cameraId) {
                this.selectedCameraId = cameraId;
                uni.showToast({
                    title: `已选中${cameraId}号摄像头`,
                    icon: 'success'
                });

                // 切换后停止录制,避免异常
                if (this.isRecording) {
                    this.$refs.cameraRef.stopRecording();
                    this.isRecording = false;
                }
            },

            // 拍照
            handleTakePhoto() {
                if (!this.cameraReady) {
                    uni.showToast({
                        title: '相机未就绪',
                        icon: 'none'
                    });
                    return;
                }

                this.$refs.cameraRef.takePicture((res) => {
                    if (res.success) {
                        console.log("拍照成功", res);
                        this.fileUrl = res.path;
                        // 预览图片
                        uni.previewImage({
                            urls: [this.fileUrl],
                            current: this.fileUrl
                        });
                    } else {
                        console.error("拍照失败", res);
                        uni.showToast({
                            title: res.error,
                            icon: 'none'
                        });
                    }
                });
            },

            // 5. 切换录制状态(开始/停止,原有功能)
            handleToggleRecord() {
                if (!this.cameraReady) {
                    uni.showToast({
                        title: '摄像头相机相机未就绪',
                        icon: 'none'
                    });
                    return;
                }

                if (this.isRecording) {
                    // 停止录制
                    this.$refs.cameraRef.stopRecording((res) => {
                        if (res.success) {
                            this.fileUrl = res.path;
                        } else {
                            console.error("摄像头相机停止录制失败", res);
                        }
                    });
                } else {
                    // 开始录制
                    this.$refs.cameraRef.startRecording((res) => {
                        if (res.success) {
                            this.fileUrl = res.path;
                        } else {
                            console.error("摄像头相机开始录制失败", res);
                        }
                    });
                }
            },

            // 相机就绪回调(原有功能)
            handleCameraReady(e) {
                console.warn('摄像头相机已就绪', e);
                this.cameraReady = true;
            },

            // 相机错误回调(原有功能)
            handleCameraError(e) {
                console.error("摄像头相机错误", e);
                this.cameraReady = false;
                uni.showToast({
                    title: `摄像头相机异常:${e.detail.error}`,
                    icon: 'none'
                });
            },

            // 拍照完成回调(原有功能)
            handlePictureTaken(e) {
                console.log('摄像头相机拍照完成:', e.detail.path);
                this.fileUrl = e.detail.path;
            },

            // 录制开始回调(同步状态,原有功能)
            handleRecordStart(e) {
                this.isRecording = true;
                this.fileUrl = e.detail.path;
            },

            // 录制停止回调(同步状态,原有功能)
            handleRecordEnd(e) {
                this.isRecording = false;
                this.fileUrl = e.detail.path;
                // 可将视频路径传递给其他页面
                uni.$emit('getVideoFile', {
                    path: e.detail.path,
                    type: 'videoUrl'
                });
            }
        },
    }
</script>

<style lang="scss">
    .main {
        position: absolute;
    }

    .flex_area {
        display: flex;
        flex-direction: row;
        align-items: center;
        justify-content: center;
        flex-wrap: wrap;
        height: 30px;
        padding-bottom: 5px;
    }

    .camera-view {}

    // .main {
    //  position: absolute;
    //  display: flex;
    //  flex-direction: row;
    //  padding: 20px;
    // }

    // .btn-group {
    //  display: flex;
    //  margin-left: 10rpx;
    // }

    // .btn {
    //  width: 160rpx;
    //  height: 60rpx;
    //  line-height: 60rpx;
    //  text-align: center;
    //  color: #fff;
    //  margin-bottom: 10rpx;
    // }

    // .camera-list {
    //  background-color: #fff;
    //  padding: 10rpx;
    //  margin-bottom: 20rpx;
    // }

    // .title {
    //  font-size: 20px;
    //  font-weight: bold;
    //  margin-bottom: 15rpx;
    // }

    // .camera-item {
    //  font-size: 28rpx;
    //  padding: 10rpx 0;
    //  border-bottom: 1rpx solid #f5f5f5;
    // }

    // .status-info {
    //  background-color: #fff;
    //  padding: 20rpx;
    //  border-radius: 10rpx;
    //  display: flex;
    //  flex-direction: column;
    //  font-size: 26rpx;
    //  color: #333;
    // }

    // .camera-view {
    //  margin-bottom: 20rpx;
    //  border-radius: 10rpx;
    //  overflow: hidden;
    // }
</style>

API 请求方法简要说明

插件方法 说明 默认值 是否必须
getCameraList() 获取所有摄像头列表
takePicture() 执行拍照
startRecording() 开始录像
stopRecording() 停止录像
setZoomValue([]) 图片水印配置

API 参数简要说明

属性名 类型 说明 可选值 默认值 cameraId Number/String 相机ID 0(必填,默认为0,最好调试时调用getCameraList获取想要展示摄像头的ID) 0 screenOrientation String 相机横竖屏配置 portrait(竖屏)、landscape(横屏) landscape zoom Number 缩放比例(0-1,0 = 无缩放) 0 ~ 1(步长建议 0.1) 0

  1. getCameraList(callback) 功能:获取所有摄像头列表。(获取所有摄像头列表, 必须再组件DCamera标签内加入参数cameraId,指定选中摄像头ID) 参数: callback(res):回调函数,返回拍照结果 字段 类型 说明 res.success Boolean 是否获取系统摄像头成功 res.cameraCount String 当前系统摄像头数量(成功时返回) res.cameraList Object 摄像头列表(成功时返回) res.error String 错误信息(失败时返回) 示例:

    this.$refs.cameraRef.getCameraList((res) => {
    console.log(res)
    });
  2. takePicture(callback) 功能:拍摄照片并保存至设备存储。 参数: callback(res):回调函数,返回拍照结果 字段 类型 说明 res.success Boolean 是否拍照成功 res.path String 照片绝对路径(成功时返回) res.error String 错误信息(失败时返回) 示例:

    this.$refs.cameraRef.takePicture((res) => {
    console.log(res)
    });
  3. startRecording(callback) 功能:启动视频录制。 参数: callback(res):回调函数,返回录像启动结果 字段 类型 说明 res.success Boolean 是否启动成功 res.path String 视频保存路径(成功时返回) res.error String 错误信息(失败时返回) 示例:

    this.$refs.cameraRef.startRecording((res) => {
    console.log(res)
    });
  4. stopRecording(callback) 功能:停止视频录制并保存文件。 参数: callback(res):回调函数,返回录像停止结果 字段 类型 说明 res.success Boolean 是否停止成功 res.path String 视频绝对路径(成功时返回) res.error String 错误信息(失败时返回) 示例:

    this.$refs.cameraRef.startRecording((res) => {
    console.log(res)
    });
  5. setZoomValue(zoom, callback) 功能:手动调整相机缩放比例(优先级高于 zoom 属性)。 参数: zoom:Number,缩放比例(0-1,0 = 无缩放,1 = 最大缩放) callback(res):回调函数,返回缩放结果 字段 类型 说明 res.success Boolean 是否缩放成功 res.currentZoom Number 当前实际缩放比例 res.error String 错误信息(失败时返回) 示例:

    // 设置缩放为50% currentZoom: 0-1
    this.currentZoom = 0.5;
    this.$refs.cameraRef.setZoomValue(this.currentZoom);

API 回调方法监听简要说明

回调函数 说明 默认值 是否必须
@cameraReady 相机初始化完成并开始预览时 { status: "ready" }
@cameraError 相机初始化/操作失败时 { error: "具体错误信息" }
@pictureTaken 拍照完成时 { path: "照片保存路径" }
@recordStart 录像启动时 { status: "started", path: "视频路径" } 非通过组件绑定 @事件名="处理函数" 监听相机状态变化,事件与方法回调同步触发。
@recordEnd 录像停止并保存完成时 { status: "stopped", path: "视频路径" } 非通过组件绑定 @事件名="处理函数" 监听相机状态变化,事件与方法回调同步触发。

反馈与维护

若遇到插件使用问题或功能需求,可通过以下方式反馈:

***

隐私、权限声明

1. 本插件需要申请的系统权限列表:

<uses-permission android:name="android.permission.CAMERA" /> <uses-feature android:name="android.hardware.camera" /> <uses-feature android:name="android.hardware.camera.autofocus" /> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT" tools:ignore="ProtectedPermissions" />

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件不采集任何数据

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率: