草庐IT

Flutter 学习 之 Nviagator 2.0 (MaterialApp.router 的使用)

半城半离人 2023-03-28 原文

关于Navigator 2.0 详细内容可以参考 此文章,今天就对此文章进行一个封装 Flutter Navigator 2.0 指南与原理解析

一.修改Main.dart的启动代码

Navigator 2.0 之后,Flutter 也提供了 MaterialApp 的新构造函数 router 来帮助我们直接在应用顶层构造出全局的 Router 组件,使用方式如下

MaterialApp.router(
  title: 'Flutter Navigator 2.0 Demo',
  theme: ThemeData(
    primarySwatch: Colors.blue,
  ),
  //必传项目 路由返回 用来解析浏览器路径 
  routeInformationParser: MyRouteParser(),
  //必传项目 路由代理
  routerDelegate: delegate,
)

注意 delegate的初始化要在build之前完成

class MyApp extends StatelessWidget {
  final MyRouterDelegate routerDelegate=MyRouterDelegate();
   MyApp({Key? key}) : super(key: key);

二.MyRouteParser的简单实现

class MyRouteParser extends RouteInformationParser<RouteSettings> {
  @override
  ///parseRouteInformation() 方法的作用就是接受系统传递给我们的路由信息 routeInformation
  Future<RouteSettings> parseRouteInformation(
      RouteInformation routeInformation) {
    // Uri uri = Uri.parse(routeInformation.location??"/");
    return SynchronousFuture(RouteSettings(name: routeInformation.location));
  }

  @override
  ///恢复路由信息
  RouteInformation restoreRouteInformation(RouteSettings configuration) {
    return RouteInformation(location: configuration.name);
  }
}

三.实现 RouterDelegate

RouteInformationParser 传递过来的RouteInformation 被 RouterDelegate所接受 执行setInitialRoutePath方法 他调用setNewRoutePath 创建新的路由

///继承RouterDelegate并混合PopNavigatorRouterDelegateMixin和ChangeNotifier
///ChangeNotifier 用来通知路由改变
///backButtonDispatcher 发出回退按钮事件时,会调用 RouterDelegate 的 popRoute() 方法,由混入的 PopNavigatorRouterDelegateMixin 实现。
class MyRouterDelegate extends RouterDelegate<RouteSettings>
    with PopNavigatorRouterDelegateMixin<RouteSettings>, ChangeNotifier {
  static MyRouterDelegate? _instance;

  factory MyRouterDelegate() => _instance ??= MyRouterDelegate._();

  MyRouterDelegate._();

  ///页面栈
  List<Page> _stack = [];
  //当前页面信息
  RouteSettings? _setting;

  //重写navigatorKey
  @override
  GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

  @override
  // TODO: implement currentConfiguration
  RouteSettings? get currentConfiguration => _stack.last;
///如果发现showModelBottomSheet不能受返回键控制 请注释掉popRoute这方法 将首页的Widegt包裹WillPopScope   --4.22新加此注释
  @override
  Future<bool> popRoute() {
    if (_stack.length > 1) {
      _stack.removeLast();
      _setting = _stack.last;
      changePage();
      //非最后一个页面
      return Future.value(true);
    }

    //最后一个页面确认退出操作
    return _confirmExit();
  }

  Future<bool> _confirmExit() async {
    bool result = doubleCheckExit(navigatorKey.currentContext!);
    // bool result = await ExitUtil.backToDesktop();
    return !result;
  }
//添加页面的操作
  void addPage({required name, arguments}) {
    _setting = RouteSettings(name: name, arguments: arguments);
    changePage();

    notifyListeners();
  }

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

  /// 按下返回的回调
  bool _onPopPage(Route<dynamic> route, dynamic result) {
    if (!route.didPop(result)) {
      return false;
    }
    return true;
  }

  changePage() {
    int index = getCurrentIndex(_stack, _setting!);
    List<Page> tempPages = _stack;

    if (index != -1) {
      // 要求栈中只允许有一个同样的页面的实例 否则开发模式热更新会报错
      // 要打开的页面在栈中已存在,则将该页面和它上面的所有页面进行出栈
      tempPages = tempPages.sublist(0, index);
      // 或者删除之前存在栈里的页面,重新创建
      // tempPages.removeAt(index);
    }
    Page page;
    if (_setting?.name == RouterName.rootPage) {
      _stack.clear();
    }
    page = buildPage(name: _setting?.name, arguments: _setting?.arguments);
    tempPages = [...tempPages, page];

    _stack = tempPages;
  }
  ///获取当前页面位于栈的位置
  int getCurrentIndex(List<Page> pages, RouteSettings setting) {
    for (int i = 0; i < pages.length; i++) {
      Page page = pages[i];
      if (page.name == setting.name) {
        return i;
      }
    }
    return -1;
  }
  ///首次进入时会通过这个方法来配置路由站
  @override
  Future<void> setNewRoutePath(RouteSettings configuration) async {
    if (configuration.name == RouterName.rootPage) {
      _stack.clear();
    }
    addPage(name: configuration.name, arguments: configuration.arguments);
  }

//建造页面
  buildPage({required name, arguments}) {
    return MaterialPage(
        child: getPageChild(name: name, arguments: arguments),
        arguments: arguments,
        name: name,
        key: ValueKey(name));
  }

  static DateTime? _lastTapDt;

  /// 双击退出 单机提示再按一次
  static bool doubleCheckExit(context) {
    if (_lastTapDt == null ||
        DateTime.now().difference(_lastTapDt!) > const Duration(seconds: 1)) {
      ScaffoldMessenger.of(context).clearMaterialBanners();
      ScaffoldMessenger.of(context)
          .showSnackBar(const SnackBar(content: Text("再按一次退出")));
      _lastTapDt = DateTime.now();
      return false;
    }
    _lastTapDt = null;
    return true;
  }

  //通过routerName获取页面
  Widget getPageChild({required name, arguments}) {
    Widget page;
    Map? arg;
    if (arguments is Map) {
      arg = arguments;
    }
    switch (name) {
      case RouterName.rootPage:
        page = RootPage(arguments: arg);
        break;
      default:
        page = const UndefinedPage();
    }
    return page;
  }
}

四.使用

 Center(
        child: TextButton(
          child: const Text("点我跳转"),
          onPressed: () {
            MyRouterDelegate().addPage(name: RouterName.splashPage);
          },
        ),
      ),

五.优化

当项目文件比较多时将内容拆分成多个

1. route_name.dart 存放路由名称

class RouterName {
  static const String rootPage = "/"; //默认MyRouteParser传回来的地址是/您可以在返回之前做修改或者把默认修改成/
  static const String splashPage = "SPLASH_PAGE";
}

2.route_page_config.dart 根据路由名称寻找页面

///通过Router的名字获取页面
///[name] 路由名称
///[arguments] 可空 路由传递参数
  Widget getPageChild({required name, arguments}) {
    Widget page;
    Map? arg;
    if (arguments is Map) {
      arg = arguments;
    }
    switch (name) {
      case RouterName.rootPage:
        page = RootPage(arguments: arg);
        break;
      default:
        page = const UndefinedPage();
    }
    return page;
  }

3.my_route_information_parser.dart 没有涉及到浏览器跳转和上面保持不变

4.my_router_delegate.dart 改动较大

///继承RouterDelegate并混合PopNavigatorRouterDelegateMixin和ChangeNotifier
///ChangeNotifier 用来通知路由改变
///backButtonDispatcher 发出回退按钮事件时,会调用 RouterDelegate 的 popRoute() 方法,由混入的 PopNavigatorRouterDelegateMixin 实现。
class MyRouterDelegate extends RouterDelegate<RouteSettings>
    with PopNavigatorRouterDelegateMixin<RouteSettings>, ChangeNotifier {
  ///页面栈
  List<Page> _stack = [];

  //当前的界面信息
  RouteSettings? _setting = const RouteSettings(name: RouterName.rootPage);

  //重写navigatorKey
  @override
  GlobalKey<NavigatorState> navigatorKey;

  MyRouterDelegate() : navigatorKey = GlobalKey<NavigatorState>() {
    //初始化两个方法 一个是push页面 另一个是替换页面

    NavigatorUtil()
        .registerRouteJump(RouteJumpFunction(onJumpTo: (RouteSettings setting) {
      _setting = setting;
      changePage();
    }, onReplaceAndJumpTo: (RouteSettings setting) {
      if (_stack.isNotEmpty) {
        _stack.removeLast();
      }
      _setting = setting;
      changePage();
    }));
  }

  @override
  // TODO: implement currentConfiguration
  RouteSettings? get currentConfiguration => _stack.last;

  @override
  Future<bool> popRoute() {
    if (_stack.length > 1) {
      _stack.removeLast();
      _setting = _stack.last;
      changePage();
      //非最后一个页面
      return Future.value(true);
    }
    //最后一个页面确认退出操作
    return _confirmExit();
  }

  Future<bool> _confirmExit() async {
    bool result = ExitUtil.doubleCheckExit(navigatorKey.currentContext!);
    // bool result = await ExitUtil.backToDesktop();
    return !result;
  }

  void addPage({required name, arguments}) {
    _setting = RouteSettings(name: name, arguments: arguments);
    changePage();
  }

  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      //解决物理返回建无效的问题
      onWillPop: () async => !await navigatorKey.currentState!.maybePop(),
      child: Navigator(
        key: navigatorKey,
        pages: _stack,
        onPopPage: _onPopPage,
      ),
    );
  }

  /// 按下返回的回调
  bool _onPopPage(Route<dynamic> route, dynamic result) {
    debugPrint("这里的试试");
    if (!route.didPop(result)) {
      return false;
    }
    return true;
  }

  changePage() {
    int index = getCurrentIndex(_stack, _setting!);
    List<Page> tempPages = _stack;

    if (index != -1) {
      // 要求栈中只允许有一个同样的页面的实例 否则开发模式热更新会报错
      // 要打开的页面在栈中已存在,则将该页面和它上面的所有页面进行出栈
      tempPages = tempPages.sublist(0, index);
      // 或者删除之前存在栈里的页面,重新创建
      // tempPages.removeAt(index);
    }
    Page page;
    if (_setting?.name == RouterName.rootPage) {
      _stack.clear();
    }
    page = buildPage(name: _setting?.name, arguments: _setting?.arguments);
    tempPages = [...tempPages, page];
  NavigatorUtil().notify(tempPages, _stack);

    _stack = tempPages;
    notifyListeners();
  }

  @override
  Future<void> setNewRoutePath(RouteSettings configuration) async {
    debugPrint("setNewRoutePath${configuration}");
    if (configuration.name == RouterName.rootPage) {
      _stack.clear();
    }
    addPage(name: configuration.name, arguments: configuration.arguments);
  }
}

5.创建navigator_util.dart 管理跳转

typedef OnJumpTo = void Function(RouteSettings settings);

///创建跳转方法的抽象类
///传递一个Function(routeSetting)
abstract class _RouteJumpFunction {
  ///跳转到
  void onJumpTo({required name, Map? arguments});

  ///替换当前页面的跳转
  void onReplaceAndJumpTo({required name, Map? arguments});
}

class RouteJumpFunction {
  OnJumpTo? onJumpTo;
  OnJumpTo? onReplaceAndJumpTo;

  RouteJumpFunction({this.onJumpTo, this.onReplaceAndJumpTo});
}

class NavigatorUtil extends _RouteJumpFunction {
  //创造工厂方法
  static NavigatorUtil? _instance;

  factory NavigatorUtil() => _instance ??= NavigatorUtil._();

  NavigatorUtil._();

  //跳转方法
  RouteJumpFunction? _function;
  

  //当前页面
  RouteSettings? _current;

  ///注册路由跳转逻辑
  void registerRouteJump(RouteJumpFunction function) {
    _function = function;
  }

//编写跳转逻辑
  @override
  void onJumpTo({required name, Map? arguments}) {
    RouteSettings settings = RouteSettings(name: name, arguments: arguments);
    return _function!.onJumpTo!(settings);
  }

//编写替换当前页面的跳转逻辑
  @override
  void onReplaceAndJumpTo({required name, Map? arguments}) {
    RouteSettings setting = RouteSettings(name: name, arguments: arguments);
    _function?.onReplaceAndJumpTo!(setting);
  }


  void notify(List<Page> currentPages, List<Page> prePages) {
    if (currentPages == prePages) {
      return;
    }
    RouteSettings setting = RouteSettings(
        name: currentPages.last.name, arguments: currentPages.last.arguments);
    _notify(setting);
  }

  void _notify(RouteSettings setting) {

    debugPrint("当前的页面是:${setting.name}");
    debugPrint("上一个页面是:${_current?.name}");
    
    _current = setting;
  }
}

//建造页面
buildPage({required name, arguments}) {
  return MaterialPage(
      child: getPageChild(name: name, arguments: arguments),
      arguments: arguments,
      name: name,
      key: ValueKey(name));
}

///获取当前页面在栈中的位置
int getCurrentIndex(List<Page> pages, RouteSettings setting) {
  for (int i = 0; i < pages.length; i++) {
    Page page = pages[i];
    if (page.name == setting.name) {
      return i;
    }
  }
  return -1;
}

6.退出方法

import 'package:flutter/material.dart';

class ExitUtil{
  static DateTime? _lastTapDt;

  /// 双击退出 单击提示再按一次
  static bool doubleCheckExit(context) {
    if (_lastTapDt == null ||
        DateTime.now().difference(_lastTapDt!) > const Duration(seconds: 1)) {
      ScaffoldMessenger.of(context).clearMaterialBanners();
      ScaffoldMessenger.of(context)
          .showSnackBar(const SnackBar(content: Text("再按一次退出")));
      _lastTapDt = DateTime.now();
      return false;
    }
    _lastTapDt = null;
    return true;
  }
}

7.使用

Center(
        child: TextButton(
          child: const Text("点我跳转"),
          onPressed: () {
            NavigatorUtil().onJumpTo(name: RouterName.splashPage);
          },
        ),
      ),

项目demo地址链接:https://pan.baidu.com/s/1BqIdxlAO7M9Q9wDyJwamdA
提取码:r6ob

有关Flutter 学习 之 Nviagator 2.0 (MaterialApp.router 的使用)的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  5. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  6. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  7. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  8. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  9. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

  10. ruby - 在 64 位 Snow Leopard 上使用 rvm、postgres 9.0、ruby 1.9.2-p136 安装 pg gem 时出现问题 - 2

    我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po

随机推荐