zl程序教程

您现在的位置是:首页 >  工具

当前栏目

Flutter路由管理指北

路由flutter 管理 指北
2023-09-27 14:28:53 时间

路由管理

FLutter中的路由,和原生组件化的路由一样,就是页面之间的跳转,也可以称之为导航。app维护一个路由栈,路由入栈(push)操作对应打开一个新页面,路由出栈(pop)操作对应页面关闭操作,而路由管理主要是指如何来管理路由栈。

MaterialPageRoute

MaterialPageRoute是一种模态路由,可以针对不同平台自适应的过渡动画替换整个屏幕页面:

对于Android,打开新页面时,新页面从屏幕底部导入到顶部。关闭页面的时候,会从顶部滑动到底部消失。

在iOS上,页面从右侧滑入并反向退出。

下面我们介绍一下MaterialPageRoute 构造函数的各个参数的意义:

  MaterialPageRoute({
    WidgetBuilder builder,
    RouteSettings settings,
    bool maintainState = true,
    bool fullscreenDialog = false,
  })
复制代码

builder 是一个WidgetBuilder类型的回调函数,它的作用是构建路由页面的具体内容,返回值是一个widget。我们通常要实现此回调,返回新路由的实例。 settings 包含路由的配置信息,如路由名称、是否初始路由(首页)。 maintainState:默认情况下,当入栈一个新路由时,原来的路由仍然会被保存在内存中,如果想在路由没用的时候释放其所占用的所有资源,可以设置maintainState为false。 fullscreenDialog表示新的路由页面是否是一个全屏的模态对话框,在iOS中,如果fullscreenDialog为true,新页面将会从屏幕底部滑入(而不是水平方向)

基本使用

Flutter为我们提供了导航器Navigator。参数传入当前的BuildContext和要导航的页面即可。

  1. 调用Navigator.push导航到第二个页面

     Navigator.push(
               context, new MaterialPageRoute(builder:       (context) => Page2()));
    复制代码
  2. 调用Navigator.pop返回前一个页面

     Navigator.pop(context, result);
    
    复制代码
  3. 关闭页面后获取结果 有时候我们需要上个页面关闭时传递一个返回值,幸运的是,Navigator的调用方法都是Future,因此我们可以等待它们的结果:

    3.1. 等待Navigator运行

    3.2. 将返回值传递给Navigator.pop函数

    3.3. 等待完成后,获取返回值

    在page1中,导航到page2,并且await到page2传递返回值并pop,根据返回值弹出不同的对话框:

    onPressed: () async {
          var navigationResult = await Navigator.push(
              context, new MaterialPageRoute(builder: (context) => Page2()));
    
          if (navigationResult == 'from_back') {
            showDialog(
                context: context,
                builder: (context) => AlertDialog(
                      title: Text('Navigation from back'),
                    ));
          } else if (navigationResult == 'from_button') {
            showDialog(
                context: context,
                builder: (context) => AlertDialog(
                      title: Text('Navigation from button'),
                    ));
          }
        },
    复制代码

    在page2中传递返回值并返回:

    Navigator.pop(context, 'from_button');
    复制代码
  4. 拦截返回键 如果不想点击返回键关闭当前页面,可以使用WillPopScope小部件,用它放在最外层包括住脚手架。并向onWillPop返回false。false告诉系统当前页面不处理返回。

       @override
       Widget build(BuildContext context) {
         return WillPopScope(
           onWillPop: () => Future.value(false),
           child: Scaffold(
             body: Container(
               child: Center(
                 child: Text('Page 2',
                     style: TextStyle(fontSize: 30.0, fontWeight: FontWeight.bold)),
               ),
             ),
           ),
         );
       }
   
复制代码

如果想自定义处理返回键,可以在return false 之前自己处理,比如关闭 当前页面并传递返回值:

       WillPopScope(
             onWillPop: () async {
                 // You can await in the calling widget for my_value and handle when complete.
                 Navigator.pop(context, 'my_value');
                 return false;
               },
               ...
       );
复制代码

命名路由

基本使用

上面代码是在没个需要导航的地方声明路由,不能复用,我们可以先给路由起一个名字,再注册路由表,然后就可以通过路由名字直接打开新的路由了,这为路由管理带来了一种直观、简单的方式,并且可以复用。

MaterialApp的routes属性,既是注册路由表用的,它对应一个Map<String, WidgetBuilder>。

起名:

  static const String page1 = "/page1";
  static const String page2 = "/page2";
复制代码

声明路由表:

  Map<String, WidgetBuilder> routes = {
    page1: (context) => Page1(),
    page2: (context) => Page2(),
  };
复制代码

注册路由表:

 @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      routes: routes,
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: Page1(),
    );
  }
复制代码

然后在需要路由的地方使用命名路由调用:

Navigator.pushNamed(context, page2)
复制代码

传递参数

给page3起名:

    page3: (context) => Page3(),

复制代码

打开路由时候传递参数:

              Navigator.of(context).pushNamed(page3, arguments: "hi");

复制代码

page3中接收参数:

class Page3 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //获取路由参数
    var args = ModalRoute.of(context).settings.arguments;
    return Scaffold(
      body: Container(
        child: Center(
          child: Text('Page 3的参数是$args',
              style: TextStyle(fontSize: 30.0, fontWeight: FontWeight.bold)),
        ),
      ),
    );
  }
}

复制代码

构造函数传参

上面我们明明给page3传递了参数,但是并非传递到构造函数上。我们看构造函数,并不知道传递了什么参数,必须去看路由,并不是很好的做法。那怎么给构造函数传参呢?

起名:

const String page4 = "/page4";

复制代码

注册路由:

    page4: (context) => Page4(text: ModalRoute.of(context).settings.arguments),

复制代码

打开路由时传递参数:

              Navigator.of(context).pushNamed(page4, arguments: "hello");

复制代码

动态路由

MaterialApp还为我们提供了一个onGenerateRoute参数,未在路由表里注册的路由,会在这里寻找。RouteFactory有一个RouteSettings参数,并返回一个Route。这是我们将用来执行所有路由的功能。

Route<dynamic> Function(RouteSettings settings)

复制代码

我们可以这样使用: 先声明路由表:

Route<dynamic> generateRoute(RouteSettings settings) {
	switch (settings.name) {
		case page5:
			return MaterialPageRoute(builder: (context) => Page5());
		case page6:
			return MaterialPageRoute(builder: (context) => Page6());
		default:
			return MaterialPageRoute(builder: (context) => Page1());
	}
复制代码

注册:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      routes: routes,
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      onGenerateRoute: generateRoute,
      home: Page1(),
    );
  }
复制代码

打开路由:

              Navigator.of(context).pushNamed(page5);

复制代码

动态路由传递参数

上面说了,settings可以拿到参数,我们当然就可以传递参数了:

Route<dynamic> generateRoute(RouteSettings settings) {
	print('====${settings.name}');
	switch (settings.name) {
		case page5:
			return MaterialPageRoute(builder: (context) => Page5());
		case page6:
			return MaterialPageRoute(builder: (context) => Page6(text: settings.arguments,));
		default:
			return MaterialPageRoute(builder: (context) => Page1());
	}
}
复制代码

使用:

              Navigator.of(context).pushNamed(page6, arguments: "world");

复制代码

so easy。

处理未定义的路线

有两种处理未定义路由的方法。

  1. 利用generateRoute,找不到路由名的返回默认路由
Route<dynamic> generateRoute(RouteSettings settings) {
	print('====${settings.name}');
	switch (settings.name) {
		case page5:
			return MaterialPageRoute(builder: (context) => Page5());
		case page6:
			return MaterialPageRoute(builder: (context) => Page6(text: settings.arguments,));
		default:
			return MaterialPageRoute(builder: (context) => NotFindPage());
	}
}
复制代码
  1. 利用onUnknownRoute返回默认路由
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Flutter Demo',
        routes: routes,
        theme: ThemeData(
          primarySwatch: Colors.blue,
          visualDensity: VisualDensity.adaptivePlatformDensity,
        ),
        onGenerateRoute: generateRoute,
        onUnknownRoute: (settings) =>
            MaterialPageRoute(builder: (context) => NotFindPage()));
  }
复制代码

初始路由

打开应用第一屏的路由,也有2种方式,

  1. 可以设置initialRoute,指定路由表里注册的路由名。
  2. 可以设置home,对应的page。 initialRoute会覆盖home。
 Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Flutter Demo',
        routes: routes,
        theme: ThemeData(
          primarySwatch: Colors.blue,
          visualDensity: VisualDensity.adaptivePlatformDensity,
        ),
        initialRoute: root,
        home: Page2(),
        onGenerateRoute: generateRoute,
        onUnknownRoute: (settings) =>
            MaterialPageRoute(builder: (context) => NotFindPage()));
  }
复制代码

不使用BuildContext的路由导航

很多情况是,我们已将UI代码从业务逻辑中分离出来(类似于MVVM架构)。viewModel应处理所有逻辑,视图应仅调用模型上的函数,然后在需要时使用新状态重建自身。

我们知道Navigator需要BuildContext的参数,我们在进行实际业务逻辑决策的位置进行导航,而不是在widget里调用路由,如果在viewModel里导航,就要传入context吗?下面实现不要context的导航。

为了遵守MVVM原则,我们将把Navigation功能移动到可以从viewModel调用的服务中。在lib下创建一个名为services的新文件夹,并在其中创建一个名为navigation_service.dart的新文件。

先实现单利模式:

class NavigationService {
  factory NavigationService.getInstance() => _getInstance();

  NavigationService._internal();

  static NavigationService _instance;

  static NavigationService _getInstance() {
    if (_instance == null) {
      _instance = new NavigationService._internal();
    }
    return _instance;
  }
  }
复制代码

然后利用navigatorKey实现:

 final GlobalKey<NavigatorState> navigatorKey =
      new GlobalKey<NavigatorState>();

  Future<dynamic> navigateTo(String routeName) {
    return navigatorKey.currentState.pushNamed(routeName);
  }

  void goBack() {
    return navigatorKey.currentState.pop();
  }
复制代码

我们将NavigationService与应用程序链接的方式,通过navigatorKey提供给MaterialApp。转到main.dart文件并设置navigatorKey:

 MaterialApp(
        title: 'Flutter Demo',
        navigatorKey: NavigationService().navigatorKey,
        ...
        )
复制代码

然后写一个viewModel,尝试导航:

class ViewModel {
  final NavigationService _navigationService = NavigationService();

  Future goPage1() async{
  	/// 模拟请求数据后调到首页
	  await Future.delayed(Duration(seconds: 1));
	  _navigationService.navigateTo(page1);
  }

}
复制代码

在page6里使用viewModel导航:

  onPressed: () {
          viewModel.goPage1();
        },
复制代码

现在,将View文件的职责带回到了“显示UI”并将用户操作传递给模型,而不是“显示UI”将用户操作传递给模型并进行导航。更符合MVVM职责的划分。

这样做的好处是,随着导航逻辑的扩展,我们的UI将保持不变,并且模型将承载所有逻辑/状态管理。

代码链接


作者:艾维码
链接:https://juejin.im/post/5ec6c152e51d4578644e87ad
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。