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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 01:20:41