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: fluttercamera: ^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; // 定义一个 CameraControllerbool isRecording = false; // 定义一个标志位,表示是否正在录制视频void initState() {super.initState();// 初始化 CameraControllercontroller = CameraController(cameras[0], ResolutionPreset.max);controller.initialize().then((_) {if (!mounted) {return;}setState(() {});});}void dispose() {// 销毁 CameraControllercontroller.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.comimport '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.comclass CameraApp extends StatefulWidget {const CameraApp({super.key});State<CameraApp> createState() => _CameraAppState();
}class _CameraAppState extends State<CameraApp> {late CameraController controller; // 定义一个 CameraControllerbool isRecording = false; // 定义一个标志位,表示是否正在录制视频late Directory appDir; // 应用的目录void initState() {super.initState();// 初始化 CameraControllercontroller = 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.comimport '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.comclass 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();// 创建 CameraControllercontroller = CameraController(cameras[0], ResolutionPreset.max);// 初始化 CameraControllercontroller.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.comimport '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.comclass 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();// 创建 CameraControllercontroller = CameraController(cameras[0], ResolutionPreset.max);// 初始化 CameraControllercontroller.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.comimport '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.comclass 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: flutterpermission_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.mzph.cn/news/154227.shtml

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

相关文章

3D 纹理渲染如何帮助设计师有效、清晰地表达设计理念

在线工具推荐&#xff1a; 三维数字孪生场景工具 - GLTF/GLB在线编辑器 - Three.js AI自动纹理化开发 - YOLO 虚幻合成数据生成器 - 3D模型在线转换 - 3D模型预览图生成服务 定义 3D 渲染可视化及其用途 3D 可视化是一种艺术形式。这是一个机会。这是进步。借助 3D 纹理…

基于单片机电梯液晶显示防超重,防气体报警、防夹报警控制系统及源程序

一、系统方案 1、本设计采用51单片机作为主控器。 2、液晶显示楼层。 3、防超重&#xff0c;防气体报警、防夹报警。 二、硬件设计 原理图如下&#xff1a; 三、单片机软件设计 1、首先是系统初始化 /lcd1602初始化设置*/ void init_1602() //lcd1602初始化设置 { write_co…

亚马逊出口电热毯日本PSE认证需要什么资料解析

电热毯出口日本需要办理PSE认证&#xff0c;电热毯&#xff0c;又名电褥&#xff0c;是一种接触式电暖器具。 PSE认证介绍是日本强制性认证&#xff0c;包含安全及EMI&#xff0c;用以证明电子电气等产品符合日期电气用品安全法或国际IEC标准的要求。日本电气用品安全法规定&am…

MyBatis框架——Mybatis操作数据库之简单的insert操作的实现

入门_MyBatis中文网https://mybatis.net.cn/getting-started.html一些配置文件的模板可以从mybatis的官网中找到。 一、MyBatis操作数据库步骤 1、读取MyBatis配置文件mybatis-config.xml。 mybatis-config.xml作为mybatis的全局配置文件&#xff0c;配置MyBatis的运行环境等…

【Java 进阶篇】JQuery 遍历 —— 无尽可能性的 `each` 之旅

在前端的征途中&#xff0c;操作元素是开发者不可避免的任务之一。而在 JQuery 中&#xff0c;each 方法则是处理这个任务的得力助手。本文将深入探讨 each 方法的奇妙之处&#xff0c;以及它与原生的 for...of 循环的关系&#xff0c;带你领略无尽可能性的遍历之旅。 起步&am…

2023APMCM亚太杯/小美赛数学建模竞赛优秀论文模板分享

一、模板介绍 二、注意事项 将论文划分小节时&#xff0c;应避免在小节中出现大段的文字叙述&#xff0c;这样的叙述会妨碍评委在浏览论文时掌握论文的要点。重要的句子&#xff0c;包括首次定义的概念&#xff0c;用黑体书写。 重要的数学公式应另起新行单独列出。建模所用的…

微信小程序会议OA-登录获取手机号流程登录-小程序导入微信小程序SDK(从微信小程序和会议OA登录获取手机号到登录小程序导入微信小程序SDK)

目录 获取用户昵称头像和昵称 wx.getUserProfile bindgetuserinfo 登录过程 登录-小程序 wx.checkSession wx.login wx.request 后台 准备数据表 反向生成工具生成 准备封装前端传过来的数据 小程序服器配置 导入微信小程序SDK application.yml WxProperties …

HarmonyOS从基础到实战-高性能华为在线答题元服务

最近看到美团、新浪、去哪儿多家互联网企业启动鸿蒙原生应用开发&#xff0c;这个HarmonyOS NEXT越来越引人关注。奈何当前不面向个人开发者开放&#xff0c;但是我们可以尝试下鸿蒙新的应用形态——元服务的开发。 元服务是基于HarmonyOS提供的一种面向未来的服务提供方式&…

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

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

湖科大计网:传输层

一、传输层概述 一、基本概念 传输层是端到端的协议。 因特网的两种不同的传输层协议&#xff1a; TCP&#xff1a;面向连接 UDP&#xff1a;无连接 我们在学习的过程中&#xff0c;只需要关注传输层之间的通信&#xff0c;不需要关注传输层协议数据是经过路由器转发至目的网络…

一篇文章让你彻底了解Java算法「十大经典排序算法」

✍️作者简介&#xff1a;码农小北&#xff08;专注于Android、Web、TCP/IP等技术方向&#xff09; &#x1f433;博客主页&#xff1a; 开源中国、稀土掘金、51cto博客、博客园、知乎、简书、慕课网、CSDN &#x1f514;如果文章对您有一定的帮助请&#x1f449;关注✨、点赞&…

Redis篇---第十二篇

系列文章目录 文章目录 系列文章目录前言一、Memcache与Redis的区别都有哪些?二、单线程的redis为什么这么快三、redis的数据类型,以及每种数据类型的使用场景前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇…

你真的了解 Cookie 和 Session 吗?

文章目录 Cookie 和 Session总结 Cookie 和 Session cookie HTTP cookie&#xff08;web cookie、browser cookie&#xff09;是服务器发送给用户 web 浏览器的一小段数据。浏览器可能会存储 cookie&#xff0c;并在以后的请求中将其发送回同一台服务器。通常&#xff0c;HTTP …

【OpenGauss源码学习 —— 列存储(ColumnTableSample)】

执行算子&#xff08;ColumnTableSample&#xff09; 概述ColumnTableSample 类ColumnTableSample::ColumnTableSample 构造函数ColumnTableSample::~ColumnTableSample 析构函数ExecCStoreScan 函数ColumnTableSample::scanVecSample 函数ColumnTableSample::getMaxOffset 函数…

pikachu靶场-暴力破解攻略

pikachu暴力破解 基于表单的暴力破解 抓包发送到intruder 添加两个变量 下图攻击模式需要选择cluster bomb 用户名处添加几个常见的用户名 密码处则添加密码字典 如图可见有一条密码已经爆出 登录成功 验证码绕过(on server) 输入验证码后提交 抓包 然后发送到repeater先…

vue中data属性为什么是一个函数?

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;Vue篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来vue篇专栏内容:vue-data属性 目录 为什么data属性是一个函数而不是一个对象&#xff1f; 一、实例和组件定义dat…

解锁数据库运维秘籍:掌握AntDB-T动态共享内存,提升进程间通信效率

动态共享内存是AntDB数据库通信的重要手段&#xff0c;本文主要阐述AntDB-T数据库动态共享内存的实现原理、实现方式与使用方法。 AntDB-T数据库是一款企业级通用分布式关系型数据库&#xff0c;其数据库内核是基于进程模型实现的&#xff0c;因此进程间通信&#xff08;IPC&am…

Appium移动自动化测试—如何安装Appium

前言 Appium 自动化测试是很早之前就想学习和研究的技术了&#xff0c;可是一直抽不出一块完整的时间来做这件事儿。现在终于有了。 反观各种互联网的招聘移动测试成了主流&#xff0c;如果再不去学习移动自动化测试技术将会被淘汰。 web自动化测试的路线是这样的&#xff1…

基于单片机的公共场所马桶设计(论文+源码)

1.系统设计 本课题为公共场所的马桶设计&#xff0c;其整个系统架构如图2.1所示&#xff0c;其采用STC89C52单片机为核心控制器&#xff0c;结合HC-SR04人体检测模块&#xff0c;压力传感器&#xff0c;LCD1602液晶&#xff0c;蜂鸣器&#xff0c;L298驱动电路等构成整个系统&…

1445 雉兔同笼

Tint(input()) for i in range(T):s input().split()head int(s[0])foot int(s[1])rabbitfoot/2-headchicken2*head-foot/2if rabbit>0 and chicken>0 and rabbit.is_integer():print(int(chicken),int(rabbit))else:print(-1)