Flutter实现Android原生相机拍照


方法1:使用Flutter的camera插件(完整实现)

1. 完整依赖与权限配置
# pubspec.yaml
dependencies:flutter:sdk: fluttercamera: ^0.10.5+2path_provider: ^2.0.15 # 用于获取存储路径path: ^1.8.3           # 用于路径操作permission_handler: ^10.4.0 # 权限处理
2. AndroidManifest.xml 配置
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /><!-- 在<application>标签内添加 -->
<providerandroid:name="androidx.core.content.FileProvider"android:authorities="${applicationId}.fileprovider"android:exported="false"android:grantUriPermissions="true"><meta-dataandroid:name="android.support.FILE_PROVIDER_PATHS"android:resource="@xml/file_paths" />
</provider>
3. 创建文件路径配置 (res/xml/file_paths.xml)
<?xml version="1.0" encoding="utf-8"?>
<paths><external-path name="my_images" path="Android/data/${applicationId}/files/Pictures" />
</paths>
4. Flutter端完整代码
import 'dart:async';
import 'dart:io';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';class CameraScreen extends StatefulWidget {_CameraScreenState createState() => _CameraScreenState();
}class _CameraScreenState extends State<CameraScreen> {late CameraController _controller;late Future<void> _initializeControllerFuture;bool _isCameraReady = false;String? _lastImagePath;void initState() {super.initState();_setupCamera();}Future<void> _setupCamera() async {// 检查并请求权限final cameraStatus = await Permission.camera.status;final storageStatus = await Permission.storage.status;if (!cameraStatus.isGranted || !storageStatus.isGranted) {final results = await [Permission.camera,Permission.storage,].request();if (!results[Permission.camera]!.isGranted || !results[Permission.storage]!.isGranted) {return;}}// 获取可用相机final cameras = await availableCameras();final firstCamera = cameras.firstWhere((camera) => camera.lensDirection == CameraLensDirection.back,orElse: () => cameras.first,);// 初始化控制器_controller = CameraController(firstCamera,ResolutionPreset.high,enableAudio: false,imageFormatGroup: ImageFormatGroup.jpeg,);_initializeControllerFuture = _controller.initialize().then((_) {if (!mounted) return;setState(() => _isCameraReady = true);});}Future<String> _takePicture() async {if (!_isCameraReady) throw 'Camera not ready';final Directory appDir = await getApplicationDocumentsDirectory();final String fileName = '${DateTime.now().millisecondsSinceEpoch}.jpg';final String savePath = join(appDir.path, fileName);try {final XFile image = await _controller.takePicture();final File savedImage = await File(image.path).copy(savePath);return savedImage.path;} on CameraException catch (e) {throw 'Camera error: ${e.description}';}}void dispose() {_controller.dispose();super.dispose();}Widget build(BuildContext context) {return Scaffold(body: FutureBuilder<void>(future: _initializeControllerFuture,builder: (context, snapshot) {if (snapshot.connectionState == ConnectionState.done) {return Stack(children: [CameraPreview(_controller),Positioned(bottom: 30,left: 0,right: 0,child: FloatingActionButton(onPressed: () async {try {final path = await _takePicture();setState(() => _lastImagePath = path);print('Image saved to: $path');} catch (e) {ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Error: $e')),);}},child: Icon(Icons.camera),),)],);} else {return Center(child: CircularProgressIndicator());}},),);}
}

方法2:通过平台通道调用原生相机(完整实现)

Flutter端完整代码
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';class NativeCameraScreen extends StatefulWidget {_NativeCameraScreenState createState() => _NativeCameraScreenState();
}class _NativeCameraScreenState extends State<NativeCameraScreen> {static const platform = MethodChannel('com.example/camera_channel');String? _imagePath;Future<void> _takePhoto() async {try {final String? path = await platform.invokeMethod('takePhoto');if (path != null) {setState(() => _imagePath = path);print('Photo path: $path');}} on PlatformException catch (e) {print("Failed to take photo: '${e.message}'.");}}Widget build(BuildContext context) {return Scaffold(body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: [if (_imagePath != null)Image.file(File(_imagePath!), height: 300),ElevatedButton(onPressed: _takePhoto,child: Text('Take Photo'),),],),),);}
}
Android端完整实现 (Kotlin)
// MainActivity.kt
package com.example.your_app_nameimport android.Manifest
import android.app.Activity
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore
import androidx.annotation.NonNull
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import java.io.File
import java.io.IOException
import java.text.SimpleDateFormat
import java.util.*class MainActivity : FlutterActivity() {private val CHANNEL = "com.example/camera_channel"private var pendingResult: MethodChannel.Result? = nullprivate var currentPhotoPath: String? = nullprivate val REQUEST_IMAGE_CAPTURE = 1private val REQUEST_CAMERA_PERMISSION = 2override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {super.configureFlutterEngine(flutterEngine)MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->when (call.method) {"takePhoto" -> {pendingResult = resultcheckCameraPermission()}else -> result.notImplemented()}}}private fun checkCameraPermission() {if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {ActivityCompat.requestPermissions(this,arrayOf(Manifest.permission.CAMERA),REQUEST_CAMERA_PERMISSION)} else {dispatchTakePictureIntent()}}private fun dispatchTakePictureIntent() {Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent ->takePictureIntent.resolveActivity(packageManager)?.also {val photoFile: File? = try {createImageFile()} catch (ex: IOException) {pendingResult?.error("FILE_ERROR", ex.message, null)null}photoFile?.also {val photoURI: Uri = FileProvider.getUriForFile(this,"${BuildConfig.APPLICATION_ID}.fileprovider",it)takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE)}}}}@Throws(IOException::class)private fun createImageFile(): File {val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())val storageDir: File? = getExternalFilesDir("Pictures")return File.createTempFile("JPEG_${timeStamp}_",".jpg",storageDir).apply {currentPhotoPath = absolutePath}}override fun onRequestPermissionsResult(requestCode: Int,permissions: Array<out String>,grantResults: IntArray) {super.onRequestPermissionsResult(requestCode, permissions, grantResults)if (requestCode == REQUEST_CAMERA_PERMISSION) {if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {dispatchTakePictureIntent()} else {pendingResult?.error("PERMISSION_DENIED", "Camera permission denied", null)}}}override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {super.onActivityResult(requestCode, resultCode, data)if (requestCode == REQUEST_IMAGE_CAPTURE) {when (resultCode) {Activity.RESULT_OK -> {pendingResult?.success(currentPhotoPath)}Activity.RESULT_CANCELED -> {pendingResult?.error("CANCELLED", "User cancelled photo", null)}else -> {pendingResult?.error("CAPTURE_FAILED", "Image capture failed", null)}}pendingResult = null}}
}

关键问题解决方案

1. 文件存储问题(Android 10+适配)
// 在AndroidManifest.xml中添加
<application...android:requestLegacyExternalStorage="true" // 临时解决方案>

或使用MediaStore API(推荐):

private fun saveImageToGallery(context: Context, file: File) {val contentValues = ContentValues().apply {put(MediaStore.Images.Media.DISPLAY_NAME, file.name)put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES)}val resolver = context.contentResolverval uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)uri?.let {resolver.openOutputStream(it).use { output ->FileInputStream(file).use { input ->input.copyTo(output!!)}}}
}
2. 相机方向问题

在Flutter端处理相机方向:


void didChangeAppLifecycleState(AppLifecycleState state) {if (state == AppLifecycleState.resumed) {_controller?.initialize().then((_) {if (!mounted) return;setState(() {});});}
}// 监听设备方向
_controller.setOrientation(orientation);
3. 内存泄漏预防

void dispose() {_controller?.dispose();super.dispose();
}
4. 异常处理最佳实践
try {// 相机操作
} on CameraException catch (e) {if (e.code == 'CameraAccessDenied') {// 处理权限问题} else {// 其他相机错误}
} on PlatformException catch (e) {// 平台通道错误
} catch (e) {// 通用错误
}

两种方法对比

特性camera插件平台通道
开发难度★☆☆ (简单)★★★ (复杂)
跨平台支持需要单独实现iOS
功能控制中等完全控制
性能较好最优
依赖大小较大较小
定制能力有限无限
维护成本低 (官方维护)高 (需自行维护)

推荐方案选择

  1. 大多数情况:使用camera插件

    • 官方维护
    • 跨平台支持
    • 减少平台特定代码
  2. 需要高级功能时:使用平台通道

    • 需要特殊相机功能(HDR、手动对焦等)
    • 需要深度集成设备硬件
    • 需要完全控制图像处理流程
  3. 混合方案

    // 使用camera插件获取图像流
    final CameraImage image = await _controller.startImageStream((image) {// 处理实时图像数据
    });// 通过平台通道调用原生高级功能
    final hdrEnabled = await platform.invokeMethod('enableHDR');
    

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

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

相关文章

记录几个SystemVerilog的语法——随机

1. 随机稳定性(random stability)随机稳定性是指每个线程(thread)或对象(object)的random number generator(RNG)是私有的&#xff0c;一个线程返回的随机值序列与其他线程或对象的RNG是无关的。随机稳定性适用于以下情况&#xff1a;系统随机方法调用&#xff1a;$urandom()和…

初识 docker [下] 项目部署

项目部署Dockerfile构建镜像DockerCompose基本语法基础命令项目部署 前面我们一直在使用别人准备好的镜像&#xff0c;那如果我要部署一个Java项目&#xff0c;把它打包为一个镜像该怎么做呢&#xff1f; …省略一万字 站在巨人的肩膀上更适合我们普通人,所以直接介绍两种简单…

Android15广播ANR的源码流程分析

Android15的广播ANR源码流程跟了下实际代码的流程&#xff0c;大概如下哈&#xff1a;App.sendBroadcast() // 应用发起广播→ AMS.broadcastIntentWithFeature() // 通过Binder IPC进入system_server进程→ AMS.broadcastIntentLocked() // 权限校验广播分类&#xff08;前…

密码学中的概率论与统计学:从频率分析到现代密码攻击

在密码学的攻防博弈中&#xff0c;概率论与统计学始终是破解密码的“利器”。从古典密码时期通过字母频率推测凯撒密码的密钥&#xff0c;到现代利用线性偏差破解DES的线性密码分析&#xff0c;再到侧信道攻击中通过功耗数据的统计特性还原密钥&#xff0c;统计思维贯穿了密码分…

力扣刷题977——有序数组的平方

977. 有序数组的平方 题目&#xff1a; 给你一个按 非递减顺序 排序的整数数组 nums&#xff0c;返回 每个数字的平方 组成的新数组&#xff0c;要求也按 非递减顺序 排序。示例 1&#xff1a; 输入&#xff1a;nums [-4,-1,0,3,10] 输出&#xff1a;[0,1,9,16,100] 解释&…

应用加速游戏盾的安全作用

在数字娱乐产业蓬勃发展的今天&#xff0c;游戏已从单纯的娱乐工具演变为连接全球数十亿用户的社交平台与文化载体。然而&#xff0c;伴随游戏市场的指数级增长&#xff0c;网络攻击的频率与复杂性也呈爆发式上升。从DDoS攻击导致服务器瘫痪&#xff0c;到外挂程序破坏公平竞技…

linux安装zsh,oh-my-zsh,配置zsh主题及插件的方法

这是一份非常详细的指南&#xff0c;带你一步步在 Linux 系统中安装 Zsh、配置主题和安装插件。 Zsh&#xff08;Z Shell&#xff09;是一个功能强大的 Shell&#xff0c;相比于大多数 Linux 发行版默认的 Bash&#xff0c;它提供了更强的自定义能力、更智能的自动补全、更漂亮…

【设计模式系列】策略模式vs模板模式

策略模式是什么&#xff1f;如何定义并封装一系列算法策略模式 (Strategy Pattern)模板模式 (Template Pattern)模板模式与策略模式的深度对比与区分混合使用两种模式的场景策略模式 (Strategy Pattern) 应用场景&#xff1a;当需要根据不同条件选择不同算法或行为时&#xff…

aigc(1.1) opensora-2.0

open sora-2.0相关链接: arxiv链接 huggingface页面 HunyuanVideo VAE open sora2.0的VAE模型复用了HunyuanVideo的3D VAE,HunyuanVideo的arxiv链接。下图来自论文,可见VAE是一个因果注意力的3D结构。在配图左侧,视频会被编码为video token序列,而在配图右侧,去噪的vide…

Linux驱动21 --- FFMPEG 音频 API

目录 一、FFMPEG 音频 API 1.1 解码步骤 创建核心上下文指针 打开输入流 获取输入流 获取解码器 初始化解码器 创建输入流指针 创建输出流指针 初始化 SDL 配置音频参数 打开音频设备 获取一帧数据 发送给解码器 从解码器获取数据 开辟数据空间 初始化内存 音频重采样…

《计算机“十万个为什么”》之 [特殊字符] 序列化与反序列化:数据打包的奇妙之旅 ✈️

《计算机“十万个为什么”》之 &#x1f4e6; 序列化与反序列化&#xff1a;数据打包的奇妙之旅 ✈️欢迎来到计算机“十万个为什么”系列&#xff01; 本文将以「序列化与反序列化」为主题&#xff0c;深入探讨计算机世界中数据的打包与解包过程。 让我们一起解开数据的神秘面…

机器学习与深度学习评价指标

机器学习与深度学习评价指标完全指南 📊 为什么需要评价指标? 想象你是一位医生,需要判断一个诊断模型的好坏。如果模型说"这个病人有癌症",你需要知道: 这个判断有多准确? 会不会漏掉真正的癌症患者? 会不会误诊健康的人? 评价指标就像是给AI模型打分的&…

Hugging Face-环境配置

打开anaconda promptconda activate pytorchpip install -i https://pypi.tuna.tsinghua.edu.cn/simple transformers datasets tokenizerspycharm找到pytorch下的python.exe#将模型下载到本地调用 from transformers import AutoModelForCausalLM,AutoTokenizer#将模型和分词工…

cnn中池化层作用

一、池化层概述 在卷积神经网络中&#xff0c;池化层是核心组件之一&#xff0c;主要作用是逐步降低特征图的空间尺寸即宽和高&#xff0c;从而减少计算量、控制过拟合并增强模型的鲁棒性。 核心作用 降维与减少计算量 压缩特征图的尺寸&#xff0c;显著减少后续层的参数数量和…

写一个音乐爬虫

今天我们写一个网易云音乐的爬虫&#xff0c;爬取网易云音乐热歌榜音乐链接并下载&#xff0c;这里用到了之前引用的BeautifulSoup和requests。 BeautifulSoup是一个Python库&#xff0c;用于从HTML和XML文件中提取数据。它提供了一种简单的方式来遍历文档树和搜索文档树中的元…

战斗公式和伤害走配置文件

故事背景&#xff0c;上次属性计算用的配置&#xff0c;这次伤害计算也走配置&#xff0c;下面是测试代码和测试数据local formulas {[100001]{id 100001,name "基础伤害",formula "function (self,tag,ishit,iscritial,counterratio)\n if ishit1 then\n …

线性代数 上

文章目录线性代数知识整理一、求行列式1、 套公式2、利用性质&#xff0c;化为可套公式3、抽象行列式4、抽象向量二、代数余子式的线性组合三、求AnA^nAn四、证明A可逆五、求A的逆1、定义法2、初等变换3、公式六、求秩七、线性表示的判定八、线性无关九、求极大线性无关组十、等…

红帽AI推理服务器三大特点

生成式人工智能&#xff08;Gen AI&#xff09;的迅猛发展&#xff0c;对大型语言模型&#xff08;LLM&#xff09;的部署提出了更高的性能、灵活性和效率要求。无论部署在何种环境中&#xff0c;红帽AI推理服务器都为用户提供经过强化并获得官方支持的vLLM发行版&#xff0c;配…

开始记录一步步学习pcl

安装参考&#xff0c;大神写的非常详细&#xff0c;一步到位 https://blog.csdn.net/qq_36812406/article/details/144307648?ops_request_misc%257B%2522request%255Fid%2522%253A%25220e215e6ac266b90ded12ed6b2eab1257%2522%252C%2522scm%2522%253A%252220140713.13010233…

Linux系统Centos7 安装mysql5.7教程 和mysql的简单指令

目录 一. 安装 MySQL 官方 Yum 仓库 二. 安装 MySQL 5.7 1.查看可用的mysql版本仓库 2.启用MySql5.7仓库 3.禁用更高版本的仓库&#xff08;可选&#xff09; 4.导入 MySQL GPG 公钥 5.安装MySql5.7 三. 启动 MySQL 服务 1.启动 MySQL 服务 2. 设置开机自启 3.查看服…