Flutter - 原生交互 - 相机Camera - 02

环境

Flutter 3.29

macOS Sequoia 15.4.1

Xcode 16.3

集成

  1. 创建一个带有 State 类的 StatefulWidget 组件
  2. 添加一个变量到 State 类来存放 CameraController
  3. 添加另外一个变量到 State 类中来存放 CameraController.initialize() 返回的 Future
  4. 在 initState() 方法中创建并初始化控制器
  5. 在 dispose() 方法中销毁控制器

创建并初始化 CameraController

新建take_photo.dart文件,输入stf快捷创建一个有状态的Widget,命名为TakePictureScreen

dart 复制代码
class TakePictureScreen extends StatefulWidget {
  const TakePictureScreen({super.key, required this.cameras});
  /// 1.接收调用时传入的摄像头列表
  final List<CameraDescription> cameras;

  @override
  TakePictureScreenState createState() => TakePictureScreenState();
}

class TakePictureScreenState extends State<TakePictureScreen> {
    /// 2. 相机控制器
    late CameraController _controller;
    /// 3. Future类型存放CameraController初始化返回的Future
    late Future<void> _initializeControllerFuture;
}

在 initState 方法中创建并初始化控制器

dart 复制代码
class TakePictureScreenState extends State<TakePictureScreen> {
    /// 根据camera的原生代码,从availableCameras获取可用摄像头列表时,后置索引为0
    /**
    switch device.position {
        case .back:
          lensFacing = .back
        case .front:
          lensFacing = .front
        case .unspecified:
          lensFacing = .external
    ...
    reply.append(cameraDescription)
    */
    int frontCamera = 0; 
    ...
    @override
    void initState() {
        super.initState();
        /// 设置初始化后置摄像头
        _controller = CameraController(
          widget.cameras[frontCamera],
          /// 清晰度
          ResolutionPreset.medium,
        );
        _initializeControllerFuture = _controller.initialize();
    }
    ...

预览

使用CameraPreview类实现相机的预览

dart 复制代码
FutureBuilder<void>(
  future: _initializeControllerFuture, // initState方法中Controller返回的Future对象
  builder: (context, snapshot) {
    // Controller初始化完成
    if (snapshot.connectionState == ConnectionState.done) {
      /// 显示预览
      return CameraPreview(_controller);
    } else {
      // 不然显示一个Loading动画
      return const Center(child: CircularProgressIndicator());
    }
  },
),

使用 CameraController 拍照

使用CameraController的takePicture()方法实现拍照功能

dart 复制代码
class TakePictureScreenState extends State<TakePictureScreen> {
String photoPath = "";
...
Widget build(BuildContext context) {
...
onPressed: () async {
  try {
    /// 确认摄像头已初始化
    await _initializeControllerFuture;

    /// 拍照并返回图片
    final image = await _controller.takePicture();
    
    /// 检查是否已挂载
    if (!context.mounted) return;
    
    /// 可以持久化存储 
    
    /// 设置更新状态,将这次的照片缩略图显示在左下角的预览图中
    setState(() {
      photoPath = image.path;
    });
  } catch (e) {
    // If an error occurs, log the error to the console.
    // print(e);
  }
},
}
}

发送消息到原生方法

objc 复制代码
/// FLTCam.m
- (void)captureToFileWithCompletion:(void (^)(NSString *_Nullable,
                                              FlutterError *_Nullable))completion
{
    AVCapturePhotoSettings *settings = [AVCapturePhotoSettings photoSettings];
    // 对照片进行配置,比如是否高清,格式jpg还是heif,闪光灯,存储路径
    ...
    
    /// 照片存储后的回调操作,移除该照片的uniqueID
    __weak typeof(self) weakSelf = self;
  FLTSavePhotoDelegate *savePhotoDelegate = [[FLTSavePhotoDelegate alloc]
           initWithPath:path
                ioQueue:self.photoIOQueue
      completionHandler:^(NSString *_Nullable path, NSError *_Nullable error) {
        typeof(self) strongSelf = weakSelf;
        if (!strongSelf) return;
        dispatch_async(strongSelf.captureSessionQueue, ^{
          // cannot use the outter `strongSelf`
          typeof(self) strongSelf = weakSelf;
          if (!strongSelf) return;
          [strongSelf.inProgressSavePhotoDelegates removeObjectForKey:@(settings.uniqueID)];
        });

        if (error) {
          completion(nil, FlutterErrorFromNSError(error));
        } else {
          NSAssert(path, @"Path must not be nil if no error.");
          completion(path, nil);
        }
      }];
    ...
    /// 传入配置 + 回调代理对象 + 通知进行拍照
    [self.capturePhotoOutput capturePhotoWithSettings:settings delegate:savePhotoDelegate];
    
}

在 dispose 方法中销毁控制器

dart 复制代码
@override
void dispose() {
    _controller.dispose();
    super.dispose();
}

开启闪光灯

使用CameraController对象的setFlashMode方法

dart 复制代码
class TakePictureScreenState extends State<TakePictureScreen> {
    FlashMode _flashMode = FlashMode.always;
...
onPressed: () {
    /// 闪光灯不同配置下图标不一样,因此修改后触发build
    setState(() {
        if (_flashMode == FlashMode.always) {
            _flashMode = FlashMode.auto;
        } else if (_flashMode == FlashMode.auto) {
            _flashMode = FlashMode.off;
        } else if (_flashMode == FlashMode.off) {
        _flashMode = FlashMode.always;
        }
        _controller.setFlashMode(_flashMode);
    });
};
}

切换摄像头

通过更新CameraController对象的description参数来切换摄像头

dart 复制代码
 onPressed: () async {
    if (frontCamera == 1) {
      /// 重新创建CameraController并设置后置摄像头
      _controller = CameraController(
        widget.cameras[0],
        ResolutionPreset.medium,
      );
      /// 初始化完成后更新标记,刷新界面
      _controller.initialize().then((_) {
        if (mounted) {
          frontCamera = 0;
          setState(() {});
        }
      });
    } else {
      _controller = CameraController(
        widget.cameras[1],
        ResolutionPreset.medium,
      );
      _controller.initialize().then((_) {
        if (mounted) {
          frontCamera = 1;
          setState(() {});
        }
      });
    }
},

设置焦距

使用CameraController对象的setZoomLevel方法设置焦距

dart 复制代码
...
onPressed: () {
  setState(() {
    _controller.setZoomLevel(1);
    _isMinZoomLevel = true;
    minZoomLevelDesc = "0.5x";
  });
},

拍照后显示缩略图

使用Align设置缩略图靠左

dart 复制代码
SizedBox(
  width: MediaQuery.of(context).size.width,
  child: Stack(
    fit: StackFit.loose,
    children: [
      Align(
        alignment: Alignment(-0.8, 0),
        child: SizedBox(
          width: 60,
          height: 60,
          child:
              photoPath.isEmpty  // 相片为空,隐藏
                  ? Container()
                  : Image.file(File(photoPath)),
        ),
),

其它

装修结束,闲居在家

参考

  1. 使用 Camera 插件实现拍照功能
  2. Flutter------最详细Stack(堆叠布局)使用教程
  3. flutter 调用摄像头,前后镜头切换
相关推荐
月光下的丝瓜20 小时前
Flutter 国内安装指南
前端·flutter
恋猫de小郭3 天前
Amper 正式转正 Kotlin Toolchain ,Gradle 未来何去何从
android·前端·flutter
张风捷特烈3 天前
Flutter 类库大揭秘#02 | path_provider 各平台实现
前端·flutter
TT_Close4 天前
别劝退了!5秒搞定 Flutter 鸿蒙 FVM 起跑线
flutter·harmonyos·visual studio code
你听得到114 天前
用户说 App 卡,但说不清在哪?我把 Flutter 监控 SDK 升级成了链路观测工作台
前端·flutter·性能优化
stringwu6 天前
Flutter 开发必备:MVI 架构的高效实现指南
前端·flutter
程序员老刘7 天前
Flutter版本选择指南:3.44系列继续观望 | 2026年6月
flutter·ai编程·客户端
用户965597361908 天前
Provider vs Bloc vs GetX vs Riverpod:Flutter 状态管理方案怎么选?
flutter
恋猫de小郭8 天前
Flutter Patchwork,不用 Fork 改依赖包源码的第三方工具
android·前端·flutter
程序员老刘9 天前
跑分第一的编程大模型,我为啥不用?
flutter·ai编程·vibecoding