Flutter 学习之旅 之 flutter 在 Android 端进行简单的打开前后相机预览 / 拍照保存
目录
Flutter 学习之旅 之 flutter 在 Android 端进行简单的打开前后相机预览 / 拍照保存
一、简单介绍
Flutter 是一款开源的 UI 软件开发工具包,由 Google 开发和维护。它允许开发者使用一套代码同时构建跨平台的应用程序,包括移动设备(iOS 和 Android)、Web 和桌面平台(Windows、macOS 和 Linux)。
Flutter 使用 Dart 编程语言,它可以将代码编译为 ARM 或 Intel 机器代码以及 JavaScript,从而实现快速的性能。Flutter 提供了一个丰富的预置小部件库,开发者可以根据自己的需求灵活地控制每个像素,从而创建自定义的、适应性强的设计,这些设计在任何屏幕上都能呈现出色的外观和感觉。
二、简单介绍 camera
一个用于iOS、Android和Web的Flutter插件,允许访问设备摄像头。
三、安装 camera
1、直接运行命令
使用 Flutter:flutter pub add camera
使用 Flutter 安装权限管理插件:flutter pub add permission_handler
使用 Flutter 安装图片保存插件:flutter pub add permission_handler
2、或者在 pubspec.yaml 添加
dependencies:
camera: ^0.11.1
permission_handler: ^11.4.0
saver_gallery: ^4.0.1
四、简单案例实现
1、这里使用 Android Studio 进行创建 Flutter 项目
2、创建一个 application 的 Flutter 项目
3、工程创建后如下
4、添加权限
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
5、编写代码实现打开相机,预览,拍照功能
6、连接设备,运行项目,简单效果如下
五、关键代码
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart' show getExternalStorageDirectory;
import 'package:path/path.dart' as path show join;
import 'package:permission_handler/permission_handler.dart';
// 应用入口点
Future<void> main() async {
// 确保 Flutter 的 WidgetsBinding 已初始化
WidgetsFlutterBinding.ensureInitialized();
// 请求存储权限
await requestPermissions();
// 获取设备上所有可用的摄像头列表
final cameras = await availableCameras();
runApp(
MaterialApp(
theme: ThemeData.dark(), // 使用暗色主题
home: TakePictureScreen(cameras: cameras), // 传入摄像头列表
),
);
}
// 请求存储权限的函数
Future<void> requestPermissions() async {
// 检查存储权限的状态
var status = await Permission.storage.status;
if (!status.isGranted) {
// 如果权限未开启,则请求权限
await Permission.storage.request();
}
}
// TakePictureScreen 页面
class TakePictureScreen extends StatefulWidget {
// 构造函数,传入摄像头列表
const TakePictureScreen({super.key, required this.cameras});
final List<CameraDescription> cameras;
@override
TakePictureScreenState createState() => TakePictureScreenState();
}
// TakePictureScreen 的状态管理
class TakePictureScreenState extends State<TakePictureScreen> {
late CameraController _controller; // 摄像头控制器
late Future<void> _initializeControllerFuture; // 初始化摄像头的 Future
int _selectedCameraIndex = 0; // 当前选中的摄像头索引
@override
void initState() {
super.initState();
// 初始化当前选中的摄像头
_initializeCamera(widget.cameras[_selectedCameraIndex]);
}
// 初始化摄像头的函数
void _initializeCamera(CameraDescription camera) {
_controller = CameraController(
camera, // 摄像头描述
ResolutionPreset.medium, // 设置分辨率
);
_initializeControllerFuture = _controller.initialize(); // 初始化摄像头
}
// 切换摄像头的函数
void _switchCamera() {
setState(() {
// 切换到下一个摄像头
_selectedCameraIndex = (_selectedCameraIndex + 1) % widget.cameras.length;
// 释放当前摄像头资源
_controller.dispose();
// 初始化新的摄像头
_initializeCamera(widget.cameras[_selectedCameraIndex]);
});
}
@override
void dispose() {
// 释放摄像头资源
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Take a picture')), // 页面标题
body: FutureBuilder<void>(
future: _initializeControllerFuture, // 监听摄像头初始化的 Future
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
// 如果摄像头初始化完成,显示摄像头预览
return CameraPreview(_controller);
} else {
// 如果摄像头初始化未完成,显示加载动画
return const Center(child: CircularProgressIndicator());
}
},
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
// 拍照按钮
FloatingActionButton(
onPressed: () async {
try {
await _initializeControllerFuture; // 确保摄像头已初始化
final image = await _controller.takePicture(); // 拍照
print("Image path: ${image.path}");
// 读取图片文件为字节数据
final file = File(image.path);
final imageBytes = await file.readAsBytes();
print("Image bytes length: ${imageBytes.length}");
// 手动保存图片到指定路径
final saveResult = await saveImageManually(imageBytes);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(saveResult)),
);
} catch (e) {
print("Error taking picture: $e");
}
},
child: const Icon(Icons.camera_alt), // 拍照图标
),
const SizedBox(height: 16),
// 切换摄像头按钮
FloatingActionButton(
onPressed: _switchCamera, // 切换摄像头
child: const Icon(Icons.switch_camera), // 切换摄像头图标
),
],
),
);
}
}
// 手动保存图片到指定路径的函数
Future<String> saveImageManually(Uint8List imageBytes) async {
// 获取外部存储路径
final directory = await getExternalStorageDirectory();
if (directory == null) {
throw Exception("Failed to get external storage directory.");
}
// 拼接目标路径
final picturesDir = path.join(directory.path, "Pictures", "CameraApp");
// 确保目标目录存在
await Directory(picturesDir).create(recursive: true);
// 生成文件名
final fileName = "camera_image_${DateTime.now().millisecondsSinceEpoch}.jpg";
final filePath = path.join(picturesDir, fileName);
// 写入文件
final file = File(filePath);
await file.writeAsBytes(imageBytes);
print("Image saved manually to: $filePath");
return "Image saved to: $filePath";
}
代码说明
代码结构清晰:每个函数和逻辑块都有详细的注释,解释了其功能和实现方式。
关键点解释:
如何初始化摄像头。
如何切换摄像头。
如何保存图片到指定路径。
错误处理:在关键操作中添加了错误处理逻辑,并打印了错误信息。
用户交互:通过
SnackBar
提示用户图片保存成功或失败。