当前位置: 首页 > news >正文

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.dtcms.com/a/302029.html

相关文章:

  • 如何在技术世界中保持清醒和高效
  • iphone手机使用charles代理,chls.pro/ssl 后回车 提示浏览器打不开该网页
  • NI Ettus USRP X440 软件无线电
  • 免费 SSL 证书申请简明教程,让网站实现 HTTPS 访问
  • PyTorch 使用指南
  • 基于Spring Boot的审计日志自动化解决方案,结合SpEL表达式和AOP技术,实现操作轨迹自动记录,并满足GDPR合规要求
  • <七> CentOS 8 安装最新版本Docker
  • 从零开始的云计算生活——第三十七天,跬步千里,ansible之playbook
  • LWGJL教程(8)——基础知识
  • JavaScript手录-排序算法篇
  • UNet改进(26):UNet结合分层注意力机制的图像分割深度解析
  • socketpair函数详解
  • CHI - Transaction介绍 - 其他类型介绍
  • 图论(BFS)构造邻接表(运用队列实现搜索)
  • Java面试深度剖析:从JVM到云原生的技术演进
  • 10.若依的自定义注解 Log
  • 发布“悟能”具身智能平台,商汤让机器人像人一样和现实世界交互
  • GitLab 18.2 发布几十项与 DevSecOps 有关的功能,可升级体验【一】
  • RAGFlow系列(03):把知识库通过API方式共享给Dify等外部平台使用
  • WPS 将一个PPT里面的图片和文字导入到另一个PPT中
  • CSP-J 2022_第三题逻辑表达式
  • 面试官:详细说说Kafka rebalance 的策略以及具体过程
  • 中国计算机学会(CCF)推荐学术会议-B(数据库/数据挖掘/内容检索):WSDM 2026
  • Rust并发编程中的所有权挑战与解决方案:从实际项目看Clone策略的应用
  • vue3卡片垂直无限滚动
  • Android 中 TCP 协议的实战运用
  • 【JAVA安全-Fastjson系列】Fastjson 1.2.24 反序列化漏洞分析及测试环境构建【复习回顾】
  • 安宝特案例丨户外通信机房施工革新:AR+作业流技术破解行业难题
  • 安宝特案例丨AR+AI赋能轨道交通制造:破解人工装配难题的创新实践
  • AR技术赋能工业设备维护:效率与智能的飞跃