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 提示用户图片保存成功或失败。

相关推荐
API小爬虫20 分钟前
使用PHP爬虫获取淘宝App商品详情?
android·爬虫·php
一笑的小酒馆9 小时前
Android在ksp中简单使用Room
android
meimeiqian11 小时前
flutter android端抓包工具
android·flutter
Android技术之家11 小时前
谷歌决定终止开源Android以及对开发者的影响
android·开源
每次的天空12 小时前
Android Jetpack学习总结(源码级理解)
android·学习·android jetpack
木子庆五12 小时前
Android设计模式之代理模式
android·设计模式·代理模式
在雨季等你13 小时前
创业之旅 - 反思 - 整改 - 新的方向 - 诚邀
android
Long_poem13 小时前
【自学笔记】PHP语言基础知识点总览-持续更新
android·笔记·php
fatiaozhang952714 小时前
晶晨S905L3A(B)-安卓9.0-开启ADB和ROOT-支持IPTV6-支持外置游戏系统-支持多种无线芯片-支持救砖-完美通刷线刷固件包
android·游戏·adb·华为·电视盒子·机顶盒rom·魔百盒固件
行墨15 小时前
Kotlin语言的==与===比较操作
android