基于Flutter MethodChannel调用全功能相机的技术问询
解决方案
1. 调用全功能原生相机
系统默认的MediaStore.ACTION_IMAGE_CAPTURE通常唤起简化版拍照界面,要调用全功能原生相机,可通过调整Intent参数实现:
Kotlin代码修改
private fun dispatchTakePictureIntent(result: MethodChannel.Result) { // 唤起系统全功能相机(兼容多数厂商) val takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE).apply { // 添加参数提示启用全功能模式 putExtra("android.intent.extra.USE_FULL_SCREEN", true) putExtra("android.intent.extra.ALWAYS_USE_FULL_SCREEN", true) } // 可选:指定调用Google相机(仅适用于安装了Google Camera的设备) // takePictureIntent.setPackage("com.google.android.GoogleCamera") if (takePictureIntent.resolveActivity(packageManager) != null) { // 原有文件创建、启动相机逻辑保持不变 } }
2. 实现多张拍摄功能
系统默认相机仅支持单张拍摄,要实现多拍模式,最优方案是自定义原生相机页面,以下是核心实现步骤:
步骤0:添加CameraX依赖
在app/build.gradle的dependencies块中添加Android官方相机库依赖:
dependencies { // CameraX 核心依赖 implementation "androidx.camera:camera-core:1.3.0" implementation "androidx.camera:camera-camera2:1.3.0" implementation "androidx.camera:camera-view:1.3.0" implementation "androidx.camera:camera-lifecycle:1.3.0" }
步骤1:创建自定义相机Activity
新建CustomCameraActivity,实现相机预览、多拍、照片存储逻辑:
class CustomCameraActivity : AppCompatActivity() { private lateinit var cameraProvider: ProcessCameraProvider private lateinit var imageCapture: ImageCapture private val capturedImagePaths = mutableListOf<String>() private val CHANNEL = "samples.flutter.dev/camera" override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_custom_camera) // 初始化相机 val cameraProviderFuture = ProcessCameraProvider.getInstance(this) cameraProviderFuture.addListener({ cameraProvider = cameraProviderFuture.get() bindCameraUseCases() }, ContextCompat.getMainExecutor(this)) // 拍照按钮点击事件 findViewById<Button>(R.id.btn_capture).setOnClickListener { takePhoto() } // 完成按钮:返回所有照片路径并关闭相机 findViewById<Button>(R.id.btn_finish).setOnClickListener { val resultIntent = Intent().apply { putStringArrayListExtra("image_paths", ArrayList(capturedImagePaths)) } setResult(Activity.RESULT_OK, resultIntent) finish() } } private fun bindCameraUseCases() { val preview = Preview.Builder().build().also { it.setSurfaceProvider(findViewById<PreviewView>(R.id.preview_view).surfaceProvider) } imageCapture = ImageCapture.Builder() .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) .build() val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA try { cameraProvider.unbindAll() cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture) } catch(ex: Exception) { Log.e("CustomCamera", "相机绑定失败: ${ex.message}") } } private fun takePhoto() { val photoFile = createImageFile() ?: return val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build() imageCapture.takePicture( outputOptions, ContextCompat.getMainExecutor(this), object : ImageCapture.OnImageSavedCallback { override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) { val savedPath = photoFile.absolutePath capturedImagePaths.add(savedPath) Log.d("CustomCamera", "照片已保存: $savedPath") // 实时回传单张路径到Flutter MethodChannel(flutterEngine?.dartExecutor?.binaryMessenger ?: return, CHANNEL) .invokeMethod("onImageCaptured", savedPath) } override fun onError(exception: ImageCaptureException) { Log.e("CustomCamera", "拍照失败: ${exception.message}") } } ) } private fun createImageFile(): File? { return try { val storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES) File.createTempFile( "JPEG_${System.currentTimeMillis()}_", ".jpg", storageDir ) } catch (ex: IOException) { Log.e("CustomCamera", "创建文件失败: ${ex.message}") null } } }
步骤2:配置布局与权限
- 在
AndroidManifest.xml中添加权限与Activity声明:
<uses-permission android:name="android.permission.CAMERA" /> <uses-feature android:name="android.hardware.camera.any" /> <activity android:name=".CustomCameraActivity" />
- 创建
activity_custom_camera.xml布局:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <androidx.camera.view.PreviewView android:id="@+id/preview_view" android:layout_width="match_parent" android:layout_height="match_parent" /> <Button android:id="@+id/btn_capture" android:layout_width="72dp" android:layout_height="72dp" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" android:layout_marginBottom="32dp" android:background="@drawable/ic_camera_shutter" /> <Button android:id="@+id/btn_finish" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_alignParentEnd="true" android:layout_margin="16dp" android:text="完成" /> </RelativeLayout>
步骤3:修改MainActivity启动自定义相机
替换原有系统相机调用逻辑:
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) MethodChannel(flutterEngine!!.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result -> if (call.method == "openCamera") { // 启动自定义相机 val intent = Intent(this, CustomCameraActivity::class.java) startActivityForResult(intent, REQUEST_IMAGE_CAPTURE) } else { result.notImplemented() } } } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == Activity.RESULT_OK) { val imagePaths = data?.getStringArrayListExtra("image_paths") ?: emptyList() // 回传所有照片路径到Flutter MethodChannel(flutterEngine!!.dartExecutor.binaryMessenger, CHANNEL) .invokeMethod("onAllImagesCaptured", imagePaths) } }
3. Flutter端接收照片路径
修改Flutter代码,注册MethodChannel监听,实时接收照片路径:
class _MyHomePageState extends State<MyHomePage> { static const platform = MethodChannel('samples.flutter.dev/camera'); List<String> capturedImagePaths = []; @override void initState() { super.initState(); // 注册监听接收原生端消息 platform.setMethodCallHandler((call) async { switch (call.method) { case 'onImageCaptured': String path = call.arguments; setState(() { capturedImagePaths.add(path); }); print("单张照片路径: $path"); break; case 'onAllImagesCaptured': List<String> paths = List<String>.from(call.arguments); setState(() { capturedImagePaths = paths; }); print("所有照片路径: $paths"); break; } }); } Future<void> openCamera() async { bool isCameraGranted = await _requestPermission(Permission.camera); if (isCameraGranted) { try { await platform.invokeMethod('openCamera'); } on PlatformException catch (e) { print("打开相机失败: '${e.message}'."); } } else { print('权限被拒绝'); } } Future<bool> _requestPermission(Permission permission) async { if (await permission.isGranted) return true; var status = await permission.request(); return status == PermissionStatus.granted; } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( onPressed: openCamera, child: const Text('打开自定义相机'), ), const SizedBox(height: 20), const Text('已拍摄照片路径:'), Expanded( child: ListView.builder( itemCount: capturedImagePaths.length, itemBuilder: (context, index) { return ListTile( title: Text(capturedImagePaths[index]), ); }, ), ), ], ), ), ); } }
内容的提问来源于stack exchange,提问作者Kennedy Mwenda
相关产品推荐
相关产品推荐

