• Flutter笔记:使用相机


    Flutter笔记
    使用相机

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

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


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


    1. 概述

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

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

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

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

    2. camera库的安装和配置

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

    2.1 安装camera库

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

    dependencies:
      flutter:
        sdk: flutter
        camera: ^0.10.5+5
    
    • 1
    • 2
    • 3
    • 4

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

    2.2 在Android中配置camera库

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

    android {
      defaultConfig {
        minSdkVersion 21
        ...
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

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

    minSdkVersion flutter.minSdkVersion
    
    • 1

    你需要将该条替换。

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

    2.3 在 iOS 中配置camera库

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

    <key>NSCameraUsageDescriptionkey>
    <string>需要使用您的相机来拍摄照片string>
    <key>NSMicrophoneUsageDescriptionkey>
    <string>需要使用您的麦克风来录制音频string>
    
    • 1
    • 2
    • 3
    • 4

    2.3 在 Web 中配置camera库

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

    3. 处理相机权限

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

    3.1 请求相机权限

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

    PermissionStatus status = await Permission.camera.request();
    
    • 1

    3.2 处理权限拒绝

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

    if (status.isDenied) {
      // 用户拒绝了权限,请相应处理。
    }
    
    • 1
    • 2
    • 3

    3.3 处理权限限制

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

    if (status.isRestricted) {
      // 由于某些限制,用户不能授予权限,请相应处理。
    }
    
    • 1
    • 2
    • 3

    4. camera 库的基本使用

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

    4.1 初始化CameraController

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

    late CameraController controller;
    late List<CameraDescription> cameras;
    
    Future<void> initCamera() async {
      cameras = await availableCameras();
      controller = CameraController(cameras[0], ResolutionPreset.max);
      await controller.initialize();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    4.2 显示相机预览

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

    Widget build(BuildContext context) {
      if (!controller.value.isInitialized) {
        return Container();
      }
      return CameraPreview(controller);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    4.3 捕获图片

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

    Future<void> capturePicture() async {
      final image = await controller.takePicture();
      // image.path contains the saved image path.
    }
    
    • 1
    • 2
    • 3
    • 4

    4.4 录制视频

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

    Future<void> startRecording() async {
      await controller.startVideoRecording();
    }
    
    Future<void> stopRecording() async {
      final video = await controller.stopVideoRecording();
      // video.path contains the saved video path.
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    5. 示例小项目

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

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

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

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

    示例代码如下:

    import 'package:flutter/material.dart';
    import 'package:camera/camera.dart';
    import 'package:permission_handler/permission_handler.dart';
    
    • 1
    • 2
    • 3
    // 定义一个全局的相机列表
    List<CameraDescription> cameras = [];
    
    void main() async {
      WidgetsFlutterBinding.ensureInitialized();
      // 获取可用的相机列表
      cameras = await availableCameras();
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
    
      
      Widget build(BuildContext context) {
        return const MaterialApp(
          home: CameraApp(),
        );
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    class CameraApp extends StatefulWidget {
      const CameraApp({super.key});
    
      
      State<CameraApp> createState() => _CameraAppState();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    class _CameraAppState extends State<CameraApp> {
      late CameraController controller; // 定义一个 CameraController
      bool isRecording = false; // 定义一个标志位,表示是否正在录制视频
    
      
      void initState() {
        super.initState();
        // 初始化 CameraController
        controller = CameraController(cameras[0], ResolutionPreset.max);
        controller.initialize().then((_) {
          if (!mounted) {
            return;
          }
          setState(() {});
        });
      }
    
      
      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;
        });
      }
    
      
      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, // 点击后开始或停止录制视频
              ),
            ],
          ),
        );
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86

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

    在这里插入图片描述在这里插入图片描述

    这个UI效果看起来像这样:
    在这里插入图片描述

    不过这里仅仅是用于展示基本的用法,为了避免与相机无关的部分内容影响本文要讲解的主要知识点,因此并没有实现视频 和 图片保存的方法。
    你可以自行添加相关方法实现录制的图片和视频的保存,或者参考 附1的示例。

    6. 高级功能

    6.1 切换摄像头

    6.1.1 步骤简介

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

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

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

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

    6.1.2 实现案例

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

    // 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});
    
      
      Widget build(BuildContext context) {
        return const MaterialApp(
          home: CameraApp(),
        );
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    // Author: 李俊才
    // Homepage: https://jclee95.blog.csdn.net/
    // Email: 291148484@163.com
    
    class CameraApp extends StatefulWidget {
      const CameraApp({super.key});
    
      
      State<CameraApp> createState() => _CameraAppState();
    }
    
    class _CameraAppState extends State<CameraApp> {
      late CameraController controller; // 定义一个 CameraController
      bool isRecording = false; // 定义一个标志位,表示是否正在录制视频
      late Directory appDir; // 应用的目录
    
      
      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();
          }
        }
      }
    
      
      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}')));
        });
      }
    
      
      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, // 点击后开始或停止录制视频
              ),
            ],
          ),
        );
      }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124

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

    在这里插入图片描述

    6.2 调整焦距

    6.2.1 步骤简介

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

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

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

    具体步骤如下:

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

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

    Slider(
      value: currentZoomLevel,
      min: 1.0,
      max: maxZoomLevel,
      onChanged: _setZoomLevel,
      label: 'Zoom Level',
    ),
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    6.2.2 实现案例

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

    // 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});
    
      
      Widget build(BuildContext context) {
        return const MaterialApp(
          home: CameraApp(),
        );
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    // Author: 李俊才
    // Homepage: https://jclee95.blog.csdn.net/
    // Email: 291148484@163.com
    
    class CameraApp extends StatefulWidget {
      const CameraApp({Key? key}) : super(key: key);
    
      
      State<CameraApp> createState() => _CameraAppState();
    }
    
    class _CameraAppState extends State<CameraApp> {
      late CameraController controller; // 控制器,用于操作摄像头
      double currentZoomLevel = 1.0; // 当前的焦距级别
      double maxZoomLevel = 1.0; // 最大的焦距级别
    
      
      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;
        });
      }
    
      
      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',
              ),
            ],
          ),
        );
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76

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

    效果如下:
    在这里插入图片描述

    6.3 调整曝光

    6.3.1 步骤简介

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

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

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

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

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

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

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

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

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

      controller = CameraController(cameras[0], ResolutionPreset.max);
      
      • 1

      其中:

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

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

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

    6.2.2 实现案例

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

    // 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);
    
      
      Widget build(BuildContext context) {
        return const MaterialApp(
          home: CameraApp(),
        );
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    // Author: 李俊才
    // Homepage: https://jclee95.blog.csdn.net/
    // Email: 291148484@163.com
    
    class CameraApp extends StatefulWidget {
      const CameraApp({Key? key}) : super(key: key);
    
      
      State<CameraApp> createState() => _CameraAppState();
    }
    
    class _CameraAppState extends State<CameraApp> {
      late CameraController controller;
      double currentExposureOffset = 0.0;
      double minExposureOffset = 0.0;
      double maxExposureOffset = 0.0;
    
      
      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;
        });
      }
    
      
      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',
              ),
            ],
          ),
        );
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77

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

    在这里插入图片描述

    6.2.3 曝光+聚焦的例子

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

    // 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);
    
      
      Widget build(BuildContext context) {
        return const MaterialApp(
          home: CameraApp(),
        );
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    // Author: 李俊才
    // Homepage: https://jclee95.blog.csdn.net/
    // Email: 291148484@163.com
    
    class CameraApp extends StatefulWidget {
      const CameraApp({Key? key}) : super(key: key);
    
      
      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;
    
      
      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;
        });
      }
    
      
      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',
              ),
            ],
          ),
        );
      }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90

    在这里插入图片描述

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

    7.1 请求权限

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

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

    1. 相机权限:这个权限是必需的,因为应用需要使用设备的相机来捕获图片和录制视频。

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

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

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

    dependencies:
      flutter:
        sdk: flutter
      permission_handler: ^11.0.1
    
    • 1
    • 2
    • 3
    • 4

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

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

    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) {
          // 用户拒绝了存储权限
          // 在这里处理权限被拒绝的情况
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    7.2 处理权限拒绝

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

    1. 提示用户:当用户拒绝权限请求时,我们可以显示一个对话框或者 Snackbar,告诉用户应用需要这个权限来提供某项功能,并引导他们去设置中开启权限。

    2. 降级处理:如果可能,我们可以提供一种降级的方案,即在没有这个权限的情况下,提供一种有限的功能或者体验。

    3. 退出应用:如果这个权限是应用必需的,那么在用户拒绝权限请求时,我们可能需要退出应用。

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

    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();
                    },
                  ),
                ],
              );
            },
          );
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
  • 相关阅读:
    FastestDet---模型训练
    如何拥有自己的专属GPT-本地部署目前最强大模型llama3
    python——运行方式
    小记java正则表达式中matcher.find() 和 matcher.matches() 的区别
    Typescript面向对象(接口、类、多态、重写、抽象类、访问修饰符)
    【等保】网络安全等级保护(等保2.0PPT)
    广告联盟是什么?app开发者如何选择广告联盟?
    Linux笔记--文件内容的查阅与统计
    前端经典面试题 | Computed 和 Watch 的区别
    Redis7学习笔记01
  • 原文地址:https://blog.csdn.net/qq_28550263/article/details/134493373