方法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 |
功能控制 | 中等 | 完全控制 |
性能 | 較好 | 最優 |
依賴大小 | 較大 | 較小 |
定制能力 | 有限 | 無限 |
維護成本 | 低 (官方維護) | 高 (需自行維護) |
推薦方案選擇
-
大多數情況:使用
camera
插件- 官方維護
- 跨平臺支持
- 減少平臺特定代碼
-
需要高級功能時:使用平臺通道
- 需要特殊相機功能(HDR、手動對焦等)
- 需要深度集成設備硬件
- 需要完全控制圖像處理流程
-
混合方案:
// 使用camera插件獲取圖像流 final CameraImage image = await _controller.startImageStream((image) {// 處理實時圖像數據 });// 通過平臺通道調用原生高級功能 final hdrEnabled = await platform.invokeMethod('enableHDR');