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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 13:41:32