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

Flutter实现顶部滑入滑出的Overlay组件(无第三方包)

Flutter 实现顶部滑入滑出的 Overlay 组件(无第三方包)

你当前的代码已经实现了Overlay的显示和隐藏,但滑出动画未生效——原因是直接移除了OverlayEntry,没有给动画执行的机会,且OverlayEntry不会随组件setState自动重建。以下是基于Flutter原生API的修正方案:

完整实现代码

import 'package:flutter/material.dart';

class TopOverlayDemo extends StatefulWidget {
  const TopOverlayDemo({super.key, required this.title});

  final String title;

  @override
  State<TopOverlayDemo> createState() => _TopOverlayDemoState();
}

class _TopOverlayDemoState extends State<TopOverlayDemo> {
  OverlayEntry? _overlayEntry;
  bool _isOverlayVisible = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: const [
            Text('Overlay Example'),
            SizedBox(height: 12),
            ElevatedButton(
              onPressed: _toggleOverlay,
              child: Text('Press Me'),
            ),
          ],
        ),
      ),
    );
  }

  void _toggleOverlay() {
    if (_isOverlayVisible) {
      // 触发滑出动画
      setState(() => _isOverlayVisible = false);
      _overlayEntry?.markNeedsBuild();
      // 等待动画结束后移除Overlay
      Future.delayed(const Duration(milliseconds: 500), _hideOverlay);
    } else {
      setState(() => _isOverlayVisible = true);
      _showOverlay();
      // 3秒后自动触发滑出
      Future.delayed(const Duration(seconds: 3), () {
        if (_isOverlayVisible) {
          setState(() => _isOverlayVisible = false);
          _overlayEntry?.markNeedsBuild();
          Future.delayed(const Duration(milliseconds: 500), _hideOverlay);
        }
      });
    }
  }

  void _showOverlay() {
    if (_overlayEntry != null) return;

    _overlayEntry = OverlayEntry(
      builder: (context) => AnimatedPositioned(
        left: 0,
        right: 0,
        top: _isOverlayVisible ? 0 : -MediaQuery.of(context).size.height,
        duration: const Duration(milliseconds: 500),
        curve: Curves.easeInOut,
        child: _buildOverlayContent(),
      ),
    );

    Overlay.of(context)?.insert(_overlayEntry!);
  }

  void _hideOverlay() {
    _overlayEntry?.remove();
    _overlayEntry = null;
  }

  Widget _buildOverlayContent() {
    return Container(
      constraints: const BoxConstraints(maxHeight: 200),
      child: Material(
        color: const Color(0xff2D2B36).withOpacity(0.6),
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(12),
        ),
        child: const Padding(
          padding: EdgeInsets.all(16.0),
          child: Column(
            children: [
              Text(
                '这是顶部滑入的Overlay',
                style: TextStyle(color: Colors.white, fontSize: 18),
              ),
              SizedBox(height: 12),
              Text(
                '3秒后自动滑出',
                style: TextStyle(color: Colors.white70),
              ),
            ],
          ),
        ),
      ),
    );
  }

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

关键修改说明

  1. 修复动画触发逻辑:
    OverlayEntry的builder不会随组件setState自动重建,因此在_isOverlayVisible变化时,必须调用_overlayEntry?.markNeedsBuild()强制刷新Overlay内容,触发AnimatedPositioned的动画。

  2. 实现滑出动画再移除:
    不再直接调用_hideOverlay(),而是先更新_isOverlayVisible触发滑出动画,等待动画时长(500ms)后再移除OverlayEntry,确保动画完整执行。

  3. 避免重复创建OverlayEntry:
    在_showOverlay()中先判断_overlayEntry是否存在,防止重复创建导致的异常。

  4. 优化自动隐藏逻辑:
    点击按钮显示后,3秒后先触发滑出动画,动画结束后再清理Overlay资源,流程更合理。

  5. 添加dispose清理:
    在组件销毁时主动移除Overlay,避免内存泄漏。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 18:00:04