manual_camera_view

一个在 Flutter 页面中嵌入原生相机预览的轻量插件。

当前能力:

  • 原生相机实时预览
  • 点击预览区域进行对焦与测光,并显示对焦框
  • 统一的亮度调节接口 setNormalizedExposure(-1.0 ~ 1.0)
    • Android:直接走原生归一化曝光逻辑
    • iOS:内部换算为曝光补偿档位
  • 设置 JPEG 图片质量 setImageQuality(1~100)
  • 拍照并直接返回 JPEG 二进制 Uint8List

适合场景:

  • Flutter 页面里直接嵌入原生相机画面
  • 使用 Flutter 自定义拍照按钮、曝光滑杆和业务 UI
  • 拍照后自行上传、压缩、保存或展示图片

适用场景

如果你想要的是:

  • Flutter 页面里直接嵌入原生相机画面
  • 用 Flutter 按钮控制拍照
  • 手动调暗或调亮画面
  • 拿到拍照结果后自己上传、展示或处理

这个插件就可以直接用。

安装

在你的 Flutter 项目中引入插件:

dependencies:
  manual_camera_view: ^0.1.0

然后执行:

flutter pub get

如果你是在插件仓库本地联调,可以把 example/pubspec.yaml 里的路径依赖保留为 path: ../。

权限配置

Android

在 AndroidManifest.xml 中添加相机权限:

<uses-permission android:name="android.permission.CAMERA" />

iOS

在 Info.plist 中添加相机权限说明:

<key>NSCameraUsageDescription</key>
<string>需要使用相机进行拍照</string>

基本用法

先导入:

import 'package:manual_camera_view/manual_camera.dart';

先创建一个 ManualCameraController,再传给 ManualCameraView:

final ManualCameraController _controller = ManualCameraController();

ManualCameraView(
  controller: _controller,
  onViewCreated: () {
    debugPrint('camera view ready');
  },
)

说明:

  • controller 需要由 Flutter 层自己持有
  • onViewCreated 触发后,控制器才算完成与原生视图绑定
  • 在视图 ready 之前调用拍照、曝光等方法,会抛出 StateError

调节亮度

使用一个滑杆即可:

Slider(
  min: -1.0,
  max: 1.0,
  value: _exposure,
  onChanged: (value) {
    setState(() => _exposure = value);
    _controller.setNormalizedExposure(value);
  },
)

说明:

  • -1.0:更暗
  • 0.0:默认曝光
  • 1.0:更亮
  • Flutter 层无需区分 Android 和 iOS,统一调用这一套 API

点击对焦

ManualCameraView 已内置点击对焦能力:

  • 点击预览画面后,会自动把触点换算成 0~1 的归一化坐标
  • 原生侧会按该坐标执行 AF/AE 对焦与测光
  • Flutter 层会短暂显示一个对焦框

如果你有自定义交互需求,也可以直接调用:

await _controller.setFocusPoint(0.5, 0.5);

拍照

拍照前可以按需设置图片质量:

await _controller.setImageQuality(90);

然后调用:

final bytes = await _controller.takePicture();

返回值就是 JPEG 二进制数据 Uint8List,你可以直接用于:

  • 上传服务器
  • 写入本地文件
  • 再次转存到相册
  • 页面预览显示

示例:

final bytes = await _controller.takePicture();
debugPrint('image bytes: ${bytes.length}');

完整示例

下面是一份和当前插件 API 对应的最小示例:

import 'package:manual_camera_view/manual_camera.dart';
import 'package:flutter/material.dart';

class CameraPage extends StatefulWidget {
  const CameraPage({super.key});

  @override
  State<CameraPage> createState() => _CameraPageState();
}

class _CameraPageState extends State<CameraPage> {
  final ManualCameraController _controller = ManualCameraController();
  double _exposure = 0.0;
  bool _isCapturing = false;

  Future<void> _takePicture() async {
    if (!_controller.isReady || _isCapturing) return;
    setState(() => _isCapturing = true);
    try {
      await _controller.setImageQuality(90);
      final bytes = await _controller.takePicture();
      debugPrint('captured: ${bytes.length}');
    } finally {
      if (mounted) setState(() => _isCapturing = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: [
          ManualCameraView(controller: _controller),
          Positioned(
            left: 16,
            right: 16,
            bottom: 120,
            child: Slider(
              min: -1.0,
              max: 1.0,
              value: _exposure,
              onChanged: (value) {
                setState(() => _exposure = value);
                _controller.setNormalizedExposure(value);
              },
            ),
          ),
          Align(
            alignment: Alignment.bottomCenter,
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: ElevatedButton(
                onPressed: _isCapturing ? null : _takePicture,
                child: const Text('拍照'),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

当前对外可用能力

主要对外 API:

  • ManualCameraController.isReady
  • ManualCameraController.setNormalizedExposure()
  • ManualCameraController.setImageQuality()
  • ManualCameraController.setFocusPoint()
  • ManualCameraController.takePicture()
  • ManualCameraView

说明

  • Android 和 iOS 的底层实现不同,但 Flutter 使用方式保持统一
  • 当前 takePicture() 返回的是内存中的 JPEG 字节,不直接返回文件路径
  • 是否保存到系统相册,由你的业务层自行决定
  • 如果想看更完整的页面接入方式,可以参考 example/lib/main.dart