在 Flutter 中使用 pushNamed 导航后如何使 OverlayEntry 消失

Posted

技术标签:

【中文标题】在 Flutter 中使用 pushNamed 导航后如何使 OverlayEntry 消失【英文标题】:How to make OverlayEntry disappear after navigating with pushNamed in Flutter 【发布时间】:2019-12-04 16:11:37 【问题描述】:

我正在尝试制作如下所示的叠加层: https://www.didierboelens.com/2018/06/how-to-create-a-toast-or-notifications-notion-of-overlay/ 使用OverlayEntry

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

class ShowNotificationIcon 

    void show(BuildContext context) async 
        OverlayState overlayState = Overlay.of(context);
        OverlayEntry overlayEntry = new OverlayEntry(builder: _build);

        overlayState.insert(overlayEntry);
    

    Widget _build(BuildContext context)
      return new Positioned(
        top: 50.0,
        left: 50.0,
        child: new Material(
            color: Colors.transparent,
            child: new Icon(Icons.warning, color: Colors.purple),
        ),
      );
    

调用:

ShowNotificationIcon _icon = new ShowNotificationIcon();

_icon.show(context);

但是,当我尝试导航到其他屏幕时,覆盖仍保留在屏幕中。

如何只在被调用的屏幕中显示覆盖而不在其他屏幕中显示?

以防万一,这是我在有状态小部件中尝试过的:

  ShowNotificationIcon _icon = new ShowNotificationIcon();

  @override
  void initState() 
    WidgetsBinding.instance.addPostFrameCallback((_) 
      _icon.show(context);
    );

    super.initState();
  

  @override
  void dispose() 
    _icon.remove();
    super.dispose();
  

【问题讨论】:

有一个包调用overlay_containergithub.com/MustansirZia/overlay_container/blob/master/lib/…,或许你可以从中得到灵感 如果您只需要显示通知。你可以使用包flushbar pub.dev/packages/flushbar @chunhunghan 感谢您的输入,但我正在寻找一种方法来创建自己的代码。 @chunhunghan 我会看看overlay_container。谢谢你的建议。 【参考方案1】:

这通常使用RouteAware+RouteObserver 执行。

RouteObserver 是一个对象,它允许实现RouteAware 的对象对与路由相关的一些更改做出反应,其中包括:

一条路线已被推到当前路线的顶部 路线又回到了第一个计划中

然后您可以使用这两个事件来隐藏/显示您的叠加层


首先,您需要RouteObserver

这可以创建为全局变量,需要传递给您的Navigator。在基于MaterialApp 的应用程序中,它通常如下所示:

final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();

void main() 
  runApp(MaterialApp(
    home: Container(),
    navigatorObservers: [routeObserver],
  ));

然后,拥有OverlayEntry 的小部件现在可以像这样实现RouteAware

class RouteAwareWidget extends StatefulWidget 
  State<RouteAwareWidget> createState() => RouteAwareWidgetState();


// Implement RouteAware in a widget's state and subscribe it to the RouteObserver.
class RouteAwareWidgetState extends State<RouteAwareWidget> with RouteAware 

  @override
  void didChangeDependencies() 
    super.didChangeDependencies();
    // routeObserver is the global variable we created before
    routeObserver.subscribe(this, ModalRoute.of(context) as PageRoute);
  

  @override
  void dispose() 
    routeObserver.unsubscribe(this);
    super.dispose();
  

  @override
  void didPush() 
    // Route was pushed onto navigator and is now topmost route.
  

  @override
  void didPopNext() 
    // Covering route was popped off the navigator.
  

  @override
  Widget build(BuildContext context) => Container();


此时,您可以使用didPushdidPopNext 来显示/隐藏您的OverlayEntry:

OverlayEntry myOverlay;

@override
void didPush() 
  myOverlay.remove();


@override
void didPopNext() 
  Overlay.of(context).insert(myOverlay);

【讨论】:

如何在小部件中使用routeObserver?复制上面的代码给了我routeObserverundefined_identifier 错误。 这是我们在上一步中定义的全局变量。你必须导入它 好的,到目前为止效果非常好。但是我使用了didPushNext() 而不是didPush(),因为后者在当前路由已被推送且OverlayEntry 尚未插入时被调用。 但是,我希望找到一种更直接的方法,例如 Snackbar 的 showSnackBar() 函数。它不必担心在路由更改时将其删除。有没有办法检测函数本身内的路由变化形式?如我上面的示例,在ShowNotificationIcon 类中? 没有这样的东西。如果你愿意,你可以在 RouteAware 上构建你自己的,但你至少需要它。【参考方案2】:

我想建议使用软件包flushbar。 https://github.com/AndreHaueisen/flushbar 正如包所说:如果您在通知用户时需要更多自定义,请使用此包。对于 android 开发人员来说,它可以替代 toasts 和snackbars。

您也可以将flushbarPosition设置为TOP或BOTTOM

Flushbar(
      title: "Hey Ninja",
      message: "Lorem Ipsum is simply dummy text of the printing and typesetting industry",
      flushbarPosition: FlushbarPosition.TOP,
      flushbarStyle: FlushbarStyle.FLOATING,
      reverseAnimationCurve: Curves.decelerate,
      forwardAnimationCurve: Curves.elasticOut,
      backgroundColor: Colors.red,
      boxShadows: [BoxShadow(color: Colors.blue[800], offset: Offset(0.0, 2.0), blurRadius: 3.0)],
      backgroundGradient: LinearGradient(colors: [Colors.blueGrey, Colors.black]),
      isDismissible: false,
      duration: Duration(seconds: 4),
      icon: Icon(
        Icons.check,
        color: Colors.greenAccent,
      ),
      mainButton: FlatButton(
        onPressed: () ,
        child: Text(
          "CLAP",
          style: TextStyle(color: Colors.amber),
        ),
      ),
      showProgressIndicator: true,
      progressIndicatorBackgroundColor: Colors.blueGrey,
      titleText: Text(
        "Hello Hero",
        style: TextStyle(
            fontWeight: FontWeight.bold, fontSize: 20.0, color: Colors.yellow[600], fontFamily: "ShadowsIntoLightTwo"),
      ),
      messageText: Text(
        "You killed that giant monster in the city. Congratulations!",
        style: TextStyle(fontSize: 18.0, color: Colors.green, fontFamily: "ShadowsIntoLightTwo"),
      ),
    )..show(context);

【讨论】:

感谢您的输入,但我正在尝试学习编写此类功能的方法。所以,没有第三方库。有什么想法吗? overlay_container 包构造就像你做的那样。也许你可以看到它。 好的,我去看看。感谢您的建议 我看过了。但是,它没有我一直在寻找的两个要求,即:1)它应该作为一个函数调用。 2)导航到下一个屏幕时应该隐藏它。【参考方案3】:

你只需要CompositedTransformTarget CompositedTransformFollower LinkLayout

多亏了这些,如果附加了叠加层的小部件消失了,叠加层也会消失。

final key = GlobalKey();
OverlayEntry? floatingEntry ;
final layerLink = LayerLink();

void hideEntry()
  floatingEntry ?.remove();


void displayOverlay() 
    final overlay = Overlay.of(context);
    floatingEntry = OverlayEntry(builder: _buildFloatingButton);
    overlay!.insert(floatingEntry!);
  


 Widget _buildFloatingButton(BuildContext context) 
    final render = key.currentContext!.findRenderObject() as RenderBox;
    final offset = render.localToGlobal(Offset.zero);
    final size = render.size;
    return Positioned(
      width: floatinSize,
      child: CompositedTransformFollower(
        link: layerLink,
        offset: Offset(0.0, -size.height / 2),
        showWhenUnlinked: false,
        child: Container(
          width: floatinSize,
          height: floatinSize,
          decoration: BoxDecoration(
            color: Get.theme.scaffoldBackgroundColor,
            shape: BoxShape.circle,
          ),
          padding: const EdgeInsets.all(10.0),
          child: Container(
            decoration: BoxDecoration(
              color: Get.theme.primaryColor,
              shape: BoxShape.circle,
            ),
            child: Icon(
              FontAwesomeIcons.plus,
              color: Get.theme.primaryColorDark,
            ),
          ),
        ),
      ),
    );
  

 @override
  Widget build(BuildContext context) 
    final size = MediaQuery.of(context).size;
    return Container(
      width: size.width,
      height: _navigationHeight,
      color: Get.theme.bottomNavigationBarTheme.backgroundColor,
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
              CompositedTransformTarget(
               link:linkLayout,
               child:Container(key:key),
              )
         ],
      ),
    );
  

【讨论】:

以上是关于在 Flutter 中使用 pushNamed 导航后如何使 OverlayEntry 消失的主要内容,如果未能解决你的问题,请参考以下文章

flutter pushname使用和多参数传递

Flutter:如何将 PageRoute Animation 与 Navigator.of(context).pushNamed 结合起来

为啥 Navigator.pushNamed(context, "route");在我的 flutter-cupertino-app 代码示例中工作?

如何使用 onGenerateRoute 在 URL 中显示 Flutter Web 路由名称?

Flutter页面导航

Flutter/Dart - 延迟后打开视图