Flutter CameraAwesome包自定义底部拍摄按钮功能实现求助
解决Camera Awesome自定义拍摄按钮触发预览弹窗的问题
核心思路
要实现点击底部拍摄按钮直接弹出预览弹窗,必须放弃Camera Awesome的默认UI,改用自定义布局模式,完全接管相机UI的构建和按钮逻辑,这样就能在拍摄完成后立即触发弹窗,而不是依赖默认的预览缩略图点击。
具体实现步骤
1. 依赖与初始化
确保已引入camera_awesome包,初始化相机控制器:
import 'package:camera_awesome/camera_awesome_plugin.dart'; import 'package:camera_awesome/pigeon.dart'; import 'package:flutter/material.dart'; import 'dart:io'; class CustomCameraPage extends StatefulWidget { const CustomCameraPage({super.key}); @override State<CustomCameraPage> createState() => _CustomCameraPageState(); } class _CustomCameraPageState extends State<CustomCameraPage> { late CameraController _cameraController; MediaCapture? _latestCapture; // 存储最新拍摄的媒体(照片/视频) CaptureMode _currentMode = CaptureMode.photo; // 当前拍摄模式:拍照/录像 @override void initState() { super.initState(); _initCamera(); } Future<void> _initCamera() async { // 初始化相机,配置参数 _cameraController = CameraController( cameraPosition: CameraPosition.back, captureMode: _currentMode, imageFormat: ImageFormat.jpeg, ); await _cameraController.initialize(); setState(() {}); }
2. 构建自定义相机UI
使用CameraBuilder.custom()构建自定义布局,自己实现底部操作栏:
@override Widget build(BuildContext context) { if (!_cameraController.isInitialized) { return const Scaffold(body: Center(child: CircularProgressIndicator())); } return Scaffold( body: CameraBuilder.custom( controller: _cameraController, previewBuilder: (context, previewSize, previewRect) { // 相机预览区域 return CameraPreview(controller: _cameraController); }, bottomActionsBuilder: (context, state) { // 自定义底部操作栏 return SafeArea( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ // 切换拍照/录像模式按钮 IconButton( icon: Icon( _currentMode == CaptureMode.photo ? Icons.videocam : Icons.photo_camera, color: Colors.white, size: 30, ), onPressed: _toggleCaptureMode, ), // 中间的拍摄按钮 GestureDetector( onTap: _captureMedia, child: Container( width: 70, height: 70, decoration: BoxDecoration( color: Colors.white, shape: BoxShape.circle, border: Border.all(color: Colors.grey.shade300, width: 3), ), child: Center( child: Icon( _currentMode == CaptureMode.photo ? Icons.camera : (_cameraController.isRecordingVideo ? Icons.stop : Icons.radio_button_on), color: Colors.black, size: 35, ), ), ), ), // 闪光灯切换按钮 IconButton( icon: Icon( _cameraController.flashMode == FlashMode.auto ? Icons.flash_auto : Icons.flash_off, color: Colors.white, size: 30, ), onPressed: _toggleFlash, ), ], ), ), ); }, ), ); }
3. 实现拍摄与弹窗逻辑
拍摄完成后直接触发预览弹窗,处理重拍/确认逻辑:
// 切换拍照/录像模式 Future<void> _toggleCaptureMode() async { setState(() { _currentMode = _currentMode == CaptureMode.photo ? CaptureMode.video : CaptureMode.photo; }); await _cameraController.setCaptureMode(_currentMode); } // 切换闪光灯 Future<void> _toggleFlash() async { final newFlashMode = _cameraController.flashMode == FlashMode.auto ? FlashMode.off : FlashMode.auto; await _cameraController.setFlashMode(newFlashMode); setState(() {}); } // 拍摄照片/视频 Future<void> _captureMedia() async { try { if (_currentMode == CaptureMode.photo) { // 拍照逻辑 final photo = await _cameraController.takePicture(); setState(() { _latestCapture = photo; }); // 拍照完成立即弹弹窗 _showPreviewDialog(); } else { // 录像逻辑:第一次点击开始,第二次停止并弹弹窗 if (_cameraController.isRecordingVideo) { final video = await _cameraController.stopRecordingVideo(); setState(() { _latestCapture = video; }); _showPreviewDialog(); } else { await _cameraController.startRecordingVideo(); setState(() {}); } } } catch (e) { debugPrint("拍摄失败: $e"); } } // 显示预览弹窗 void _showPreviewDialog() { showDialog( context: context, barrierDismissible: false, builder: (context) => AlertDialog( backgroundColor: Colors.black, content: _latestCapture is ImageCapture ? Image.file(File((_latestCapture as ImageCapture).path)) : FutureBuilder( future: VideoPlayerController.file( File((_latestCapture as VideoCapture).path), ).initialize(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { return AspectRatio( aspectRatio: (snapshot.data as VideoPlayerController).value.aspectRatio, child: VideoPlayer(snapshot.data as VideoPlayerController), ); } return const CircularProgressIndicator(color: Colors.white); }, ), actions: [ // 重拍按钮 TextButton( onPressed: () { Navigator.pop(context); setState(() { _latestCapture = null; }); }, child: const Text("重拍", style: TextStyle(color: Colors.white)), ), // 确认按钮 TextButton( onPressed: () { Navigator.pop(context); // 处理确认后的逻辑:保存、上传等 if (_latestCapture is ImageCapture) { debugPrint("确认使用照片: ${(_latestCapture as ImageCapture).path}"); } else { debugPrint("确认使用视频: ${(_latestCapture as VideoCapture).path}"); } }, child: const Text("确认", style: TextStyle(color: Colors.blue)), ), ], ), ); } @override void dispose() { _cameraController.dispose(); super.dispose(); } }
关键注意点
- 必须使用
CameraBuilder.custom()才能完全自定义UI和按钮逻辑,默认UI的按钮事件是包内部封装的,无法直接修改。 - 录像模式下要区分开始/停止状态:第一次点击拍摄按钮启动录像,第二次点击停止并触发弹窗。
_latestCapture用来存储最新拍摄结果,弹窗直接基于该对象渲染预览内容。- 重拍时需要清空
_latestCapture,确保下次拍摄是全新内容。
内容的提问来源于stack exchange,提问作者Natan
相关产品推荐
相关产品推荐

