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

8

我正在尝试制作一个叠加层,就像这里显示的一样: 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_container的包 https://github.com/MustansirZia/overlay_container/blob/master/lib/overlay_container.dart ,也许你可以从中得到一些灵感。 - chunhunghan
如果你只需要显示通知,你可以使用Flushbar包 https://pub.dev/packages/flushbar - chunhunghan
@chunhunghan 感谢您的建议,但我正在寻找一种创建自己代码的方法。 - Giraldi
@春荣涵 我会看一下overlay_container,谢谢你的建议。 - Giraldi
3个回答

17

通常可以使用 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?复制上面的代码后,routeObserver 给我带来了 undefined_identifier 错误。 - Giraldi
这是我们在之前定义的一个全局变量。你需要导入它。 - Rémi Rousselet
1
好的,到目前为止它运行得非常好。但是我使用了didPushNext()而不是didPush(),因为后者在当前路由被推送且OverlayEntry尚未插入时被调用。 - Giraldi
然而,我希望找到更简单的方法,就像Snackbar的showSnackBar()函数一样。它不必担心在路由更改时将其删除。有没有办法从函数本身检测路由更改?就像我上面的示例中,在ShowNotificationIcon类内部? - Giraldi
没有这样的东西。如果你想要的话,你可以在RouteAware的基础上构建自己的东西,但至少需要它。 - Rémi Rousselet
它在 onPop() 中调用 remove 时有效,但之后应用程序变得无响应。 - minato

2

你只需要使用CompositedTransformTargetCompositedTransformFollowerLinkLayout

由于这些工具的作用,如果叠加层所依附的小部件消失了,叠加层也会随之消失。

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),
              )
         ],
      ),
    );
  }

这个方法对我来说几乎可行(使用 showWhenUnlinked: false),但是叠加层会在新页面动画进入视图时继续显示,然后在新页面完成过渡后消失。使用 RouteAware + RouteObserver 的效果更好,当新路由开始过渡时,叠加层会立即消失。 - dokkaebi

0

我想建议使用flushbar包。https://github.com/AndreHaueisen/flushbar
正如该包所说:如果您需要在通知用户时进行更多自定义,请使用此包。对于Android开发人员,它是用来替代toast和snackbar的。

您还可以将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);

enter image description here


谢谢您的建议,但我正在尝试学习编写此功能的方法。因此,不使用第三方库。有什么想法吗? - Giraldi
覆盖容器包结构与您所做的相同。也许您可以看到它。 - chunhunghan
好的,我会看一下。谢谢你的建议。 - Giraldi
我看了一下,但它没有我需要的两个要求,即:1)它应该作为一个函数被调用。2)在导航到下一个屏幕时应该隐藏它。 - Giraldi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接