Flutter 学习之旅 之 flutter 在 Android 端进行简单的打开前后相机预览 / 拍照保存

Flutter 学习之旅 之 flutter 在 Android 端进行简单的打开前后相机预览 / 拍照保存

目录

[Flutter 学习之旅 之 flutter 在 Android 端进行简单的打开前后相机预览 / 拍照保存](#Flutter 学习之旅 之 flutter 在 Android 端进行简单的打开前后相机预览 / 拍照保存)

一、简单介绍

[二、简单介绍 camera](#二、简单介绍 camera)

[三、安装 camera](#三、安装 camera)

四、简单案例实现

五、关键代码


一、简单介绍

Flutter 是一款开源的 UI 软件开发工具包,由 Google 开发和维护。它允许开发者使用一套代码同时构建跨平台的应用程序,包括移动设备(iOS 和 Android)、Web 和桌面平台(Windows、macOS 和 Linux)。

Flutter 使用 Dart 编程语言,它可以将代码编译为 ARM 或 Intel 机器代码以及 JavaScript,从而实现快速的性能。Flutter 提供了一个丰富的预置小部件库,开发者可以根据自己的需求灵活地控制每个像素,从而创建自定义的、适应性强的设计,这些设计在任何屏幕上都能呈现出色的外观和感觉。

二、简单介绍 camera

网址:camera | Flutter package

一个用于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 添加

Dart 复制代码
dependencies:
  camera: ^0.11.1
  permission_handler: ^11.4.0
  saver_gallery: ^4.0.1

四、简单案例实现

1、这里使用 Android Studio 进行创建 Flutter 项目

2、创建一个 application 的 Flutter 项目

3、工程创建后如下

4、添加权限

XML 复制代码
    <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、连接设备,运行项目,简单效果如下

五、关键代码

Dart 复制代码
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";
}

代码说明

  1. 代码结构清晰:每个函数和逻辑块都有详细的注释,解释了其功能和实现方式。

  2. 关键点解释

    • 如何初始化摄像头。

    • 如何切换摄像头。

    • 如何保存图片到指定路径。

  3. 错误处理:在关键操作中添加了错误处理逻辑,并打印了错误信息。

  4. 用户交互 :通过 SnackBar 提示用户图片保存成功或失败。

相关推荐
Kapaseker1 小时前
一杯美式搞懂 Any、Unit、Nothing
android·kotlin
黄林晴1 小时前
你的 Android App 还没接 AI?Gemini API 接入全攻略
android
恋猫de小郭11 小时前
2026 Flutter VS React Native ,同时在 AI 时代 VS Native 开发,你没见过的版本
android·前端·flutter
冬奇Lab12 小时前
PowerManagerService(上):电源状态与WakeLock管理
android·源码阅读
明君8799716 小时前
Flutter 如何给图片添加多行文字水印
前端·flutter
BoomHe17 小时前
Now in Android 架构模式全面分析
android·android jetpack
四眼肥鱼1 天前
flutter 利用flutter_libserialport 实现SQ800 串口通信
前端·flutter
二流小码农1 天前
鸿蒙开发:上传一张参考图片便可实现页面功能
android·ios·harmonyos
鹏程十八少1 天前
4.Android 30分钟手写一个简单版shadow, 从零理解shadow插件化零反射插件化原理
android·前端·面试
Kapaseker1 天前
一杯美式搞定 Kotlin 空安全
android·kotlin