Flutter笔记:使用相机

news2024/10/6 20:35:25
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

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

2.2 在Android中配置camera库

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

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和使用描述。例如:

<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()方法来请求相机权限。

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

3.2 处理权限拒绝

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

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

3.3 处理权限限制

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

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

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();
}

4.2 显示相机预览

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

Widget build(BuildContext context) {
  if (!controller.value.isInitialized) {
    return Container();
  }
  return CameraPreview(controller);
}

4.3 捕获图片

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

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

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.
}

5. 示例小项目

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

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

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

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

示例代码如下:

import 'package:flutter/material.dart';
import 'package:camera/camera.dart';
import 'package:permission_handler/permission_handler.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(),
    );
  }
}
class CameraApp extends StatefulWidget {
  const CameraApp({super.key});

  
  State<CameraApp> createState() => _CameraAppState();
}
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, // 点击后开始或停止录制视频
          ),
        ],
      ),
    );
  }
}

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

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

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

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

6. 高级功能

6.1 切换摄像头

6.1.1 步骤简介

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

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

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

由于 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(),
    );
  }
}
// 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, // 点击后开始或停止录制视频
          ),
        ],
      ),
    );
  }
}

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

在这里插入图片描述

6.2 调整焦距

6.2.1 步骤简介

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

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

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

具体步骤如下:

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

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

Slider(
  value: currentZoomLevel,
  min: 1.0,
  max: maxZoomLevel,
  onChanged: _setZoomLevel,
  label: 'Zoom Level',
),

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(),
    );
  }
}
// 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',
          ),
        ],
      ),
    );
  }
}

在这个示例中,我们首先创建和初始化了 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);
    

    其中:

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

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

在这个步骤中,我们首先创建和初始化了 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(),
    );
  }
}
// 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',
          ),
        ],
      ),
    );
  }
}

在这个示例中,我们首先创建和初始化了 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(),
    );
  }
}
// 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',
          ),
        ],
      ),
    );
  }
}

在这里插入图片描述

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

然后,运行 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) {
      // 用户拒绝了存储权限
      // 在这里处理权限被拒绝的情况
    }
  }
}

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();
                },
              ),
            ],
          );
        },
      );
    }
  }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1233295.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

系列一、介绍

一、概述 官网&#xff1a; ThreadLocal用于提供线程内的局部变量&#xff0c;不同线程之间不会互相干扰&#xff0c;这种变量在线程的生命周期内起作用&#xff0c;减少同一个线程内多个函数或者组件之间一些公共变量传递的复杂度。 大白话&#xff1a; 线程并发&#xff1a;T…

RFID读写器在物联网中的应用与优势

随着物联网技术的不断发展&#xff0c;RFID读写器作为物联网感知层的重要组成部分&#xff0c;在各个领域得到了广泛应用。本文将介绍RFID读写器在物联网中的应用及优势。 一、RFID读写器概述 RFID&#xff08;Radio Frequency Identification&#xff09;技术是一种利用无线…

媲美有线操作,支持4KHz响应和无线充电的游戏鼠标,雷柏VT3S上手

对于无线鼠标来说&#xff0c;操作延迟和精度对游戏操作影响很大&#xff0c;常见的游戏鼠标至少都有1KHz的回报率&#xff0c;而雷柏今年已经出了很多支持4KHz回报的鼠标了&#xff0c;像是我现在用的这款VT3S游戏鼠标&#xff0c;就搭载了旗舰级的原相3395引擎&#xff0c;支…

计算机网络的OSI七层模型

目录 1、OSI七层模型是什么 1.1 物理层&#xff08;Physical Layer&#xff09; 1.2 数据链路层&#xff08;Data Link Layer&#xff09; 1.3 网络层&#xff08;Network Layer&#xff09; 1.4 传输层&#xff08;Transport Layer&#xff09; 1.5 会话层&#xff08;S…

PLC设备相关常用英文单词(一)

PLC设备相关常用英文单词&#xff08;一&#xff09; Baud rate 波特率Bus 总线Binary 二进制Configuration 组态Consistent data 一致性数据Counter 计数器Cycle time 循环时间Conveyor 传送Device names 设备名称Debug 调试Download 下载Expand 扩展Fix 固定Flow 流量Functio…

【数据结构(三)】单链表(1)

文章目录 1. 链表介绍2. 单链表应用实例2.1. 顺序添加方式2.1.1. 思路分析2.1.2. 代码实现 2.2. 按照编号顺序添加方式2.2.1. 思路分析2.2.2. 代码实现 3. 单链表节点的修改3.1. 思路分析3.2. 代码实现 4. 单链表节点的删除4.1. 思路分析4.2. 代码实现 5. 单链表常见面试题5.1.…

支持寄存器模型读写功能的AHB driver实现——导读

1.前言 UVM driver在接口协议的实现中起着非常重要的作用&#xff0c;因为它一端处理基于类的事务级sequence&#xff0c;另一端处理基于时钟的信号/引脚级的总线行为。因此&#xff0c;如何实现 UVM driver及其与sequence的同步对于 DUT 和 UVM 环境之间的交互以及避免 UVM d…

LeSS敏捷框架高效生产力实践

每个团队可能都有一套适合自己的敏捷方法&#xff0c;本文介绍了ResponseTap工程团队通过采用LeSS框架、引入准备周&#xff0c;从而提升迭代冲刺研发效能的实践。原文: LeSS Agile, More Productive — Part 1: Pain[1], LeSS Agile, More Productive — Part 2: Promise, LeS…

吉他效果器开发方法

吉他效果器开发方法 是否需要申请加入数字音频系统研究开发交流答疑群(课题组)&#xff1f;可加我微信hezkz17, 本群提供音频技术答疑服务&#xff0c;群赠送语音信号处理降噪算法&#xff0c;蓝牙耳机音频&#xff0c;ADI DSP音频项目核心开发资料, 1 做出的效果图 2 介绍 …

ISP概念入门

这里写自定义目录标题 引言ISP的处理流程1、Sensor有暗电流2、通过镜头到达Sensor中间的光多于到达Sensor的边缘的光&#xff0c;即光学系统中的渐晕3、Senor上有的像素点的输出有坏点4、Cmos的Sensor采用了Bayer色彩滤波阵列(Bayer Color Filter Array&#xff0c;CFA)5、Seno…

css animation 动画如何保留动画结束后的状态 animation-fill-mode: forwards

css animation 动画如何保留动画结束后的状态 animation-fill-mode: forwards 一、问题描述 在做一个弹窗动画提示的时候遇到了一个问题&#xff1a; 在动画结束的时候&#xff0c;移除元素时会有闪一下的问题&#xff0c;像这样&#xff0c;有残留的痕迹。 我的动画结尾是这…

Mysql之聚合函数

Mysql之聚合函数 什么是聚合函数常见的聚合函数GROUP BYWITH ROLLUPHAVINGHAVING与WHERE的对比 总结SQL底层原理 什么是聚合函数 对一组数据进行汇总的函数&#xff0c;但是还是返回一个结果 聚合函数也叫聚集&#xff0c;分组函数 常见的聚合函数 1.AVG(): 求平均值 2.SUM() :…

choices参数的使用、MVC和MTV的模式、创建表对表关系的三种创建方式

【1】choices参数的使用 应用场景&#xff1a;针对表中可能列表完全的字段&#xff0c;采用choices参数 例如&#xff1a;性别&#xff0c;代码如下 # 1.创建一张表class gender_info(models.Model):name models.CharField(max_length32)password models.CharField(max_lengt…

webstorm配置console.log打印

一、设置面板 打开设置面板(windows 快捷键&#xff1a; ctrl alt s) &#xff0c;找到 编辑器 -> 实时模板 -> JavaScript -> log&#xff0c;点击log会出现对应的配置 二、模板文本 将下面这些模板文本粘贴进去 console.info("&#x1f680; ~ file:$file…

自然语言处理:Transformer与GPT

Transformer和GPT&#xff08;Generative Pre-trained Transformer&#xff09;是深度学习和自然语言处理&#xff08;NLP&#xff09;领域的两个重要概念&#xff0c;它们之间存在密切的关系但也有明显的不同。 1 基本概念 1.1 Transformer基本概念 Transformer是一种深度学…

Python matplotlib Linecollection() 函数用法

Python matplotlib Linecollection 函数用法 今天看到了这个函数的用法觉得很有意思&#xff0c;因为通常&#xff0c;如果我们想要在 matplotlib 中绘制多条线条&#xff0c;通常我们的做法是使用 for 循环绘制。然而当想要绘制的线条逐渐多起来的时候&#xff0c;使用 for 循…

如何在外部数据库中存储空间化表时使用Mapinfo_mapcatalog

开始创建地图目录表之前 您将使用EasyLoader在要使用的数据库中创建地图目录表。EasyLoader与MapInfo Pro一起安装。 &#xff08;工具“DBMS_Catalog”不再随MapInfo Professional 64位一起提供&#xff0c;因为它的功能可以在EasyLoader工具中找到。&#xff09; ​ 注&…

windows 查看防火墙设置命令使用方法

点击键盘上windows键&#xff0c;输入cmd&#xff0c;选择以管理员身份运行 输入下面命令查看使用说明 netsh advfirewall firewall add rule ? 发现显示不全&#xff0c;不方便看 可以输入下面命令&#xff0c;生成文件&#xff0c;方便查看 netsh advfirewall firewall ad…

人机交互——机器人形态

1.聊天机器人 2.任务型机器人 3.面向FAQ的问答机器人 4.面向KB的问答机器人

【C++】——多态性与模板(其二)

&#x1f383;个人专栏&#xff1a; &#x1f42c; 算法设计与分析&#xff1a;算法设计与分析_IT闫的博客-CSDN博客 &#x1f433;Java基础&#xff1a;Java基础_IT闫的博客-CSDN博客 &#x1f40b;c语言&#xff1a;c语言_IT闫的博客-CSDN博客 &#x1f41f;MySQL&#xff1a…