Flutter笔记:使用相机

Flutter笔记 使用相机


作者李俊才 (jcLee95)blog.csdn.net/qq_28550263
邮箱 : 291148484@163.com
本文地址blog.csdn.net/qq_28550263...

【简介】本文介绍在 Flutter 中 基于使用相机拍摄、录制、切换像头、调整焦距以及曝光等相关知识,以及相关的权限处理。各个部分都配备了操作步骤,以及使用案例,内容丰富翔实。


另外:关于 Dart / Flutter 项目中的文件读写请参考 《目录与文件存储以及在Flutter中的使用》,地址:jclee95.blog.csdn.net/article/det...

目 录* * *


1. 概述

在移动应用开发中,相机功能是一项常见且重要的功能,无论是用于拍照、录像,还是用于扫描二维码、人脸识别等,相机都扮演着重要的角色。在Flutter这个跨平台的移动应用开发框架中,我们可以通过camera库来实现对相机的操作。

camera库是一个Flutter插件,它提供了对iOS、Android和Web设备相机的访问和操作。通过camera库,开发者可以在Flutter应用中实现对设备相机的访问和操作,包括显示相机预览、捕获图片、录制视频等。此外,camera库还提供了对相机权限的处理,以及对相机生命周期的管理,这些都是在开发过程中需要注意和处理的问题。

camera库可以应用于各种需要使用到设备相机的场景。例如,用户可以通过拍照应用拍摄照片,并对照片进行编辑和分享;通过视频录制应用录制视频,并对视频进行剪辑和分享;通过扫描应用扫描二维码或条形码,获取相关信息;或者通过人脸识别应用,应用可以通过相机捕获用户的面部信息,进行人脸识别或面部表情分析。此外,还可以通过相机获取现实世界的视图,结合虚拟信息,创建增强现实的体验。

本文接下来讲介绍基于camera库使用相机拍摄、录制、切换像头、调整焦距以及曝光等相关知识。

2. camera库的安装和配置

在Flutter中使用camera库,首先需要进行安装和配置。

2.1 安装camera库

在你的pubspec.yaml文件中添加camera作为依赖。你可以指定版本,也可以使用最新版本。例如:

yaml 复制代码
dependencies:
  flutter:
    sdk: flutter
    camera: ^0.10.5+5

然后,运行flutter packages get命令来获取包。

2.2 在Android中配置camera库

对于 Android ,你需要在 android/app/build.gradle 文件中将最小的 Android sdk 版本改为 21(或更高)。例如:

markdown 复制代码
android {
  defaultConfig {
    minSdkVersion 21
    ...
  }
}

如果不进行修改,则在 Flutter 的安卓子项目中默认为:

minSdkVersion flutter.minSdkVersion

你需要将该条替换。

需要注意的是,MediaRecorder类在模拟器上不能正常工作,当录制带有声音的视频并试图播放时,持续时间不会正确,你只会看到第一帧。

2.3 在 iOS 中配置camera库

对于iOS,你需要在 ios/Runner/Info.plist 文件中添加两行,一行是键Privacy - Camera Usage Description和使用描述,另一行是键Privacy - Microphone Usage Description和使用描述。例如:

vbnet 复制代码
<key>NSCameraUsageDescription</key>
<string>需要使用您的相机来拍摄照片</string>
<key>NSMicrophoneUsageDescription</key>
<string>需要使用您的麦克风来录制音频</string>

2.3 在 Web 中配置camera库

对于Web,camera 库的使用和在移动设备上基本相同,不需要额外的配置。但是,由于 Web 的安全策略,你可能需要在服务器上配置 HTTPS,因为大多数浏览器都要求使用 HTTPS 来访问设备的摄像头和麦克风。

3. 处理相机权限

在使用 camera 库进行相机操作之前,我们需要获取用户的相机权限。这是因为相机是设备的敏感资源,直接涉及到用户的隐私,所以在访问相机之前必须得到用户的明确许可。

3.1 请求相机权限

Flutter 中,我们可以使用 permission_handler 库来请求相机权限。首先,需要在 pubspec.yaml 文件中添加permission_handler库的依赖。然后,在需要请求权限的地方,调用Permission.camera.request()方法来请求相机权限。

ini 复制代码
PermissionStatus status = await Permission.camera.request();

3.2 处理权限拒绝

如果用户拒绝了相机权限,我们需要给出相应的提示,并引导用户去设置中开启权限。同时,我们也需要处理用户拒绝权限后的操作,比如返回上一页面或者显示错误信息。

scss 复制代码
if (status.isDenied) {
  // 用户拒绝了权限,请相应处理。
}

3.3 处理权限限制

在某些情况下,用户可能无法更改相机权限,比如在家长控制模式下。这时,我们需要检测到这种情况,并给出相应的提示。

scss 复制代码
if (status.isRestricted) {
  // 由于某些限制,用户不能授予权限,请相应处理。
}

4. camera 库的基本使用

Flutter 中,使用 camera 库进行相机操作主要涉及到以下几个步骤:初始化 CameraController,显示相机预览,捕获图片和录制视频。

4.1 初始化CameraController

CameraControllercamera 库中的一个核心类,它用于控制相机的操作。在使用 camera 库时,首先需要创建并初始化一个 CameraController 实例。初始化 CameraController 时,需要指定要使用的相机和分辨率预设。

ini 复制代码
late CameraController controller;
late List<CameraDescription> cameras;

Future<void> initCamera() async {
  cameras = await availableCameras();
  controller = CameraController(cameras[0], ResolutionPreset.max);
  await controller.initialize();
}

4.2 显示相机预览

初始化CameraController后,可以使用CameraPreview小部件来显示相机预览。CameraPreview是一个Flutter小部件,它接收一个CameraController并显示相机的实时预览。

scss 复制代码
Widget build(BuildContext context) {
  if (!controller.value.isInitialized) {
    return Container();
  }
  return CameraPreview(controller);
}

4.3 捕获图片

要捕获图片,可以使用 CameraControllertakePicture 方法。这个方法会捕获一张图片,并将其保存到指定的路径。

csharp 复制代码
Future<void> capturePicture() async {
  final image = await controller.takePicture();
  // image.path contains the saved image path.
}

4.4 录制视频

要录制视频,可以使用 CameraControllerstartVideoRecordingstopVideoRecording方法。startVideoRecording 方法会开始录制视频,stopVideoRecording 方法会停止录制,并将视频保存到指定的路径。

csharp 复制代码
Future<void> startRecording() async {
  await controller.startVideoRecording();
}

Future<void> stopRecording() async {
  final video = await controller.stopVideoRecording();
  // video.path contains the saved video path.
}

5. 示例小项目

接下来,我们将给出一个使用 camera 库实现相机功能的例子。这个例子中,我们将展示如何获取设备上的相机,如何显示相机的实时预览,以及如何实现捕获图片和录制视频的功能。

首先,我们会获取设备上所有可用的相机,并选择其中一个来进行操作。然后,我们会创建一个 CameraController 实例,用于控制相机的操作,包括显示相机预览、捕获图片和录制视频。

在显示相机预览时,我们会使用 CameraPreview 组件,它可以显示选定相机的实时预览。在捕获图片和录制视频时,我们会先请求相机权限,然后调用 CameraController 的相应方法来进行操作。

用户可以通过点击这两个按钮来捕获图片或开始/停止录制视频。这些操作都是实时的,用户可以立即在屏幕上看到结果。

示例代码如下:

arduino 复制代码
import 'package:flutter/material.dart';
import 'package:camera/camera.dart';
import 'package:permission_handler/permission_handler.dart';
csharp 复制代码
// 定义一个全局的相机列表
List<CameraDescription> cameras = [];

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  // 获取可用的相机列表
  cameras = await availableCameras();
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: CameraApp(),
    );
  }
}
scala 复制代码
class CameraApp extends StatefulWidget {
  const CameraApp({super.key});

  @override
  State<CameraApp> createState() => _CameraAppState();
}
scss 复制代码
class _CameraAppState extends State<CameraApp> {
  late CameraController controller; // 定义一个 CameraController
  bool isRecording = false; // 定义一个标志位,表示是否正在录制视频

  @override
  void initState() {
    super.initState();
    // 初始化 CameraController
    controller = CameraController(cameras[0], ResolutionPreset.max);
    controller.initialize().then((_) {
      if (!mounted) {
        return;
      }
      setState(() {});
    });
  }

  @override
  void dispose() {
    // 销毁 CameraController
    controller.dispose();
    super.dispose();
  }

  // 请求相机权限
  Future<void> _askCameraPermission() async {
    var status = await Permission.camera.status;
    if (!status.isGranted) {
      await Permission.camera.request();
    }
  }

  // 捕获图片
  Future<void> _capturePicture() async {
    await _askCameraPermission();
    await controller.takePicture();
  }

  // 开始录制视频
  Future<void> _startRecording() async {
    await _askCameraPermission();
    await controller.startVideoRecording();
    setState(() {
      isRecording = true;
    });
  }

  // 停止录制视频
  Future<void> _stopRecording() async {
    await controller.stopVideoRecording();
    setState(() {
      isRecording = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    if (!controller.value.isInitialized) {
      return Container();
    }
    return Scaffold(
      appBar: AppBar(title: const Text('Camera Demo')),
      body: Column(
        children: <Widget>[
          Expanded(
            child: AspectRatio(
              aspectRatio: controller.value.aspectRatio,
              child: CameraPreview(controller), // 显示相机预览
            ),
          ),
          ListTile(
            leading: const Icon(Icons.camera),
            title: const Text('Capture Picture'),
            onTap: _capturePicture, // 点击后捕获图片
          ),
          ListTile(
            leading: const Icon(Icons.videocam),
            title: Text(isRecording ? 'Stop Recording' : 'Start Recording'),
            onTap:
                isRecording ? _stopRecording : _startRecording, // 点击后开始或停止录制视频
          ),
        ],
      ),
    );
  }
}

当第一次安装应用时,还没有被授权,则会请求用户以获取权限。

这个UI效果看起来像这样:

不过这里仅仅是用于展示基本的用法,为了避免与相机无关的部分内容影响本文要讲解的主要知识点,因此并没有实现视频 和 图片保存的方法。

你可以自行添加相关方法实现录制的图片和视频的保存,或者参考 附1的示例。

6. 高级功能

6.1 切换摄像头

6.1.1 步骤简介

在许多应用中,用户可能需要在前置和后置摄像头之间切换。

要切换摄像头,我们需要先获取所有可用的摄像头,然后在这些摄像头之间切换。具体步骤如下:

  1. 获取所有摄像头:我们可以使用 availableCameras 函数来获取所有可用的摄像头。这个函数返回一个 Future,表示异步获取摄像头列表。
ini 复制代码
final cameras = await availableCameras();
  1. 创建 CameraController:我们需要为每个摄像头创建一个 CameraController。在创建 CameraController 时,我们需要传入 CameraDescription 和分辨率预设。
ini 复制代码
final controller = CameraController(cameras[0], ResolutionPreset.max);
  1. 切换摄像头:要切换摄像头,我们需要先销毁当前的 CameraController,然后创建一个新的 CameraController,并传入新的 CameraDescription。
ini 复制代码
await controller.dispose();
controller = CameraController(cameras[1], ResolutionPreset.max);

由于 CameraController 的创建和销毁都是异步操作,所以我们需要使用 await 关键字来等待这些操作完成。此外,我们还需要在状态类中添加一个新的状态变量来存储当前的 CameraController,以便在 UI 中显示摄像头预览和切换摄像头。

6.1.2 实现案例

以下是一个示例,展示了如何在前置和后置摄像头之间切换:

scala 复制代码
// Author: 李俊才
// Homepage: https://jclee95.blog.csdn.net/
// Email: 291148484@163.com

import 'package:flutter/material.dart';
import 'package:camera/camera.dart';

List<CameraDescription> cameras = [];

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  cameras = await availableCameras();
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: CameraApp(),
    );
  }
}
scss 复制代码
// Author: 李俊才
// Homepage: https://jclee95.blog.csdn.net/
// Email: 291148484@163.com

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

  @override
  State<CameraApp> createState() => _CameraAppState();
}

class _CameraAppState extends State<CameraApp> {
  late CameraController controller; // 定义一个 CameraController
  bool isRecording = false; // 定义一个标志位,表示是否正在录制视频
  late Directory appDir; // 应用的目录

  @override
  void initState() {
    super.initState();
    // 初始化 CameraController
    controller = CameraController(cameras[0], ResolutionPreset.max);
    controller.initialize().then((_) {
      if (!mounted) {
        return;
      }
      setState(() {});
    });
    // 初始化应用目录
    initAppDir();
  }

  // 初始化应用目录
  Future<void> initAppDir() async {
    final status = await Permission.storage.request();
    if (status.isGranted) {
      final docDir = await getApplicationDocumentsDirectory();
      appDir = Directory('${docDir.path}/mycamera/');
      if (!await appDir.exists()) {
        await appDir.create();
      }
    }
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  // 请求相机权限
  Future<void> _askCameraPermission() async {
    var status = await Permission.camera.status;
    if (!status.isGranted) {
      await Permission.camera.request();
    }
  }

  // 捕获图片
  Future<void> _capturePicture() async {
    await _askCameraPermission();
    final file = await controller.takePicture();
    final savedFile =
        await File(file.path).copy('${appDir.path}/${DateTime.now()}.png');
    setState(() {
      // 显示提示信息
      ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('Picture saved at ${savedFile.path}')));
    });
  }

  // 开始录制视频
  Future<void> _startRecording() async {
    await _askCameraPermission();
    await controller.startVideoRecording();
    setState(() {
      isRecording = true;
    });
  }

  // 停止录制视频
  Future<void> _stopRecording() async {
    XFile file = await controller.stopVideoRecording();
    final savedFile =
        await File(file.path).copy('${appDir.path}/${DateTime.now()}.mp4');
    setState(() {
      isRecording = false;
      // 显示提示信息
      ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('Video saved at ${savedFile.path}')));
    });
  }

  @override
  Widget build(BuildContext context) {
    if (!controller.value.isInitialized) {
      return Container();
    }
    return Scaffold(
      appBar: AppBar(title: const Text('Camera Demo')),
      body: Column(
        children: <Widget>[
          Expanded(
            child: AspectRatio(
              aspectRatio: controller.value.aspectRatio,
              child: CameraPreview(controller), // 显示相机预览
            ),
          ),
          ListTile(
            leading: const Icon(Icons.camera),
            title: const Text('Capture Picture'),
            onTap: _capturePicture, // 点击后捕获图片
          ),
          ListTile(
            leading: const Icon(Icons.videocam),
            title: Text(isRecording ? 'Stop Recording' : 'Start Recording'),
            onTap:
                isRecording ? _stopRecording : _startRecording, // 点击后开始或停止录制视频
          ),
        ],
      ),
    );
  }
}

在这个示例中,我们首先获取了设备上所有可用的相机,并将它们保存在一个列表中。然后,我们创建了一个 CameraController 实例,用于控制相机的操作。当用户点击 "Switch Camera" 按钮时,我们会改变 selectedCameraIndex,并重新初始化 CameraController,从而实现在前置和后置摄像头之间切换。

6.2 调整焦距

6.2.1 步骤简介

聚焦是指调整摄像头的镜头,使得被摄物体在图像上清晰可见。在数字摄像头中,我们可以通过改变镜头的焦距来调整聚焦。焦距越大,摄像头视野中的物体看起来就越近,反之则越远。

可见,调整摄像头的焦距是一个相当常见的需要求。

camera 库中,我们可以使用 setZoomLevel 方法来调整焦距。

具体步骤如下:

  1. 创建 CameraController :我们需要为摄像头创建一个 CameraController 。在创建 CameraController 时,我们需要传入 CameraDescription 和分辨率预设。
ini 复制代码
controller = CameraController(cameras[0], ResolutionPreset.max);
  1. 初始化 CameraController :在使用 CameraController 之前,我们需要先初始化它。初始化操作是异步的,所以我们需要使用 await 关键字来等待操作完成。
csharp 复制代码
await controller.initialize();
  1. 获取最大焦距级别:我们可以使用 getMaxZoomLevel 方法来获取摄像头的最大焦距级别。这个方法返回一个 Future,表示异步获取焦距级别。
ini 复制代码
maxZoomLevel = await controller.getMaxZoomLevel();
  1. 设置焦距:我们可以使用 setZoomLevel 方法来设置焦距。这个方法接受一个 double 类型的参数,表示新的焦距级别。在设置焦距级别时,我们需要确保新的焦距级别在 1 和 maxZoomLevel 之间。
csharp 复制代码
await controller.setZoomLevel(newZoomLevel);
  1. 更新 UI:为了在 UI 中显示和修改当前的焦距级别,我们需要在状态类中添加一个新的状态变量来存储当前的焦距级别。

例如,我们可以在 build 方法中使用 Slider 控件来显示和修改焦距级别。

less 复制代码
Slider(
  value: currentZoomLevel,
  min: 1.0,
  max: maxZoomLevel,
  onChanged: _setZoomLevel,
  label: 'Zoom Level',
),

6.2.2 实现案例

以下是一个示例,展示了如何调整焦距:

scala 复制代码
// Author: 李俊才
// Homepage: https://jclee95.blog.csdn.net/
// Email: 291148484@163.com

import 'package:flutter/material.dart';
import 'package:camera/camera.dart';

List<CameraDescription> cameras = [];

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  cameras = await availableCameras();
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: CameraApp(),
    );
  }
}
scss 复制代码
// Author: 李俊才
// Homepage: https://jclee95.blog.csdn.net/
// Email: 291148484@163.com

class CameraApp extends StatefulWidget {
  const CameraApp({Key? key}) : super(key: key);

  @override
  State<CameraApp> createState() => _CameraAppState();
}

class _CameraAppState extends State<CameraApp> {
  late CameraController controller; // 控制器,用于操作摄像头
  double currentZoomLevel = 1.0; // 当前的焦距级别
  double maxZoomLevel = 1.0; // 最大的焦距级别

  @override
  void initState() {
    super.initState();
    // 创建 CameraController
    controller = CameraController(cameras[0], ResolutionPreset.max);
    // 初始化 CameraController
    controller.initialize().then((_) async {
      if (!mounted) {
        return;
      }
      // 获取最大焦距级别
      maxZoomLevel = await controller.getMaxZoomLevel();
      setState(() {});
    });
  }

  void _setZoomLevel(double zoomLevel) async {
    // 检查新的焦距级别是否在有效范围内
    if (zoomLevel < 1 || zoomLevel > maxZoomLevel) {
      return;
    }
    // 设置新的焦距级别
    await controller.setZoomLevel(zoomLevel);
    setState(() {
      // 更新当前的焦距级别
      currentZoomLevel = zoomLevel;
    });
  }

  @override
  Widget build(BuildContext context) {
    // 检查 CameraController 是否已初始化
    if (!controller.value.isInitialized) {
      return Container();
    }
    return Scaffold(
      appBar: AppBar(title: const Text('Camera Demo')),
      body: Column(
        children: <Widget>[
          Expanded(
            child: AspectRatio(
              // 设置预览的宽高比
              aspectRatio: controller.value.aspectRatio,
              // 显示摄像头预览
              child: CameraPreview(controller),
            ),
          ),
          Slider(
            // 显示和修改当前的焦距级别
            value: currentZoomLevel,
            min: 1.0,
            max: maxZoomLevel,
            onChanged: _setZoomLevel,
            label: 'Zoom Level',
          ),
        ],
      ),
    );
  }
}

在这个示例中,我们首先创建和初始化了 CameraController,然后获取了最大焦距级别,并在状态变量 maxZoomLevel 中存储了它。然后,我们添加了一个 Slider 控件来显示和修改当前的焦距级别。当用户移动 Slider 时,我们会调用 _setZoomLevel 方法来设置新的焦距级别。

效果如下:

6.3 调整曝光

6.3.1 步骤简介

用户还可能需要调整摄像头的曝光。

曝光是指摄像头的 感光元件 (如 CCD 或 CMOS)接收到的 光量

曝光的多少会影响图像的亮度和色彩:

  • 曝光过多,图像会过亮,可能会丢失细节;
  • 曝光不足,图像会过暗,同样可能会丢失细节。

在 camera 库中,我们可以使用 setExposureOffset 方法来设置曝光偏移

其中,曝光偏移(Exposure Offset),是一个可以调整摄像头曝光的参数。它是一个浮点数,可以是负数、零或正数。

  • 负的曝光偏移会减少摄像头的曝光,使图像变暗;
  • 正的曝光偏移会增加摄像头的曝光,使图像变亮。

以下是调整曝光的主要步骤:

  1. 创建 CameraController :我们需要为摄像头创建一个 CameraController 。在创建 CameraController 时,我们需要传入 CameraDescription 和 分辨率预设。

    ini 复制代码
    controller = CameraController(cameras[0], ResolutionPreset.max);

    其中:

    • cameras[0]:这是一个 CameraDescription 对象,表示要控制的摄像头。在这个例子中,我们选择了设备上的第一个摄像头;
    • ResolutionPreset.max:这个枚举值表示摄像头的分辨率预设。ResolutionPreset.max 表示使用摄像头的最大分辨率。
  2. 初始化 CameraController :在使用 CameraController 之前,我们需要先初始化它。初始化操作是异步的,所以我们需要使用 await 关键字来等待操作完成。

csharp 复制代码
await controller.initialize();
  1. 获取最大和最小曝光偏移:我们可以使用 getMinExposureOffset 和 getMaxExposureOffset 方法来获取摄像头的最大和最小曝光偏移。这两个方法都返回一个 Future,表示异步获取曝光偏移。
ini 复制代码
minExposureOffset = await controller.getMinExposureOffset();
maxExposureOffset = await controller.getMaxExposureOffset();
  1. 设置曝光偏移:我们可以使用 setExposureOffset 方法来设置曝光偏移。这个方法接受一个 double 类型的参数,表示新的曝光偏移。在设置曝光偏移时,我们需要确保新的曝光偏移在 minExposureOffset 和 maxExposureOffset 之间。
csharp 复制代码
await controller.setExposureOffset(newExposureOffset);
  1. 更新 UI:为了在 UI 中显示和修改当前的曝光偏移,我们需要在状态类中添加一个新的状态变量来存储当前的曝光偏移。然后,我们可以在 build 方法中使用 Slider 控件来显示和修改曝光偏移。
less 复制代码
Slider(
  value: currentExposureOffset,
  min: minExposureOffset,
  max: maxExposureOffset,
  onChanged: _setExposureOffset,
  label: 'Exposure Offset',
),

在这个步骤中,我们首先创建和初始化了 CameraController,然后获取了最大和最小曝光偏移,并在状态变量中存储了它们。然后,我们添加了一个 Slider 控件来显示和修改当前的曝光偏移。当用户移动 Slider 时,我们会调用 _setExposureOffset 方法来设置新的曝光偏移。

6.2.2 实现案例

下面的例子展示了调整曝光:

scala 复制代码
// Author: 李俊才
// Homepage: https://jclee95.blog.csdn.net/
// Email: 291148484@163.com

import 'package:flutter/material.dart';
import 'package:camera/camera.dart';

List<CameraDescription> cameras = [];

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  cameras = await availableCameras();
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: CameraApp(),
    );
  }
}
scss 复制代码
// Author: 李俊才
// Homepage: https://jclee95.blog.csdn.net/
// Email: 291148484@163.com

class CameraApp extends StatefulWidget {
  const CameraApp({Key? key}) : super(key: key);

  @override
  State<CameraApp> createState() => _CameraAppState();
}

class _CameraAppState extends State<CameraApp> {
  late CameraController controller;
  double currentExposureOffset = 0.0;
  double minExposureOffset = 0.0;
  double maxExposureOffset = 0.0;

  @override
  void initState() {
    super.initState();
    // 创建 CameraController
    controller = CameraController(cameras[0], ResolutionPreset.max);
    // 初始化 CameraController
    controller.initialize().then((_) async {
      if (!mounted) {
        return;
      }
      // 获取最大和最小曝光偏移
      minExposureOffset = await controller.getMinExposureOffset();
      maxExposureOffset = await controller.getMaxExposureOffset();
      setState(() {});
    });
  }

  void _setExposureOffset(double exposureOffset) async {
    // 检查新的曝光偏移是否在有效范围内
    if (exposureOffset < minExposureOffset || exposureOffset > maxExposureOffset) {
      return;
    }
    // 设置新的曝光偏移
    await controller.setExposureOffset(exposureOffset);
    setState(() {
      // 更新当前的曝光偏移
      currentExposureOffset = exposureOffset;
    });
  }

  @override
  Widget build(BuildContext context) {
    if (!controller.value.isInitialized) {
      return Container();
    }
    return Scaffold(
      appBar: AppBar(title: const Text('Camera Demo')),
      body: Column(
        children: <Widget>[
          Expanded(
            child: AspectRatio(
              // 设置预览的宽高比
              aspectRatio: controller.value.aspectRatio,
              // 显示摄像头预览
              child: CameraPreview(controller),
            ),
          ),
          Slider(
            // 显示和修改当前的曝光偏移
            value: currentExposureOffset,
            min: minExposureOffset,
            max: maxExposureOffset,
            onChanged: _setExposureOffset,
            label: 'Exposure Offset',
          ),
        ],
      ),
    );
  }
}

在这个示例中,我们首先创建和初始化了 CameraController ,然后获取了最大和最小曝光偏移,并在状态变量中存储了它们。然后,我们添加了一个 Slider 控件来显示和修改当前的曝光偏移。当用户移动 Slider 时,我们会调用 _setExposureOffset 方法来设置新的曝光偏移。看起来效果是这样的:

6.2.3 曝光+聚焦的例子

下面的例子展示了同时调整曝光+聚焦:

scala 复制代码
// Author: 李俊才
// Homepage: https://jclee95.blog.csdn.net/
// Email: 291148484@163.com

import 'package:flutter/material.dart';
import 'package:camera/camera.dart';

List<CameraDescription> cameras = [];

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  cameras = await availableCameras();
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: CameraApp(),
    );
  }
}
scss 复制代码
// Author: 李俊才
// Homepage: https://jclee95.blog.csdn.net/
// Email: 291148484@163.com

class CameraApp extends StatefulWidget {
  const CameraApp({Key? key}) : super(key: key);

  @override
  State<CameraApp> createState() => _CameraAppState();
}

class _CameraAppState extends State<CameraApp> {
  late CameraController controller;
  double currentZoomLevel = 1.0;
  double maxZoomLevel = 1.0;
  double currentExposureOffset = 0.0;
  double minExposureOffset = 0.0;
  double maxExposureOffset = 0.0;

  @override
  void initState() {
    super.initState();
    controller = CameraController(cameras[0], ResolutionPreset.max);
    controller.initialize().then((_) async {
      if (!mounted) {
        return;
      }
      maxZoomLevel = await controller.getMaxZoomLevel();
      minExposureOffset = await controller.getMinExposureOffset();
      maxExposureOffset = await controller.getMaxExposureOffset();
      setState(() {});
    });
  }

  void _setZoomLevel(double zoomLevel) async {
    if (zoomLevel < 1 || zoomLevel > maxZoomLevel) {
      return;
    }
    await controller.setZoomLevel(zoomLevel);
    setState(() {
      currentZoomLevel = zoomLevel;
    });
  }

  void _setExposureOffset(double exposureOffset) async {
    if (exposureOffset < minExposureOffset ||
        exposureOffset > maxExposureOffset) {
      return;
    }
    await controller.setExposureOffset(exposureOffset);
    setState(() {
      currentExposureOffset = exposureOffset;
    });
  }

  @override
  Widget build(BuildContext context) {
    if (!controller.value.isInitialized) {
      return Container();
    }
    return Scaffold(
      appBar: AppBar(title: const Text('Camera Demo')),
      body: Column(
        children: <Widget>[
          Expanded(
            child: AspectRatio(
              aspectRatio: controller.value.aspectRatio,
              child: CameraPreview(controller),
            ),
          ),
          Slider(
            value: currentZoomLevel,
            min: 1.0,
            max: maxZoomLevel,
            onChanged: _setZoomLevel,
            label: 'Zoom Level',
          ),
          Slider(
            value: currentExposureOffset,
            min: minExposureOffset,
            max: maxExposureOffset,
            onChanged: _setExposureOffset,
            label: 'Exposure Offset',
          ),
        ],
      ),
    );
  }
}

7. 补充:相关权限的处理

7.1 请求权限

在前面的示例中实际上已经涉及到了请求权限,有一些入门读者可能不是很清楚 Flutter 中如何请求权限,这里进行一些补充。

在这个应用中,你需要获取以下权限:

  1. 相机权限:这个权限是必需的,因为应用需要使用设备的相机来捕获图片和录制视频。
  2. 存储权限:这个权限也是必需的,因为应用需要将捕获的图片和录制的视频保存到设备的存储中。在 Android 10(API 级别 29)及以上版本中,如果你只需要访问应用的私有目录(例如通过getApplicationDocumentsDirectory获取的目录),那么你不需要这个权限。但是,如果你需要访问其他目录,例如用户的公共目录,那么你仍然需要这个权限。

这些权限的请求和处理都应该在运行时进行,而不是在安装时进行。这是因为用户有权在任何时候撤销这些权限。因此,你的应用应该在每次需要使用这些权限时都检查它们的状态,并在需要时请求它们。

在 Flutter 中,我们可以使用 permission_handler 库来请求权限。首先,我们需要在项目中安装 permission_handler 库。在 pubspec.yaml 文件中添加以下依赖:

yaml 复制代码
dependencies:
  flutter:
    sdk: flutter
  permission_handler: ^11.0.1

然后,运行 flutter pub get 命令来获取库。

然后就可以在你的代码中,分别检查和请求 相机权限 以及 存储权限了,以下是一个模板:

csharp 复制代码
import 'package:permission_handler/permission_handler.dart';

Future<void> requestPermissions() async {
  // 请求相机权限
  var cameraStatus = await Permission.camera.status;
  if (!cameraStatus.isGranted) {
    cameraStatus = await Permission.camera.request();
    if (!cameraStatus.isGranted) {
      // 用户拒绝了相机权限
      // 在这里处理权限被拒绝的情况
    }
  }

  // 请求存储权限
  var storageStatus = await Permission.storage.status;
  if (!storageStatus.isGranted) {
    storageStatus = await Permission.storage.request();
    if (!storageStatus.isGranted) {
      // 用户拒绝了存储权限
      // 在这里处理权限被拒绝的情况
    }
  }
}

7.2 处理权限拒绝

当用户拒绝权限请求时,我们需要妥善处理这种情况,以确保应用的正常运行。以下是一些处理权限被拒绝的常见策略:

  1. 提示用户:当用户拒绝权限请求时,我们可以显示一个对话框或者 Snackbar,告诉用户应用需要这个权限来提供某项功能,并引导他们去设置中开启权限。
  2. 降级处理:如果可能,我们可以提供一种降级的方案,即在没有这个权限的情况下,提供一种有限的功能或者体验。
  3. 退出应用:如果这个权限是应用必需的,那么在用户拒绝权限请求时,我们可能需要退出应用。

下面是一个模板,包括了相机权限和存储权限拒绝的处理。实际开发中,可以依据你的需要进行调整和修改。

less 复制代码
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';

Future<void> requestPermissions(BuildContext context) async {
  // 请求相机权限
  var cameraStatus = await Permission.camera.status;
  if (!cameraStatus.isGranted) {
    cameraStatus = await Permission.camera.request();
    if (!cameraStatus.isGranted) {
      // 用户拒绝了相机权限
      // 显示一个对话框,告诉用户应用需要相机权限
      showDialog(
        context: context,
        builder: (BuildContext context) {
          return AlertDialog(
            title: Text('相机权限拒绝'),
            content: Text('此应用程序需要相机权限来捕捉图片和录制视频。请转到"设置"并授予权限'),
            actions: <Widget>[
              TextButton(
                child: Text('OK'),
                onPressed: () {
                  Navigator.of(context).pop();
                },
              ),
            ],
          );
        },
      );
    }
  }

  // 请求存储权限
  var storageStatus = await Permission.storage.status;
  if (!storageStatus.isGranted) {
    storageStatus = await Permission.storage.request();
    if (!storageStatus.isGranted) {
      // 用户拒绝了存储权限
      // 显示一个对话框,告诉用户应用需要存储权限
      showDialog(
        context: context,
        builder: (BuildContext context) {
          return AlertDialog(
            title: Text('存储权限被拒绝'),
            content: Text('此应用需要存储权限来保存图片和视频。请转到"设置"并授予权限。'),
            actions: <Widget>[
              TextButton(
                child: Text('OK'),
                onPressed: () {
                  Navigator.of(context).pop();
                },
              ),
            ],
          );
        },
      );
    }
  }
}
相关推荐
绘绘~6 分钟前
android studio new flutter project-运行第一个flutter项目
android·flutter·android studio
H_kiwi4 小时前
APT 参与者将恶意软件嵌入 macOS Flutter 应用程序中
java·python·安全·flutter·macos·安全威胁分析·安全性测试
yujunlong39194 小时前
flutter pigeon gomobile 插件中使用go工具类
flutter·golang·kotlin·swift
weifont21 小时前
Flutter入门第一节(共56)
学习·flutter
程序员阿俊21 小时前
Flutter开发应用安装二次打开闪退,ios解决方案
flutter·ios·蓝桥杯
木叶丸1 天前
框架中常用的过滤器、拦截器是如何实现的?
flutter·ios·设计模式
VUE1 天前
Flutter 系列之GetX的学习(3) --> 其他实用功能
flutter
VUE1 天前
Flutter 系列之GetX的学习(1) --> 状态管理
flutter
fifiAmx1 天前
Flutter Getx状态管理
flutter
sunly_1 天前
Flutter:input输入框
android·flutter