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

Flutter中如何使用camera插件实现相机预览宽高比遮罩层

Flutter 实现Instagram风格相机宽高比遮罩(基于camera插件)

核心思路是用层叠布局把相机预览、遮罩、操作UI按顺序堆叠,重点要解决两个问题:一是相机预览不能拉伸变形,二是遮罩要精准留出对应比例的透明拍摄区,不影响下层手势操作。

实现要点

  • 基础依赖:先完成camera插件的基础配置,完成相机权限申请、相机控制器初始化,确保能正常调出相机预览
  • 层叠结构:用Stack作为根布局,从下到上依次放「适配缩放的相机预览」「宽高比遮罩层」「拍摄操作UI」
  • 遮罩实现:半透明遮挡层居中留出固定宽高比的透明区域,可按需给透明区加描边,还原Instagram的视觉效果
  • 手势穿透:给遮罩层加IgnorePointer,避免遮挡点击对焦、双指缩放等相机手势
  • 成片对齐:拍照后按设定的宽高比裁切原始照片,保证成片和预览看到的范围完全一致

核心实现代码

import 'package:camera/camera.dart';
import 'package:flutter/material.dart';

class InstagramCameraPage extends StatefulWidget {
  const InstagramCameraPage({super.key});

  @override
  State<InstagramCameraPage> createState() => _InstagramCameraPageState();
}

class _InstagramCameraPageState extends State<InstagramCameraPage> {
  late CameraController _cameraController;
  // 可切换的拍摄比例,Instagram常用1:1、4:5、9:16
  double _targetAspectRatio = 4 / 5;
  bool _isInit = false;

  @override
  void initState() {
    super.initState();
    _initCamera();
  }

  Future<void> _initCamera() async {
    final cameras = await availableCameras();
    _cameraController = CameraController(
      cameras.first,
      ResolutionPreset.high,
      enableAudio: false,
    );
    await _cameraController.initialize();
    if (!mounted) return;
    setState(() => _isInit = true);
  }

  @override
  void dispose() {
    _cameraController.dispose();
    super.dispose();
  }

  // 拍照逻辑,拍完按比例裁切即可
  Future<void> _takePhoto() async {
    final XFile photo = await _cameraController.takePicture();
    // 这里补充图片裁切、保存逻辑
  }

  @override
  Widget build(BuildContext context) {
    if (!_isInit) {
      return const Scaffold(
        backgroundColor: Colors.black,
        body: Center(child: CircularProgressIndicator(color: Colors.white)),
      );
    }

    final Size screenSize = MediaQuery.of(context).size;
    // 计算相机预览缩放值,解决预览拉伸问题
    final double previewScale =
        1 / (_cameraController.value.aspectRatio * screenSize.aspectRatio);

    return Scaffold(
      backgroundColor: Colors.black,
      body: Stack(
        fit: StackFit.expand,
        children: [
          // 底层:不变形的相机预览
          Transform.scale(
            scale: previewScale,
            alignment: Alignment.center,
            child: Center(child: CameraPreview(_cameraController)),
          ),
          // 中层:宽高比遮罩
          _buildRatioMask(screenSize),
          // 上层:操作UI
          Positioned(
            bottom: 50,
            left: 0,
            right: 0,
            child: Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                GestureDetector(
                  onTap: _takePhoto,
                  child: Container(
                    width: 72,
                    height: 72,
                    decoration: BoxDecoration(
                      shape: BoxShape.circle,
                      color: Colors.white,
                      border: Border.all(color: Colors.white30, width: 4),
                    ),
                  ),
                )
              ],
            ),
          )
        ],
      ),
    );
  }

  // 构建宽高比遮罩
  Widget _buildRatioMask(Size screenSize) {
    final double previewWidth = screenSize.width;
    final double previewHeight = previewWidth / _targetAspectRatio;
    // 计算上下遮罩的高度
    final double maskHeight = (screenSize.height - previewHeight) / 2;

    return IgnorePointer(
      // 透传手势,不影响相机对焦、缩放操作
      child: Column(
        children: [
          // 上部半透明遮罩
          Container(
            height: maskHeight,
            width: double.infinity,
            color: Colors.black.withOpacity(0.7),
          ),
          // 中间透明拍摄区,加白边还原Instagram效果
          AspectRatio(
            aspectRatio: _targetAspectRatio,
            child: Container(
              decoration: BoxDecoration(
                border: Border.all(color: Colors.white, width: 1),
              ),
            ),
          ),
          // 下部半透明遮罩
          Container(
            height: maskHeight,
            width: double.infinity,
            color: Colors.black.withOpacity(0.7),
          ),
        ],
      ),
    );
  }
}

效果参考

Instagram风格相机遮罩预览

扩展提示

  • 如果需要做拍摄比例切换功能,只需要把_targetAspectRatio改成可观察的状态变量,切换时调用setState即可实时更新遮罩
  • 横竖屏切换场景下,需要重新计算预览缩放值和遮罩尺寸,避免变形
  • 如果需要实现镂空圆角、异形遮罩效果,可以把遮罩层换成CustomPaint,通过路径组合绘制任意形状的镂空区

内容的提问来源于stack exchange,提问作者litt

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 14:18:17