flutter 第二代路由导航系统

 第二代路由最重要的类就是Page,RouterDelegate,Router了,

概述

新版Flutter包含两个版本的导航器,分别是Navigator 1.0和Navigator 2.0。Navigator 2.0是从Flutter 1.22版开始引入的一个全新的导航器,其相比1.0功能更多也更加的灵活,当然了从使用的难易程度上来说Navigator 2.0比Navigator 1.0要难上手一些。

QA:Navigator 2.0出来后Navigator 1.0还能继续用吗?

相信很多同学有这样的疑问,Navigator 2.0是对Navigator 1.0的补充,目前Flutter官方还没有将Navigator 1.0从Flutter中移除的计划,所以大家可以根据需要选择使用这两种导航器。当然了,作为从事Flutter开发的同学如果对Flutter的新特性,比如Navigator 2.0一点都不了解那么肯定是说不过去的,那么接下来我们就来重点学习下Navigator 2.0的特性、原理以及使用技巧。

QA:选择Navigator 2.0还是fluro?

fluro也是一个不错的Flutter导航框架,它和Navigator没有绝对的好与坏,主要区别是一个是社区的,一个是官方的。fluro的出现主要是解决Navigator 1.0功能弱的问题,但这在Navigator 2.0已经得到了很好的缓解。

  • 如果你的项目之前是有的就是fluro,那么可以根据需要继续使用;
  • 如果你的项目用的是Navigator 1.0,那么可以择机升级到Navigator 2.0;
  • 如果你的项目是一个新的项目没有历史包袱,那么可以一步到位直接上手Navigator 2.0;

QA:为什么Navigator 2 比Navigator 1 复杂这么多?

  • Navigator 2 为了提供对Flutter Web的支持新引入了很多的API和概念,比如:Pag、Router、RouterDelegate、RouteInformationParser,除此之外还需要自己管理路由栈,比Navigator 1 的复杂度高出了一个量级;
  • 但这些API并不是必须要是要实用的,对应一个APP的导航框架来说只需要用到Router、Page、RouterDelegate就可以了,其中Router、Page的使用比较简单,主要是RouterDelegate比较复杂;
  • RouterDelegate中最为核心的一点是在build方法中来实现对路由栈的管理,这个参考课程中的设计就可以了;
  • 对Navigator 2的使用和理解不仅仅停留在看视频上,要参考着课程中的讲解,动手去写多写几遍,以及跟着后面的实战项目按部就班的学习,然后回过头来在来看之前不理解的部分,就会豁然开朗了;
  • 支持自定义页面的路由栈;
  • 支持一次打开或关闭多个页面;
  • 支持删除当前页面下的页面;

那这些特性能都是Navigator 1.0没有或很难实现的一个功能。

打开和关闭页面是平时开发中最常用的一个功能,那么在Navigator 1.0中最常用的打开和关闭页面的方式是什么呢?

打开一个页面

Navigator.push(context, MaterialPageRoute(builder: (context) => page));

关闭一个页面

Navigator.pop(context);

走进Navigator 2.0

Navigator 2.0 提供了一系列全新的接口,可以实现将路由状态成为应用状态的一部分,并提供解析来自底层平台如:(Web URL)的路由的功能,新增的 API 如下:

  • Page:用来表示 Navigator 路由栈中各个页面的不可变对象;
    • Page是个抽象类通常使用它的派生类:MaterialPage或CupertinoPage;
  • Router:用来配置要由 Navigator 展示的页面列表,通常,该页面列表会根据系统或应用程序的状态改变而改变;
    • 除了可以直接使用Router本身外还可以使用MaterialApp.router()来创建Router;
  • RouterDelegate:定义应用程序中的路由行为,例如 Router 如何知道应用程序状态的变化以及如何响应;
    • 主要的工作就是监听RouteInformationParser和应用状态,并使用当前列表来构建Pages;
  • RouteInformationParser:可缺省,主要应用与web,持有RouteInformationProvider 提供的 RouteInformation ,可以将其解析为我们定义的数据类型;
  • BackButtonDispatcher:响应后退按钮,并通知 Router

Tips:上面API中BackButtonDispatcher是用到的情况很少,另外对应移动端APP开发来说我们只需要用到Navigator 2.0 中的Page、Router、RouterDelegate这三个API即可,RouteInformationParser与RouteInformationProvider主要是应用于开发web网站的路由用的;

下图展示了 RouterDelegate 与 Router、RouteInformationParser 在一起的交互和应用程序的状态:

流程解析:

  1. 当系统打开新页面(如 “/detail”)时,RouteInformationParser 会将其转换为应用中的具体数据类型 T(如:BiliRoutePath);
  2. 该数据类型会被传递给 RouterDelegate 的 setNewRoutePath 方法,我们可以在这里更新路由状态;
  3. notifyListeners 会通知 Router 重建 RouterDelegate(通过 build() 方法);
  4. RouterDelegate.build() 返回一个新的 Navigator 实例,并最终展示出我们想要打开的页面;

 下面写了一个小栗子,仅供参考:首先定义RouterDelegate的子类:这里需要注意的是重写ChangeNotify方法的时候,不需要重写addListener和removeListener,否则得到的后果就是有可能跳转不了,我测试过的,在这个地方踩了坑,不希望后来人在同样的位置翻车。

一:自定义RouterDelegate 包含页面堆栈信息

import 'package:bilibili_demo/nv2/detail.dart';
import 'package:bilibili_demo/nv2/navigator2.dart';
import 'package:flutter/material.dart';

MaterialPage wrapPage(
    {required Widget child, Object? args, required String name}) {
  return MaterialPage(
      child: child, name: name, arguments: args, key: ValueKey(child.hashCode));
}

class MyRouterDelegate extends RouterDelegate<String>
    with ChangeNotifier, PopNavigatorRouterDelegateMixin<String> {
  bool isDetail = false;

  List<MaterialPage> pages = [];
  /// 管理的页面堆栈
  final _stack = <String>["/"];

  List<String> get stack => List.unmodifiable(_stack);

  static MyRouterDelegate of(BuildContext context) {
    final delegate = Router.of(context).routerDelegate;
    assert(delegate is MyRouterDelegate, "delegate mush match");
    return delegate as MyRouterDelegate;
  }

  void push(String newRouter, {Object? args}) {
    if (_stack.contains(newRouter)) return;

    _stack.add(newRouter);
    addPageMap(newRouter, args: args);
    notifyListeners();
  }
  
  /// 向pageMap中加入路由信息
  void addPageMap(String router, {Object? args}) {
    MaterialPage page = wrapPage(child: Container(), name: router);
    switch (router) {
      case "/detail":
        page = wrapPage(child: DetailDemo(), name: router, args: args);
        break;
      case "/":
        page = wrapPage(child: Navigator2Home(), name: router, args: args);
        break;
    }
    _pageMap.addAll({router: page});
  }

  void removePageMap(String router) {
    _pageMap.remove(router);
  }

  final Map<String, Page> _pageMap = <String, Page>{
    "/": wrapPage(child: Navigator2Home(), name: "/"),
  };

  void remove(String routername) {
    if (!_stack.contains(routername)) return;
    if (routername == "/") return;

    _stack.remove(routername);
    removePageMap(routername);

    notifyListeners();
  }

  // final RouteFactory onGenerateRouter;

  @override
  // TODO: implement currentConfiguration
  String? get currentConfiguration {
    if (_stack.isNotEmpty) return _stack.last;
    return null;
  }

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Navigator(
      key: navigatorKey,
      pages: [for (String name in _stack) _pageMap[name]!],
      onPopPage: (router, value) {
        if (router.didPop(value)) {
          print("value is $value router is ${router.settings.name}");
          remove(router.settings.name!);
          return true;
        }
        print("false");
        return false;
      },
    );
  }

  @override
  Future<bool> popRoute() async {
    // TODO: implement popRoute
    print("popRouter");
    // return true;
    return super.popRoute();
  }

  @override
  Future<void> setInitialRoutePath(String configuration) {
    print("init config is $configuration");
    // TODO: implement setInitialRoutePath
    return super.setInitialRoutePath(configuration);
  }

  @override
  Future<void> setNewRoutePath(configuration) async {
    // TODO: implement setNewRoutePath
    print("new router is $configuration");
  }

  @override
  // TODO: implement navigatorKey
  GlobalKey<NavigatorState>? get navigatorKey => GlobalKey<NavigatorState>();
}

class MyRouterInformationParser extends RouteInformationParser<String> {
  @override
  Future<String> parseRouteInformation(
      RouteInformation routeInformation) async {
    // TODO: implement parseRouteInformation
    print("information is $routeInformation");
    return routeInformation.location!;
  }

  @override
  RouteInformation? restoreRouteInformation(configuration) {
    print("config is $configuration");
    // TODO: implement restoreRouteInformation
    RouteInformation? info = super.restoreRouteInformation(configuration);
    print("info is $info");
    return info;
  }
}

 二:main.dart里面使用RouterDelegate:

void main() async {
  try {
    await DbManager.init();
    print("db init over");
  } catch (e) {
    printLog("==> e is $e");
  }
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final RouterDelegate<String> _delegate = MyRouterDelegate();
  /// 想使用parse的可以自行添加
  final RouteInformationParser<String> _parser = MyRouterInformationParser();

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "flutter navigator2",
      home: Router(routerDelegate: _delegate),
    );
  }
}

定义两个页面一个是homePage,一个是detail,

 三:路由使用 

MyRouterDelegate.of(context).push("/detail");
import 'package:bilibili_demo/nv2/my_router_delegate.dart';
import 'package:flutter/material.dart';

class Navigator2Home extends StatefulWidget {
  const Navigator2Home({
    Key? key,
  }) : super(key: key);

  @override
  State<Navigator2Home> createState() => _Navigator2HomeState();
}

class _Navigator2HomeState extends State<Navigator2Home> {
  void goDetail() {
    MyRouterDelegate.of(context).push("/detail");
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            ElevatedButton(onPressed: goDetail, child: Text("go detail"))
          ],
        ),
      ),
    );
  }
}

detail页面的逻辑比较简单我就不贴了,至此navigator2的内容完毕,是不是很简单 

猜你喜欢

转载自blog.csdn.net/lck8989/article/details/126806470