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

如何使自定义Overlay在Hero组件过渡动画时显示于其Overlay之上?

解决Flutter Hero过渡时自定义Overlay被遮挡的问题

方案一:利用PostFrameCallback延迟插入按钮Overlay

在第二个路由的initState中,通过WidgetsBinding.instance.addPostFrameCallback延迟添加按钮的OverlayEntry,确保它在Hero的OverlayEntry之后插入到Navigator的Overlay中(Overlay中后插入的元素层级更高):

class SecondScreen extends StatefulWidget {
  @override
  _SecondScreenState createState() => _SecondScreenState();
}

class _SecondScreenState extends State<SecondScreen> {
  OverlayEntry? _buttonOverlay;

  @override
  void initState() {
    super.initState();
    // 等待当前帧渲染完成后插入Overlay,确保Hero的Overlay已存在
    WidgetsBinding.instance.addPostFrameCallback((_) {
      _showButtonOverlay();
    });
  }

  void _showButtonOverlay() {
    _buttonOverlay = OverlayEntry(
      builder: (context) => Positioned(
        top: MediaQuery.of(context).padding.top + 16,
        left: 16,
        child: IconButton(
          icon: Icon(Icons.arrow_back),
          color: Colors.white,
          onPressed: () async {
            // 返回前先移除Overlay
            _buttonOverlay?.remove();
            Navigator.pop(context);
          },
        ),
      ),
    );
    Navigator.of(context).overlay.insert(_buttonOverlay!);
  }

  @override
  void dispose() {
    _buttonOverlay?.remove();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Hero(
        tag: 'hero-tag',
        child: Container(
          width: double.infinity,
          height: double.infinity,
          color: Colors.blue,
        ),
      ),
    );
  }
}

方案二:自定义HeroController监听过渡状态

通过重写HeroController监听Hero过渡的开始和结束事件,在过渡开始时插入按钮Overlay,结束时移除:

  1. 自定义HeroController:
class CustomHeroController extends HeroController {
  final VoidCallback onTransitionStart;
  final VoidCallback onTransitionEnd;

  CustomHeroController({
    required this.onTransitionStart,
    required this.onTransitionEnd,
  });

  @override
  Animation<double> createAnimationForHero(
    Hero hero,
    HeroFlightDirection flightDirection,
    Animation<double> overallAnimation,
  ) {
    final animation = super.createAnimationForHero(hero, flightDirection, overallAnimation);
    animation.addStatusListener((status) {
      if (status == AnimationStatus.forward) {
        onTransitionStart();
      } else if (status == AnimationStatus.completed || status == AnimationStatus.dismissed) {
        onTransitionEnd();
      }
    });
    return animation;
  }
}
  1. 在MaterialApp中配置自定义HeroController,并全局管理按钮Overlay:
OverlayEntry? _globalButtonOverlay;
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: FirstScreen(),
      heroController: CustomHeroController(
        onTransitionStart: () {
          // 仅在进入第二个路由时显示Overlay
          if (Navigator.of(navigatorKey.currentContext!).canPop()) {
            _showGlobalButtonOverlay(navigatorKey.currentContext!);
          }
        },
        onTransitionEnd: () {
          _globalButtonOverlay?.remove();
          _globalButtonOverlay = null;
        },
      ),
      navigatorKey: navigatorKey,
    );
  }
}

void _showGlobalButtonOverlay(BuildContext context) {
  _globalButtonOverlay = OverlayEntry(
    builder: (context) => Positioned(
      top: MediaQuery.of(context).padding.top + 16,
      left: 16,
      child: IconButton(
        icon: Icon(Icons.arrow_back),
        color: Colors.white,
        onPressed: () {
          _globalButtonOverlay?.remove();
          Navigator.pop(context);
        },
      ),
    ),
  );
  Navigator.of(context).overlay.insert(_globalButtonOverlay!);
}

方案三:放弃原生Hero,自定义路由过渡动画

完全自定义路由的过渡逻辑,自己控制元素的动画和层级,避免依赖原生Hero的Overlay层级限制。例如使用PageRouteBuilder,在过渡动画中将按钮和Hero元素放在同一个Stack中,确保按钮层级更高:

Route _createSecondScreenRoute() {
  return PageRouteBuilder(
    pageBuilder: (context, animation, secondaryAnimation) => SecondScreen(),
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      final heroAnimation = Tween(begin: 0.0, end: 1.0).animate(animation);
      return Stack(
        children: [
          // Hero过渡元素
          FadeTransition(
            opacity: heroAnimation,
            child: ScaleTransition(
              scale: heroAnimation,
              child: Container(color: Colors.blue),
            ),
          ),
          // 按钮始终在最上层
          Positioned(
            top: MediaQuery.of(context).padding.top + 16,
            left: 16,
            child: IconButton(
              icon: Icon(Icons.arrow_back),
              color: Colors.white,
              onPressed: () => Navigator.pop(context),
            ),
          ),
          child,
        ],
      );
    },
  );
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 11:09:56