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

Flutter如何实现从底部滑出且背景变暗的Overlay组件?

Overlay效果参考

实现思路

这个底部上滑+背景变暗的交互是典型的模态底部弹层,用框架自带组件就能实现,不需要引入第三方库,核心逻辑拆成三个部分:

  • 背景遮罩:半透明黑色蒙层,点击可触发关闭,弹出/关闭过程中透明度随动画进度同步变化,常规不透明度设0.5-0.7视觉效果最舒服
  • 内容容器:固定在屏幕底部,弹出时从屏幕下方向上滑动到预设位置,搭配easeOut类动画曲线过渡会更自然
  • 动画同步:遮罩的透明度动画、内容的位移动画用同一个控制器驱动,保证两个效果节奏完全一致,不会出现错位

快速落地(官方现成组件)

如果不需要做特殊交互定制,直接用官方封装的showModalBottomSheet方法就行,默认自带底部上滑动画、遮罩、点击遮罩/物理返回键关闭逻辑,代码量最少:

// 在需要触发弹窗的时机调用这个方法即可
void _triggerBottomPopup(BuildContext context) {
  showModalBottomSheet(
    context: context,
    backgroundColor: Colors.transparent, // 关掉默认的白色背景,方便自定义内容圆角等样式
    isScrollControlled: true, // 允许弹窗内容超过半屏、支持内部滚动
    barrierColor: Colors.black.withOpacity(0.6), // 自定义遮罩颜色和透明度
    builder: (context) {
      return Container(
        height: 420,
        decoration: const BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
        ),
        // 替换成你自己的弹窗业务内容
        child: const Center(child: Text("弹窗内容区域")),
      );
    },
  );
}

如果需要改动画时长、弹窗形状,直接在方法里传对应的参数配置就行,能覆盖80%的常规场景。

高度自定义实现

如果需要做跟手拖拽关闭、自定义动画插值、弹窗和页面内容联动这类特殊效果,可以基于Overlay手动实现,灵活度最高:
核心逻辑是手动往页面的Overlay栈里插入自定义的弹层层级,用动画控制器同时驱动遮罩透明度和内容位移,关闭时等反向动画执行完再移除Overlay节点,避免内容突兀消失。
封装好的工具类代码可以直接复用:

class BottomPopup {
  static OverlayEntry? _popupEntry;
  static AnimationController? _animController;

  /// 弹出底部弹层
  /// [content] 自定义弹层内容
  /// [barrierOpacity] 遮罩最高不透明度
  static Future show(
    BuildContext context, {
    required Widget content,
    double barrierOpacity = 0.6,
  }) async {
    // 先移除已存在的弹层,避免重复弹出
    close();
    _animController = AnimationController(
      vsync: Navigator.of(context),
      duration: const Duration(milliseconds: 260),
      reverseDuration: const Duration(milliseconds: 210),
    );

    _popupEntry = OverlayEntry(builder: (context) {
      return Material(
        color: Colors.transparent,
        child: Stack(
          children: [
            // 可点击关闭的遮罩层
            GestureDetector(
              behavior: HitTestBehavior.opaque,
              onTap: close,
              child: AnimatedBuilder(
                animation: _animController!,
                builder: (_, __) {
                  return Container(
                    color: Colors.black.withOpacity(barrierOpacity * _animController!.value),
                  );
                },
              ),
            ),
            // 底部上滑的内容层
            Align(
              alignment: Alignment.bottomCenter,
              child: SlideTransition(
                position: Tween<Offset>(
                  begin: const Offset(0, 1), // 初始位置完全在屏幕可视区域外下方
                  end: Offset.zero,
                ).animate(CurvedAnimation(
                  parent: _animController!,
                  curve: Curves.easeOutCubic,
                )),
                child: content,
              ),
            )
          ],
        ),
      );
    });

    Overlay.of(context).insert(_popupEntry!);
    _animController!.forward();
  }

  /// 关闭底部弹层
  static void close() {
    if (_popupEntry != null) {
      _animController?.reverse().then((_) {
        _popupEntry?.remove();
        _popupEntry = null;
        _animController?.dispose();
        _animController = null;
      });
    }
  }
}

调用的时候直接传自定义内容就行:

BottomPopup.show(
  context,
  content: Container(
    height: 400,
    decoration: const BoxDecoration(
      color: Colors.white,
      borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
    ),
    child: const Center(child: Text("自定义弹窗内容")),
  ),
);

如果要加跟手拖拽关闭的效果,只要给内容层加个GestureDetector监听垂直拖拽位移,把位移值绑定到动画控制器的value上即可,拓展性很强。

内容的提问来源于stack exchange,提问作者Jack .T

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 20:36:24