【问题标题】:Navigator 2.0 - WillPopScope vs BackButtonListenerNavigator 2.0 - WillPopScope 与 BackButtonListener
【发布时间】:2022-08-22 22:38:12
【问题描述】:

我有一个带有BottomNavigationBarIndexedStack 的应用程序,它显示了标签内容。每个选项卡都有自己的Router 和自己的RouterDelegate,以模仿 iOS 样式的选项卡行为(每个选项卡都有自己的导航控制器)。

之前,这个应用程序只在 iOS 上发布。我现在正在开发Android版本,需要正确支持Android硬件后退按钮。我通过为每个标签实现一个ChildBackButtonDispatchers 来做到这一点,它是父RootBackButtonDispatcher 的一个子标签。这行得通。

我现在遇到的问题是我使用WillPopScope 小部件在用户离开屏幕时保存他们的输入。如果用户点击AppBar 中的后退按钮,这将正常工作,但当用户点击硬件后退按钮时不会触发回调。我也在这些屏幕上实现了BackButtonListeners,但这意味着我必须将屏幕包装在WillPopScopes 和BackButtonListeners 中,两者都调用相同的回调。

它应该是这样,还是我做错了什么?

相关的小部件层次结构:

  • MaterialApp
  • Navigator
  • 标签界面与IndexedStack
  • 选中的标签小部件标签的Router
  • Navigator
  • 多页,在堆栈的最后一页...
  • BackButtonListener
  • WillPopScope
  • Scaffold

我的(简化的)路由器委托如下所示:

class AppRouterDelegate extends RouterDelegate<AppRoute>
    with ChangeNotifier, PopNavigatorRouterDelegateMixin<AppRoute> {
  AppRouterDelegate({
    List<MaterialPage> initialPages = const [],
  }) : _pages = initialPages;

  final navigatorKey = GlobalKey<NavigatorState>();
  final List<MaterialPage> _pages;
  List<MaterialPage> get pages => List.unmodifiable(_pages);

  void push(AppRoute route) {
    final shouldAddPage = _pages.isEmpty || (_pages.last.arguments as AppRoute != route);

    if (!shouldAddPage) {
      return;
    }

    _pages.add(route.page);
    notifyListeners();
  }

  @override
  Future<void> setNewRoutePath(AppRoute route) async {
    _pages.clear();
    _pages.add(route.page);
    notifyListeners();

    return SynchronousFuture(null);
  }

  @override
  Future<bool> popRoute() {
    if (canPop) {
      pop();
      return SynchronousFuture(true);
    }
    return SynchronousFuture(false);
  }

  bool get canPop => _pages.length > 1;

  void pop() {
    if (canPop) {
      _pages.remove(_pages.last);
      notifyListeners();
    }
  }

  void popTillRoot() {
    while (canPop) {
      _pages.remove(_pages.last);
    }
    notifyListeners();
  }

  bool _onPopPage(Route<dynamic> route, result) {
    final didPop = route.didPop(result);
    if (!didPop) {
      return false;
    }

    if (canPop) {
      pop();
      return true;
    } else {
      return false;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Navigator(
      key: navigatorKey,
      onPopPage: _onPopPage,
      pages: pages,
    );
  }
}

我找到了this Flutter issue,这让我觉得我根本不应该拥有 WillPopScope,但是没有它,AppBar 中的水龙头就不会被抓住......

    标签: flutter


    【解决方案1】:

    我知道这个问题已经过时了,但这里是其他到达这里的人的答案。

    来自AppBarleading documentation(强调我的):

    如果这是 null 并且 automaticImplyLeading 设置为 true,则 AppBar 将暗示适当的小部件。例如,如果 AppBar 位于还具有 Drawer 的 Scaffold 中,则 Scaffold 将使用打开抽屉的 IconButton 填充此小部件(使用 Icons.menu)。如果没有 Drawer 并且父 Navigator 可以返回,AppBar 将使用调用 Navigator.maybePop 的 BackButton。

    因此,为了使 Android 后退按钮与 App Bar 的后退按钮工作方式相同,您需要使用Navigator.maybePop 方法,该方法将尊重WillPopScope

    方便的是,Flutter 提供了PopNavigatorRouterDelegateMixin 让这一切变得简单;它提供了一个使用maybePoppopRoute 的实现,因此与应用栏自动生成的返回/关闭按钮的工作方式相同。 Flutter 开源的好处是你可以跳入 Flutter 代码来验证 mixin 的作用:

    mixin PopNavigatorRouterDelegateMixin<T> on RouterDelegate<T> {
      /// The key used for retrieving the current navigator.
      ///
      /// When using this mixin, be sure to use this key to create the navigator.
      GlobalKey<NavigatorState>? get navigatorKey;
    
      @override
      Future<bool> popRoute() {
        final NavigatorState? navigator = navigatorKey?.currentState;
        if (navigator == null)
          return SynchronousFuture<bool>(false);
        return navigator.maybePop();
      }
    }
    

    所以我认为你的代码中唯一的错误是,即使你在你的路由器委托上混合了PopNavigatorRouterDelegateMixin,你也提供了你自己的popRoute的覆盖。如果您只是删除 popRoute 覆盖并让 mixin 完成它的工作,那么 Android 后退按钮的功能将与 App Bar 后退/关闭按钮相同。

    【讨论】:

      猜你喜欢
      • 2021-03-16
      • 2021-02-20
      • 1970-01-01
      • 2021-06-22
      • 2021-04-17
      • 2021-07-14
      • 2020-03-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多