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
英文地址,請注明出處:http://en.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.查看服…