如何在Flutter的AndroidView/UiKitView中实现相机缩放功能
相机缩放功能实现方案
要实现基于AndroidView和UiKitView的相机缩放功能,需要Flutter层传递缩放手势数据,并在Android/iOS原生层处理相机硬件的缩放逻辑,具体步骤如下:
1. Flutter层:监听缩放手势并传递给原生
在现有AndroidView/UiKitView外层包裹手势检测器,通过MethodChannel将缩放比例传递给原生端:
import 'package:flutter/gestures.dart'; import 'package:flutter/services.dart'; import 'package:flutter/foundation.dart'; // 定义与原生通信的MethodChannel final MethodChannel _cameraChannel = MethodChannel('camera_channel'); // 包裹相机视图的手势检测组件 GestureDetector( onScaleUpdate: (ScaleUpdateDetails details) { // 将相对缩放比例传递给原生(需在原生端维护当前缩放值) _cameraChannel.invokeMethod('setZoom', details.scale); }, child: Platform.isAndroid ? AndroidView( viewType: 'camera', layoutDirection: TextDirection.ltr, creationParams: creationParams, creationParamsCodec: StandardMessageCodec(), gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{ Factory<OneSequenceGestureRecognizer>( () => EagerGestureRecognizer(), ), }, ) : UiKitView( viewType: 'camera', layoutDirection: TextDirection.ltr, creationParams: creationParams, creationParamsCodec: StandardMessageCodec(), ), )
2. Android原生端:实现相机缩放逻辑
方案A:使用CameraX(推荐)
在自定义CameraView中接收Flutter传递的缩放指令,调用CameraX的API调整缩放比例:
import android.content.Context import android.util.AttributeSet import android.view.View import androidx.camera.core.Camera import androidx.camera.core.Preview import io.flutter.plugin.common.MethodChannel class CameraView(context: Context, attrs: AttributeSet?) : View(context, attrs) { private lateinit var preview: Preview private var currentZoomRatio = 1.0f private var maxZoomRatio = 1.0f // 初始化CameraX(省略相机绑定、预览层设置等基础代码) fun initCamera(camera: Camera) { preview = camera.cameraInfo.zoomState.value?.let { maxZoomRatio = it.maxZoomRatio currentZoomRatio = it.zoomRatio Preview.Builder().build() } ?: Preview.Builder().build() } // 绑定MethodChannel,接收缩放指令 fun setMethodChannel(channel: MethodChannel) { channel.setMethodCallHandler { call, result -> when (call.method) { "setZoom" -> { val scale = call.argument<Float>("scale") ?: 1.0f // 限制缩放范围在1.0到硬件支持的最大值之间 currentZoomRatio = (currentZoomRatio * scale).coerceIn(1.0f, maxZoomRatio) preview.zoomRatio = currentZoomRatio result.success(null) } else -> result.notImplemented() } } } } // 在PlatformViewFactory中关联MethodChannel class CameraViewFactory : PlatformViewFactory(StandardMessageCodec.INSTANCE) { override fun create(context: Context, viewId: Int, args: Any?): PlatformView { val cameraView = CameraView(context, null) val channel = MethodChannel(FlutterNativeView(context), "camera_channel") cameraView.setMethodChannel(channel) // 此处补充CameraX初始化逻辑,绑定相机到cameraView return cameraView } }
方案B:使用Camera2
如果使用Camera2 API,通过调整CaptureRequest的裁剪区域实现缩放:
private fun updateZoom(scale: Float) { val camera = cameraDevice ?: return val characteristics = cameraManager.getCameraCharacteristics(camera.id) val sensorSize = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE) ?: return val maxZoom = characteristics.get(CameraCharacteristics.SCALER_MAX_DIGITAL_ZOOM) ?: 1.0f currentZoom = (currentZoom * scale).coerceIn(1.0f, maxZoom) // 计算裁剪区域,实现缩放效果 val cropWidth = sensorSize.width() / currentZoom val cropHeight = sensorSize.height() / currentZoom val cropLeft = (sensorSize.width() - cropWidth) / 2 val cropTop = (sensorSize.height() - cropHeight) / 2 val cropRegion = android.graphics.Rect( cropLeft.toInt(), cropTop.toInt(), (cropLeft + cropWidth).toInt(), (cropTop + cropHeight).toInt() ) captureRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION, cropRegion) captureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null) }
3. iOS原生端:实现相机缩放逻辑
基于AVCaptureSession,通过调整AVCaptureDevice的videoZoomFactor实现缩放:
import UIKit import Flutter import AVFoundation class CameraView: UIView, FlutterPlatformView { private var captureSession: AVCaptureSession! private var captureDevice: AVCaptureDevice! private var currentZoomFactor: CGFloat = 1.0 init(frame: CGRect, viewIdentifier: Int64, arguments: Any?, binaryMessenger: FlutterBinaryMessenger) { super.init(frame: frame) setupCamera() // 绑定MethodChannel接收缩放指令 let channel = FlutterMethodChannel(name: "camera_channel", binaryMessenger: binaryMessenger) channel.setMethodCallHandler { [weak self] call, result in guard let self = self else { return } if call.method == "setZoom" { guard let scale = call.arguments as? CGFloat else { result.error("InvalidArgs", "Scale value is missing", nil) return } self.updateZoom(scale: scale) result.success(nil) } else { result.notImplemented() } } } // 初始化相机会话(省略权限申请等基础逻辑) private func setupCamera() { captureSession = AVCaptureSession() captureSession.sessionPreset = .high guard let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) else { return } captureDevice = device do { let input = try AVCaptureDeviceInput(device: device) if captureSession.canAddInput(input) { captureSession.addInput(input) } let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) previewLayer.frame = bounds previewLayer.videoGravity = .resizeAspectFill layer.addSublayer(previewLayer) captureSession.startRunning() } catch { print(error.localizedDescription) } } // 执行缩放操作 private func updateZoom(scale: CGFloat) { guard let device = captureDevice else { return } let maxZoom = device.activeFormat.videoMaxZoomFactor // 限制缩放范围 currentZoomFactor = min(max(currentZoomFactor * scale, 1.0), maxZoom) do { try device.lockForConfiguration() device.videoZoomFactor = currentZoomFactor device.unlockForConfiguration() } catch { print(error.localizedDescription) } } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } // 实现PlatformViewFactory class CameraViewFactory: NSObject, FlutterPlatformViewFactory { private var messenger: FlutterBinaryMessenger init(messenger: FlutterBinaryMessenger) { self.messenger = messenger super.init() } func create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) -> FlutterPlatformView { return CameraView(frame: frame, viewIdentifier: viewId, arguments: args, binaryMessenger: messenger) } func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol { return FlutterStandardMessageCodec.sharedInstance() } }
关键注意事项
- 缩放比例是相对值,需在原生端维护当前缩放值,避免每次手势触发后重置为初始值
- 必须限制缩放范围在
1.0到相机硬件支持的最大缩放倍数之间,防止超出硬件能力 - Android端使用CameraX时,需确保
zoomState已初始化,可通过监听cameraInfo.zoomState的变化获取最大缩放倍数
内容的提问来源于stack exchange,提问作者priyank apps
相关产品推荐
相关产品推荐

