You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 09:07:02