Flutter for OpenHarmony三方库适配实战:camera 相机功能详解
相机功能是移动应用的核心能力之一,广泛应用于拍照、录像、扫码、视频通话等场景。在 Flutter for OpenHarmony 应用开发中, 是官方提供的跨平台相机插件,提供了完整的相机控制能力。camera 库基于 Flutter 平台接口实现,提供了以下核心特性:多相机支持:支持前后摄像头切换、多摄像头设备枚举和选择。实时预览:提供 CameraPreview 组件,支持实时相机画面预览。拍
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
本文基于flutter3.27.5开发

一、camera 库概述
相机功能是移动应用的核心能力之一,广泛应用于拍照、录像、扫码、视频通话等场景。在 Flutter for OpenHarmony 应用开发中,camera 是官方提供的跨平台相机插件,提供了完整的相机控制能力。
camera 库特点
camera 库基于 Flutter 平台接口实现,提供了以下核心特性:
多相机支持:支持前后摄像头切换、多摄像头设备枚举和选择。
实时预览:提供 CameraPreview 组件,支持实时相机画面预览。
拍照录像:支持高质量照片拍摄和视频录制,支持暂停/恢复录制。
图像流处理:支持实时图像帧流输出,可用于扫码、人脸检测等场景。
丰富的控制选项:支持闪光灯、对焦、曝光、缩放等多种相机参数控制。
方向管理:支持设备方向检测和捕获方向锁定。
功能支持对比
| 功能 | Android | iOS | OpenHarmony |
|---|---|---|---|
| 相机枚举 | ✅ | ✅ | ✅ |
| 实时预览 | ✅ | ✅ | ✅ |
| 拍照 | ✅ | ✅ | ✅ |
| 视频录制 | ✅ | ✅ | ✅ |
| 闪光灯控制 | ✅ | ✅ | ✅ |
| 对焦控制 | ✅ | ✅ | ✅ |
| 曝光控制 | ✅ | ✅ | ✅ |
| 缩放控制 | ✅ | ✅ | ✅ |
| 图像流 | ✅ | ✅ | ✅ |
| 前后摄像头切换 | ✅ | ✅ | ✅ |
使用场景:拍照应用、视频录制、二维码扫描、人脸识别、AR应用、视频通话等。
二、安装与配置
2.1 添加依赖
在项目的 pubspec.yaml 文件中添加 camera 依赖:
dependencies:
camera:
git:
url: https://atomgit.com/openharmony-tpc/flutter_packages.git
path: packages/camera/camera
然后执行以下命令获取依赖:
flutter pub get
2.2 权限配置
在 OpenHarmony 项目中需要配置相机和麦克风权限。在 ohos/entry/src/main/module.json5 中添加:
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.CAMERA",
"reason": "$string:camera_reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
},
{
"name": "ohos.permission.MICROPHONE",
"reason": "$string:microphone_reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
},
{
"name": "ohos.permission.WRITE_MEDIA",
"reason": "$string:media_reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
}
]
}
}
在 ohos/entry/src/main/resources/base/element/string.json 中添加:
{
"string": [
{
"name": "camera_reason",
"value": "用于拍照和录像功能"
},
{
"name": "microphone_reason",
"value": "用于视频录制时的音频采集"
},
{
"name": "media_reason",
"value": "用于保存照片和视频"
}
]
}
三、核心 API 详解
3.1 CameraDescription 类
CameraDescription 描述相机设备的属性。
class CameraDescription {
final String name; // 相机唯一标识符
final CameraLensDirection lensDirection; // 镜头方向
final int sensorOrientation; // 传感器方向角度
const CameraDescription({
required this.name,
required this.lensDirection,
required this.sensorOrientation,
});
}
3.2 CameraLensDirection 枚举
CameraLensDirection 定义镜头方向。
enum CameraLensDirection {
front, // 前置摄像头
back, // 后置摄像头
external, // 外部摄像头
}
3.3 availableCameras 方法
availableCameras 方法获取设备上可用的相机列表。
Future<List<CameraDescription>> availableCameras()
使用示例:
final cameras = await availableCameras();
for (final camera in cameras) {
print('相机: ${camera.name}, 方向: ${camera.lensDirection}');
}
3.4 CameraController 类
CameraController 是相机控制的核心类,管理相机的所有操作。
class CameraController extends ValueNotifier<CameraValue> {
CameraController(
CameraDescription description,
ResolutionPreset resolutionPreset, {
bool enableAudio = true,
ImageFormatGroup? imageFormatGroup,
});
Future<void> initialize();
Future<XFile> takePicture();
Future<void> startVideoRecording();
Future<XFile> stopVideoRecording();
Future<void> setFlashMode(FlashMode mode);
Future<void> setFocusMode(FocusMode mode);
Future<void> setZoomLevel(double zoom);
Future<void> dispose();
}
3.5 initialize 方法
initialize 方法初始化相机,必须在其他操作之前调用。
Future<void> initialize()
使用示例:
final controller = CameraController(
cameras.first,
ResolutionPreset.high,
);
await controller.initialize();
3.6 takePicture 方法
takePicture 方法拍摄照片并返回保存的文件。
Future<XFile> takePicture()
使用示例:
if (controller.value.isInitialized) {
final image = await controller.takePicture();
print('照片保存路径: ${image.path}');
}
3.7 startVideoRecording 方法
startVideoRecording 方法开始录制视频。
Future<void> startVideoRecording()
使用示例:
await controller.startVideoRecording();
3.8 stopVideoRecording 方法
stopVideoRecording 方法停止录制并返回视频文件。
Future<XFile> stopVideoRecording()
使用示例:
final video = await controller.stopVideoRecording();
print('视频保存路径: ${video.path}');
3.9 ResolutionPreset 枚举
ResolutionPreset 定义预览分辨率预设。
enum ResolutionPreset {
low, // 240p (320x240)
medium, // 480p (720x480)
high, // 720p (1280x720)
veryHigh, // 1080p (1920x1080)
max, // 最高分辨率
}
3.10 FlashMode 枚举
FlashMode 定义闪光灯模式。
enum FlashMode {
off, // 关闭
auto, // 自动
always, // 常开
torch, // 手电筒模式
}
3.11 FocusMode 枚举
FocusMode 定义对焦模式。
enum FocusMode {
auto, // 自动对焦
locked, // 锁定对焦
}
3.12 ExposureMode 枚举
ExposureMode 定义曝光模式。
enum ExposureMode {
auto, // 自动曝光
locked, // 锁定曝光
}
四、Widget 组件详解
4.1 CameraPreview 组件
CameraPreview 是相机预览组件,用于显示实时相机画面。
class CameraPreview extends StatelessWidget {
const CameraPreview(
this.controller, {
super.key,
this.child,
});
final CameraController controller;
final Widget? child;
}
使用示例:
if (controller.value.isInitialized) {
return CameraPreview(controller);
}
4.2 CameraValue 类
CameraValue 包含相机的当前状态信息。
class CameraValue {
final bool isInitialized; // 是否已初始化
final bool isRecordingVideo; // 是否正在录制视频
final bool isTakingPicture; // 是否正在拍照
final bool isStreamingImages; // 是否正在流式传输图像
final Size? previewSize; // 预览尺寸
final FlashMode flashMode; // 闪光灯模式
final ExposureMode exposureMode; // 曝光模式
final FocusMode focusMode; // 对焦模式
final DeviceOrientation deviceOrientation; // 设备方向
}
4.3 CameraImage 类
CameraImage 表示从相机获取的图像帧数据。
class CameraImage {
final List<Plane> planes; // 图像平面数据
final int width; // 图像宽度
final int height; // 图像高度
final ImageFormat format; // 图像格式
}
4.4 startImageStream 方法
startImageStream 方法启动图像流,用于实时处理相机帧。
Future<void> startImageStream(onLatestImageAvailable onAvailable)
使用示例:
controller.startImageStream((CameraImage image) {
// 处理每一帧图像
print('收到图像: ${image.width}x${image.height}');
});
五、OpenHarmony 平台实现原理
5.1 原生 API 映射
camera 在 OpenHarmony 平台上使用 @kit.CameraKit 和 @kit.MediaKit 模块实现:
| Flutter API | OpenHarmony API |
|---|---|
| availableCameras | cameraManager.getSupportedCameras |
| initialize | cameraInput.open + session.start |
| takePicture | photoOutput.capture |
| startVideoRecording | videoOutput.start + avRecorder |
| setFlashMode | session.setFlashMode |
| setFocusMode | session.setFocusMode |
| setZoomLevel | session.setZoomRatio |
| setExposureMode | session.setExposureMode |
5.2 相机枚举实现
OpenHarmony 使用 cameraManager 获取可用相机列表:
getAvailableCameras(context: Context): HashMap<String, Object>[] {
let cameraManager: camera.CameraManager = this.getCameraManager(context);
let cameras: Array<camera.CameraDevice> = cameraManager.getSupportedCameras();
let reply: HashMap<String, Object>[] = [];
for (let cameraDevice of cameras) {
let map: HashMap<String, Object> = new HashMap();
map.set('name', cameraDevice.cameraId);
map.set('lensFacing', this.getLensFacing(cameraDevice));
map.set('sensorOrientation', this.getSensorOrientation(cameraDevice));
reply.push(map);
}
return reply;
}
5.3 相机初始化实现
相机初始化涉及创建 CameraInput 和 Session:
async open(imageFormatGroup: string | null): Promise<void> {
this.cameraDevice = CameraUtils.getCameraDevice(
this.cameraProperties.getCameraName(),
this.cameraManager
);
this.cameraInput = this.cameraManager.createCameraInput(this.cameraDevice);
await this.cameraInput.open();
this.startPreview();
this.dartMessenger.sendCameraInitializedEvent(
resolutionFeature.getPreviewSize().width,
resolutionFeature.getPreviewSize().height,
exposureMode,
focusMode,
exposurePointSupported,
focusPointSupported
);
}
5.4 预览实现
预览使用双路输出实现 Texture 渲染:
private previewOutput: camera.PreviewOutput | null = null;
private previewOutput2: camera.PreviewOutput | null = null;
startPreview(): void {
this.photoSession = this.cameraManager.createSession('photo');
this.photoSession.beginConfig();
this.photoSession.addInput(this.cameraInput);
this.photoSession.addOutput(this.previewOutput);
this.photoSession.addOutput(this.previewOutput2);
this.photoSession.commitConfig();
this.photoSession.start();
}
5.5 拍照实现
拍照使用 PhotoOutput 捕获图像:
async takePicture(result: MethodResult) {
this.runPictureAutoFocus();
let photoCaptureSetting: camera.PhotoCaptureSetting = {
quality: camera.QualityLevel.QUALITY_LEVEL_HIGH,
rotation: this.photoRotation
};
this.photoOutPut.capture(photoCaptureSetting, async (err, photo) => {
if (photo) {
let file = await this.savePhoto(photo);
result.success(file);
}
});
}
5.6 视频录制实现
视频录制使用 AVRecorder 和 VideoOutput:
async startVideoRecording(result: MethodResult, enableStream: boolean) {
this.videoSession = this.cameraManager.createSession('video');
this.videoSession.beginConfig();
this.videoSession.addInput(this.cameraInput);
this.videoSession.addOutput(this.videoOutput);
this.videoSession.commitConfig();
this.videoSession.start();
this.avRecorder = media.createAVRecorder();
await this.avRecorder.prepare(this.getAVRecorderConfig());
await this.avRecorder.start();
this.recordingVideo = true;
result.success(null);
}
5.7 闪光灯控制实现
setFlashMode(result: MethodResult, newMode: camera.FlashMode) {
if (!this.getCurSession().hasFlash()) {
result.error("setFlashModeFailed", "The camera device does not have flash.", null);
return;
}
if (!this.getCurSession().isFlashModeSupported(newMode)) {
result.error("setFlashModeFailed", "The flash mode is unsupported.", null);
return;
}
this.getCurSession().setFlashMode(newMode);
result.success(null);
}
5.8 缩放控制实现
setZoomLevel(result: MethodResult, zoom: number): void {
let maxZoom = this.cameraFeatures.getZoomLevel().getMaximumZoomLevel(this.getCurSession());
let minZoom = this.cameraFeatures.getZoomLevel().getMinimumZoomLevel(this.getCurSession());
if (zoom > maxZoom || zoom < minZoom) {
result.error("ZOOM_ERROR", "Zoom level out of bounds", null);
return;
}
this.getCurSession().setZoomRatio(zoom);
result.success(null);
}
六、MethodChannel 通信协议
6.1 方法列表
| 方法 | 参数 | 返回值 | 说明 |
|---|---|---|---|
availableCameras |
- | List <Camera> |
获取可用相机 |
create |
cameraName, resolutionPreset | cameraId | 创建相机实例 |
initialize |
cameraId, imageFormatGroup | - | 初始化相机 |
takePicture |
cameraId | XFile | 拍照 |
startVideoRecording |
cameraId, enableStream | - | 开始录制 |
stopVideoRecording |
cameraId | XFile | 停止录制 |
setFlashMode |
cameraId, mode | - | 设置闪光灯 |
setFocusMode |
cameraId, mode | - | 设置对焦模式 |
setFocusPoint |
cameraId, x, y | - | 设置对焦点 |
setExposureMode |
cameraId, mode | - | 设置曝光模式 |
setExposurePoint |
cameraId, x, y | - | 设置曝光点 |
setExposureOffset |
cameraId, offset | - | 设置曝光补偿 |
setZoomLevel |
cameraId, zoom | - | 设置缩放级别 |
getMaxZoomLevel |
cameraId | maxZoom | 获取最大缩放 |
getMinZoomLevel |
cameraId | minZoom | 获取最小缩放 |
pausePreview |
cameraId | - | 暂停预览 |
resumePreview |
cameraId | - | 恢复预览 |
startImageStream |
cameraId | - | 开始图像流 |
stopImageStream |
cameraId | - | 停止图像流 |
dispose |
cameraId | - | 释放资源 |
6.2 相机信息返回格式
{
"name": "0",
"lensFacing": "back",
"sensorOrientation": 90,
}
6.3 事件流
| 事件 | 说明 |
|---|---|
cameraInitialized |
相机初始化完成 |
cameraError |
相机发生错误 |
cameraClosing |
相机正在关闭 |
videoRecorded |
视频录制完成 |
deviceOrientationChanged |
设备方向改变 |
七、实战案例
7.1 基础相机预览
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
class BasicCameraPage extends StatefulWidget {
const BasicCameraPage({super.key});
State<BasicCameraPage> createState() => _BasicCameraPageState();
}
class _BasicCameraPageState extends State<BasicCameraPage> {
CameraController? _controller;
List<CameraDescription>? _cameras;
void initState() {
super.initState();
_initCamera();
}
Future<void> _initCamera() async {
_cameras = await availableCameras();
if (_cameras!.isNotEmpty) {
_controller = CameraController(
_cameras!.first,
ResolutionPreset.high,
);
await _controller!.initialize();
setState(() {});
}
}
void dispose() {
_controller?.dispose();
super.dispose();
}
Widget build(BuildContext context) {
if (_controller == null || !_controller!.value.isInitialized) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
return Scaffold(
appBar: AppBar(title: const Text('相机预览')),
body: CameraPreview(_controller!),
);
}
}
7.2 拍照功能
class TakePicturePage extends StatefulWidget {
const TakePicturePage({super.key});
State<TakePicturePage> createState() => _TakePicturePageState();
}
class _TakePicturePageState extends State<TakePicturePage> {
CameraController? _controller;
bool _isTakingPicture = false;
void initState() {
super.initState();
_initCamera();
}
Future<void> _initCamera() async {
final cameras = await availableCameras();
if (cameras.isNotEmpty) {
_controller = CameraController(
cameras.first,
ResolutionPreset.veryHigh,
);
await _controller!.initialize();
setState(() {});
}
}
Future<void> _takePicture() async {
if (_controller == null || !_controller!.value.isInitialized) return;
if (_isTakingPicture) return;
setState(() => _isTakingPicture = true);
try {
final image = await _controller!.takePicture();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('照片已保存: ${image.path}')),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('拍照失败: $e')),
);
} finally {
setState(() => _isTakingPicture = false);
}
}
void dispose() {
_controller?.dispose();
super.dispose();
}
Widget build(BuildContext context) {
if (_controller == null || !_controller!.value.isInitialized) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
return Scaffold(
body: CameraPreview(_controller!),
floatingActionButton: FloatingActionButton(
onPressed: _isTakingPicture ? null : _takePicture,
child: const Icon(Icons.camera_alt),
),
);
}
}
7.3 视频录制
class VideoRecordingPage extends StatefulWidget {
const VideoRecordingPage({super.key});
State<VideoRecordingPage> createState() => _VideoRecordingPageState();
}
class _VideoRecordingPageState extends State<VideoRecordingPage> {
CameraController? _controller;
void initState() {
super.initState();
_initCamera();
}
Future<void> _initCamera() async {
final cameras = await availableCameras();
if (cameras.isNotEmpty) {
_controller = CameraController(
cameras.first,
ResolutionPreset.high,
enableAudio: true,
);
await _controller!.initialize();
setState(() {});
}
}
Future<void> _toggleRecording() async {
if (_controller == null) return;
if (_controller!.value.isRecordingVideo) {
final video = await _controller!.stopVideoRecording();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('视频已保存: ${video.path}')),
);
} else {
await _controller!.startVideoRecording();
}
setState(() {});
}
void dispose() {
_controller?.dispose();
super.dispose();
}
Widget build(BuildContext context) {
if (_controller == null || !_controller!.value.isInitialized) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
return Scaffold(
body: CameraPreview(_controller!),
floatingActionButton: FloatingActionButton(
onPressed: _toggleRecording,
backgroundColor: _controller!.value.isRecordingVideo
? Colors.red
: Colors.blue,
child: Icon(
_controller!.value.isRecordingVideo
? Icons.stop
: Icons.videocam,
),
),
);
}
}
7.4 前后摄像头切换
class CameraSwitchPage extends StatefulWidget {
const CameraSwitchPage({super.key});
State<CameraSwitchPage> createState() => _CameraSwitchPageState();
}
class _CameraSwitchPageState extends State<CameraSwitchPage> {
List<CameraDescription>? _cameras;
CameraController? _controller;
int _cameraIndex = 0;
void initState() {
super.initState();
_initCamera();
}
Future<void> _initCamera() async {
_cameras = await availableCameras();
if (_cameras!.isNotEmpty) {
await _setupCamera(_cameras![_cameraIndex]);
}
}
Future<void> _setupCamera(CameraDescription description) async {
_controller?.dispose();
_controller = CameraController(
description,
ResolutionPreset.high,
);
await _controller!.initialize();
setState(() {});
}
void _switchCamera() {
if (_cameras == null || _cameras!.length < 2) return;
_cameraIndex = (_cameraIndex + 1) % _cameras!.length;
_setupCamera(_cameras![_cameraIndex]);
}
void dispose() {
_controller?.dispose();
super.dispose();
}
Widget build(BuildContext context) {
if (_controller == null || !_controller!.value.isInitialized) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
return Scaffold(
body: CameraPreview(_controller!),
floatingActionButton: FloatingActionButton(
onPressed: _switchCamera,
child: const Icon(Icons.flip_camera_ios),
),
);
}
}
7.5 闪光灯控制
class FlashControlPage extends StatefulWidget {
const FlashControlPage({super.key});
State<FlashControlPage> createState() => _FlashControlPageState();
}
class _FlashControlPageState extends State<FlashControlPage> {
CameraController? _controller;
FlashMode _flashMode = FlashMode.auto;
void initState() {
super.initState();
_initCamera();
}
Future<void> _initCamera() async {
final cameras = await availableCameras();
if (cameras.isNotEmpty) {
_controller = CameraController(
cameras.first,
ResolutionPreset.high,
);
await _controller!.initialize();
setState(() {});
}
}
Future<void> _cycleFlashMode() async {
if (_controller == null) return;
final modes = [FlashMode.off, FlashMode.auto, FlashMode.always, FlashMode.torch];
final currentIndex = modes.indexOf(_flashMode);
final nextIndex = (currentIndex + 1) % modes.length;
_flashMode = modes[nextIndex];
await _controller!.setFlashMode(_flashMode);
setState(() {});
}
IconData _getFlashIcon() {
switch (_flashMode) {
case FlashMode.off:
return Icons.flash_off;
case FlashMode.auto:
return Icons.flash_auto;
case FlashMode.always:
return Icons.flash_on;
case FlashMode.torch:
return Icons.flashlight_on;
}
}
void dispose() {
_controller?.dispose();
super.dispose();
}
Widget build(BuildContext context) {
if (_controller == null || !_controller!.value.isInitialized) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
return Scaffold(
body: Stack(
children: [
CameraPreview(_controller!),
Positioned(
top: 40,
right: 20,
child: FloatingActionButton(
mini: true,
onPressed: _cycleFlashMode,
child: Icon(_getFlashIcon()),
),
),
],
),
);
}
}
7.6 缩放控制
class ZoomControlPage extends StatefulWidget {
const ZoomControlPage({super.key});
State<ZoomControlPage> createState() => _ZoomControlPageState();
}
class _ZoomControlPageState extends State<ZoomControlPage> {
CameraController? _controller;
double _minZoom = 1.0;
double _maxZoom = 1.0;
double _currentZoom = 1.0;
void initState() {
super.initState();
_initCamera();
}
Future<void> _initCamera() async {
final cameras = await availableCameras();
if (cameras.isNotEmpty) {
_controller = CameraController(
cameras.first,
ResolutionPreset.high,
);
await _controller!.initialize();
_minZoom = await _controller!.getMinZoomLevel();
_maxZoom = await _controller!.getMaxZoomLevel();
setState(() {});
}
}
Future<void> _setZoom(double zoom) async {
if (_controller == null) return;
await _controller!.setZoomLevel(zoom);
setState(() => _currentZoom = zoom);
}
void dispose() {
_controller?.dispose();
super.dispose();
}
Widget build(BuildContext context) {
if (_controller == null || !_controller!.value.isInitialized) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
return Scaffold(
body: Stack(
children: [
CameraPreview(_controller!),
Positioned(
bottom: 40,
left: 20,
right: 20,
child: Column(
children: [
Text(
'缩放: ${_currentZoom.toStringAsFixed(1)}x',
style: const TextStyle(
color: Colors.white,
fontSize: 16,
shadows: [Shadow(blurRadius: 4)],
),
),
Slider(
value: _currentZoom,
min: _minZoom,
max: _maxZoom,
onChanged: _setZoom,
),
],
),
),
],
),
);
}
}
7.7 图像流处理
class ImageStreamPage extends StatefulWidget {
const ImageStreamPage({super.key});
State<ImageStreamPage> createState() => _ImageStreamPageState();
}
class _ImageStreamPageState extends State<ImageStreamPage> {
CameraController? _controller;
bool _isStreaming = false;
int _frameCount = 0;
void initState() {
super.initState();
_initCamera();
}
Future<void> _initCamera() async {
final cameras = await availableCameras();
if (cameras.isNotEmpty) {
_controller = CameraController(
cameras.first,
ResolutionPreset.medium,
imageFormatGroup: ImageFormatGroup.yuv420,
);
await _controller!.initialize();
setState(() {});
}
}
void _toggleImageStream() {
if (_controller == null) return;
if (_isStreaming) {
_controller!.stopImageStream();
setState(() => _isStreaming = false);
} else {
_controller!.startImageStream((CameraImage image) {
setState(() => _frameCount++);
// 这里可以进行图像处理,如扫码、人脸检测等
});
setState(() {
_isStreaming = true;
_frameCount = 0;
});
}
}
void dispose() {
_controller?.dispose();
super.dispose();
}
Widget build(BuildContext context) {
if (_controller == null || !_controller!.value.isInitialized) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
return Scaffold(
body: Stack(
children: [
CameraPreview(_controller!),
Positioned(
bottom: 40,
left: 0,
right: 0,
child: Column(
children: [
Text(
'帧数: $_frameCount',
style: const TextStyle(
color: Colors.white,
fontSize: 20,
shadows: [Shadow(blurRadius: 4)],
),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: _toggleImageStream,
child: Text(_isStreaming ? '停止图像流' : '开始图像流'),
),
],
),
),
],
),
);
}
}
八、最佳实践
8.1 相机生命周期管理
正确管理相机生命周期,避免内存泄漏:
class CameraLifecyclePage extends StatefulWidget {
const CameraLifecyclePage({super.key});
State<CameraLifecyclePage> createState() => _CameraLifecyclePageState();
}
class _CameraLifecyclePageState extends State<CameraLifecyclePage>
with WidgetsBindingObserver {
CameraController? _controller;
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
_initCamera();
}
void didChangeAppLifecycleState(AppLifecycleState state) {
if (_controller == null || !_controller!.value.isInitialized) return;
if (state == AppLifecycleState.inactive) {
_controller?.dispose();
_controller = null;
} else if (state == AppLifecycleState.resumed) {
_initCamera();
}
}
Future<void> _initCamera() async {
final cameras = await availableCameras();
if (cameras.isNotEmpty) {
_controller = CameraController(cameras.first, ResolutionPreset.high);
await _controller!.initialize();
setState(() {});
}
}
void dispose() {
WidgetsBinding.instance.removeObserver(this);
_controller?.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
body: _controller?.value.isInitialized == true
? CameraPreview(_controller!)
: const Center(child: CircularProgressIndicator()),
);
}
}
8.2 错误处理
完善的错误处理机制:
Future<void> _initCamera() async {
try {
final cameras = await availableCameras();
if (cameras.isEmpty) {
_showError('未找到可用相机');
return;
}
_controller = CameraController(
cameras.first,
ResolutionPreset.high,
);
await _controller!.initialize();
if (mounted) {
setState(() {});
}
} on CameraException catch (e) {
_showError('相机错误: ${e.description}');
} catch (e) {
_showError('初始化失败: $e');
}
}
void _showError(String message) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: Colors.red,
),
);
}
}
8.3 权限处理
在初始化相机前检查权限:
Future<bool> _checkPermissions() async {
final cameraStatus = await Permission.camera.status;
final microphoneStatus = await Permission.microphone.status;
if (!cameraStatus.isGranted) {
final result = await Permission.camera.request();
if (!result.isGranted) {
_showError('需要相机权限');
return false;
}
}
if (!microphoneStatus.isGranted) {
final result = await Permission.microphone.request();
if (!result.isGranted) {
_showError('需要麦克风权限');
return false;
}
}
return true;
}
8.4 性能优化
使用合适的分辨率预设:
// 根据使用场景选择分辨率
ResolutionPreset _getResolutionForUseCase(UseCase useCase) {
switch (useCase) {
case UseCase.preview:
return ResolutionPreset.medium; // 预览使用中等分辨率
case UseCase.photo:
return ResolutionPreset.veryHigh; // 拍照使用高分辨率
case UseCase.video:
return ResolutionPreset.high; // 视频使用高清分辨率
case UseCase.stream:
return ResolutionPreset.low; // 图像流使用低分辨率
}
}
8.5 资源释放
确保正确释放资源:
void dispose() {
// 停止所有操作
if (_controller?.value.isRecordingVideo == true) {
_controller?.stopVideoRecording();
}
if (_controller?.value.isStreamingImages == true) {
_controller?.stopImageStream();
}
// 释放控制器
_controller?.dispose();
_controller = null;
super.dispose();
}
九、常见问题
Q1: 相机初始化失败怎么办?
A: 检查以下几点:
- 确保已在 module.json5 中配置相机权限
- 确保应用已获得相机权限授权
- 检查是否有其他应用正在占用相机
- 尝试重新启动应用
try {
await controller.initialize();
} on CameraException catch (e) {
if (e.code == 'CameraAccessDenied') {
// 引导用户去设置中开启权限
} else if (e.code == 'CameraDeviceInUse') {
// 相机被占用,稍后重试
}
}
Q2: 预览画面方向不正确?
A: 使用 sensorOrientation 调整预览方向:
final camera = cameras.first;
final orientation = camera.sensorOrientation;
// 根据传感器方向设置预览旋转
if (Platform.isAndroid || Platform.isOHOS) {
// OpenHarmony 和 Android 需要考虑传感器方向
}
Q3: 拍照后照片方向错误?
A: 使用 EXIF 信息或锁定捕获方向:
// 锁定捕获方向
await controller.lockCaptureOrientation(DeviceOrientation.portraitUp);
// 拍照
final image = await controller.takePicture();
// 解锁捕获方向
await controller.unlockCaptureOrientation();
Q4: 视频录制没有声音?
A: 确保初始化时启用了音频:
final controller = CameraController(
camera,
ResolutionPreset.high,
enableAudio: true, // 启用音频
);
Q5: 图像流帧率过低?
A: 降低分辨率预设:
// 使用较低分辨率提高帧率
final controller = CameraController(
camera,
ResolutionPreset.low, // 或 ResolutionPreset.medium
imageFormatGroup: ImageFormatGroup.yuv420,
);
Q6: 如何实现连续对焦?
A: 设置自动对焦模式:
await controller.setFocusMode(FocusMode.auto);
await controller.setFocusPoint(null); // 重置对焦点,启用连续对焦
十、总结
camera 库为 Flutter for OpenHarmony 提供了完整的相机功能支持,包括:
核心能力:
- 多相机设备枚举和选择
- 实时相机预览
- 高质量照片拍摄
- 视频录制(支持暂停/恢复)
- 图像流实时处理
控制功能:
- 闪光灯模式控制
- 对焦模式和区域控制
- 曝光模式和补偿控制
- 缩放级别控制
- 捕获方向锁定
OpenHarmony 特性:
- 基于 @kit.CameraKit 原生实现
- 支持 Texture 高效渲染
- 完整的权限管理
- 双路预览输出
通过 camera 库,开发者可以快速构建功能丰富的相机应用,满足拍照、录像、扫码等多种场景需求。
十一、完整代码示例

import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const CameraApp());
}
class CameraApp extends StatelessWidget {
const CameraApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'Camera Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
useMaterial3: true,
),
home: const CameraHomePage(),
);
}
}
class CameraHomePage extends StatefulWidget {
const CameraHomePage({super.key});
State<CameraHomePage> createState() => _CameraHomePageState();
}
class _CameraHomePageState extends State<CameraHomePage>
with WidgetsBindingObserver {
CameraController? _controller;
List<CameraDescription>? _cameras;
int _cameraIndex = 0;
FlashMode _flashMode = FlashMode.auto;
double _currentZoom = 1.0;
double _minZoom = 1.0;
double _maxZoom = 1.0;
String _statusMessage = '';
bool _isInitialized = false;
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
_initCamera();
}
void didChangeAppLifecycleState(AppLifecycleState state) {
if (_controller == null || !_controller!.value.isInitialized) return;
if (state == AppLifecycleState.inactive) {
_controller?.dispose();
setState(() {
_controller = null;
_isInitialized = false;
});
} else if (state == AppLifecycleState.resumed) {
_initCamera();
}
}
Future<void> _initCamera() async {
try {
_cameras = await availableCameras();
if (_cameras!.isEmpty) {
setState(() => _statusMessage = '未找到可用相机');
return;
}
await _setupCamera(_cameras![_cameraIndex]);
} catch (e) {
setState(() => _statusMessage = '初始化失败: $e');
}
}
Future<void> _setupCamera(CameraDescription description) async {
_controller?.dispose();
_controller = CameraController(
description,
ResolutionPreset.high,
enableAudio: true,
);
await _controller!.initialize();
final minZoom = await _controller!.getMinZoomLevel();
final maxZoom = await _controller!.getMaxZoomLevel();
setState(() {
_minZoom = minZoom <= 0 ? 1.0 : minZoom;
_maxZoom = maxZoom <= _minZoom ? _minZoom + 3.0 : maxZoom;
_currentZoom = _minZoom;
_isInitialized = true;
});
}
void _switchCamera() {
if (_cameras == null || _cameras!.length < 2) return;
_cameraIndex = (_cameraIndex + 1) % _cameras!.length;
_setupCamera(_cameras![_cameraIndex]);
}
Future<void> _takePicture() async {
if (_controller == null || !_controller!.value.isInitialized) return;
try {
final image = await _controller!.takePicture();
_showMessage('照片已保存: ${image.path}');
} catch (e) {
_showMessage('拍照失败: $e');
}
}
Future<void> _toggleRecording() async {
if (_controller == null) return;
try {
if (_controller!.value.isRecordingVideo) {
final video = await _controller!.stopVideoRecording();
_showMessage('视频已保存: ${video.path}');
} else {
await _controller!.startVideoRecording();
_showMessage('开始录制');
}
setState(() {});
} catch (e) {
_showMessage('录制失败: $e');
}
}
Future<void> _cycleFlashMode() async {
if (_controller == null) return;
final modes = [FlashMode.off, FlashMode.auto, FlashMode.always, FlashMode.torch];
final currentIndex = modes.indexOf(_flashMode);
_flashMode = modes[(currentIndex + 1) % modes.length];
await _controller!.setFlashMode(_flashMode);
setState(() {});
}
Future<void> _setZoom(double zoom) async {
if (_controller == null) return;
await _controller!.setZoomLevel(zoom);
setState(() => _currentZoom = zoom);
}
void _showMessage(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
);
}
IconData _getFlashIcon() {
switch (_flashMode) {
case FlashMode.off:
return Icons.flash_off;
case FlashMode.auto:
return Icons.flash_auto;
case FlashMode.always:
return Icons.flash_on;
case FlashMode.torch:
return Icons.flashlight_on;
}
}
void dispose() {
WidgetsBinding.instance.removeObserver(this);
_controller?.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
title: const Text('相机'),
actions: [
if (_cameras != null && _cameras!.length > 1)
IconButton(
icon: const Icon(Icons.flip_camera_ios),
onPressed: _switchCamera,
),
],
),
body: Stack(
children: [
if (_isInitialized && _controller != null)
Center(child: CameraPreview(_controller!))
else
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const CircularProgressIndicator(),
const SizedBox(height: 16),
Text(
_statusMessage.isEmpty ? '正在初始化...' : _statusMessage,
style: const TextStyle(color: Colors.white),
),
],
),
),
if (_isInitialized) ...[
Positioned(
top: 20,
right: 20,
child: FloatingActionButton(
mini: true,
heroTag: 'flash',
onPressed: _cycleFlashMode,
child: Icon(_getFlashIcon()),
),
),
Positioned(
bottom: 100,
left: 20,
right: 20,
child: Column(
children: [
Text(
'缩放: ${_currentZoom.toStringAsFixed(1)}x',
style: const TextStyle(
color: Colors.white,
fontSize: 16,
shadows: [Shadow(blurRadius: 4)],
),
),
Slider(
value: _currentZoom,
min: _minZoom,
max: _maxZoom,
onChanged: _setZoom,
),
],
),
),
],
],
),
floatingActionButton: _isInitialized
? Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FloatingActionButton(
heroTag: 'picture',
onPressed: _takePicture,
child: const Icon(Icons.camera_alt),
),
const SizedBox(width: 20),
FloatingActionButton(
heroTag: 'video',
onPressed: _toggleRecording,
backgroundColor: _controller?.value.isRecordingVideo == true
? Colors.red
: null,
child: Icon(
_controller?.value.isRecordingVideo == true
? Icons.stop
: Icons.videocam,
),
),
],
)
: null,
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
);
}
}
更多推荐



所有评论(0)