Route And Navigator

管理多个用户界面有两个核心概念和类:路由(Route)和导航器(Navigator),路由(Route)是应用程序的“屏幕”或“页面”的抽象,导航器(Navigator)是管理路由的控件。导航器(Navigator)可以推送(push)和弹出(pop)路由来帮助用户从当前屏幕移动到另一个屏幕。

Name Routes

一开始在创建App的时候定义route属性(MaterialApp有此属性),然后就可以在程序中使用Navigator.pushNamed来跳转。

这种路由的缺点是不能传递参数。

文档对route属性的解释

The application's top-level routing table.

应用程序的顶级路由表.

When a named route is pushed with Navigator.pushNamed, the route name is looked up in this map. If the name is present, the associated WidgetBuilder is used to construct a MaterialPageRoute that performs an appropriate transition, including Hero animations, to the new route.

注意上面第一句话

If the app only has one page, then you can specify it using home instead.

If home is specified, then it implies an entry in this table for the Navigator.defaultRouteName route (/), and it is an error to redundantly provide such a route in the routes table.

If a route is requested that is not specified in this table (or by home), then the onGenerateRoute callback is called to build the page instead.

The Navigator is only built if routes are provided (either via home, routes, onGenerateRoute, or onUnknownRoute); if they are not, builder must not be null.

main.dart

import 'package:flutter/material.dart';
import 'home.dart';
import 'about.dart';
import 'contact.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Nav Demo 3',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new MyHomePage(),
      routes: <String, WidgetBuilder>{
        '/home': (BuildContext context) => new MyHomePage(),
        '/about': (BuildContext context) => new AboutPage(),
        '/contact': (BuildContext context) => new ContactPage(),
      },
    );
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

home.dart

import 'package:flutter/material.dart';

class MyHomePage extends StatefulWidget {
  
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  
  Widget build(BuildContext context) {
    return new Scaffold(
        appBar: new AppBar(
          title: new Text("Navigation Demo"),
          backgroundColor: Colors.deepOrangeAccent,
        ),
        drawer: new Drawer(
          child: new ListView(
            children: <Widget>[
              new ListTile(
                title: new Text("WELCOME"),
              ),
              new Divider(),
              new ListTile(
                  title: new Text("About"),
                  trailing: new Icon(Icons.info),
                  onTap: () {
                    Navigator.of(context).pop();
                    Navigator.of(context).pushNamed('/about');
                  }),
              new ListTile(
                  title: new Text("Contact"),
                  trailing: new Icon(Icons.phone),
                  onTap: () {
                    Navigator.of(context).pop();
                    Navigator.of(context).pushNamed('/contact');
                  }),
            ],
          ),
        ),
        body: new Center(
          child: new Text("Home Page", style: new TextStyle(fontSize: 35.0)),
        ));
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

传参

Navigator.push(context, new MaterialPageRoute(builder: (BuildContext context){
   return new ThirdPage(title:"请输入昵称");
}))
1
2
3

MaterialApp and WidgetsApp

WidgetsApp是没有material风格支持的app。如果想要一个非material或者ios风格的App,可以用WidgetsApp

MaterialApp is a widget that introduces many interesting tools such as Navigator or Theme to help you develop your app.

可以认为MaterialApp基于WidgetsApp

关键对比-相同属性

字段 类型
navigatorKey(导航键) GlobalKey<NavigatorState>
onGenerateRoute(生成路由的回调函数,当导航的命名路由的时候,会使用这个来生成界面) RouteFactory
onUnknownRoute(未知路由) RouteFactory
navigatorObservers(导航观察器) List<NavigatorObserver>
initialRoute(初始路由,默认值为 Window.defaultRouteName) String
builder(建造者) ransitionBuilder
title(在任务管理窗口中所显示的应用名字) String
onGenerateTitle(生成标题) GenerateAppTitle
color(应用的主要颜色值(primary color),也就是安卓任务管理窗口中所显示的应用颜色) Color
locale(本地化) Locale
localizationsDelegates(本地化委托) Iterable<LocalizationsDelegate<dynamic>>
localeResolutionCallback) LocaleResolutionCallback
supportedLocales(支持本地化列表) Iterable<Locale>
showPerformanceOverlay(显示性能标签,https://flutter.io/debugging/#performanceoverlay) bool
checkerboardRasterCacheImages bool
checkerboardOffscreenLayers bool
showSemanticsDebugger) bool
debugShowCheckedModeBanner) bool

关键对比-MaterialApp特有属性

字段 类型
home(应用默认所显示的界面 Widget) Widget
routes(应用的顶级导航表格) Map<String, WidgetBuilder>
theme(应用各种 UI 所使用的主题颜色) ThemeData
debugShowMaterialGrid(是否显示 纸墨设计 基础布局网格,用来调试 UI 的工具) bool

WidgetsApp特有属性

字段 类型
textStyle TextStyle
debugShowWidgetInspector bool
inspectorSelectButtonBuilder) InspectorSelectButtonBuilder

MaterialApp相关属性

materialApp初始化

-[home], [routes], [onGenerateRoute], or [builder] 至少一个非空. 如果设置了[route], 那么必须包含有 [Navigator.defaultRouteName] (/)的入口, 这是应用程启动时候在指定一个不支持的路由的情况下试图使用的路由。 -[routes]和[navigatorObservers]不能为空l.

home和routes

home是默认根路由,也就是'/' routes是路由表. 如果没有home。那么routes中要有“/”路由(Navigator.defaultRouteName

initialRoute

app运行后进入的初始路由,如果没有设置,则进入home

onGenerateRoute

如果所查找的路由在 routes 中不存在,则会通过 onGenerateRoute 来查找

home,routes,onGenerateRoute,onUnknownRoute之一不为空

  void _updateNavigator() {
    if (widget.home != null ||
        widget.routes.isNotEmpty ||
        widget.onGenerateRoute != null ||
        widget.onUnknownRoute != null) {
      _navigatorObservers = List<NavigatorObserver>.from(widget.navigatorObservers)
        ..add(_heroController);
    } else {
      _navigatorObservers = null;
    }
  }
1
2
3
4
5
6
7
8
9
10
11
  • pop:不管三七二十一,出栈
  • push:入栈
  • canPop,检测是否已经是初始路由了,如果是则不能pop,返回false
  • maybePop,去尝试pop,如果能pop就pop
  • pushReplacementNamed,入栈,替换调最后一个入栈的路由。可以用在登录成后,替换登录路由为登录成功后的默认路由
  • popAndPushNamed: 出栈最后一个入栈的路由,然后再push。与pushReplacementNamed有动画效果上的差异,结果是一样的。
  • pushNamedAndRemoveUntil:删除之前的全部路由,可以用在注销。例如,用户已经完成了付款,那么所有与付款、购物车有关的页面应该从栈中删除,把用户导航到付款完成的页面。
  • popUntil:删除到某一个路由之后入栈的全部路由。比如注册分了3多个界面,但是到某一个步,用户不想注册了,点击了取消,那么就可以使用popUntil

/// 删除全部路由
Navigator.of(context)
    .pushNamedAndRemoveUntil('/login', (Route<dynamic> route) => false);
    
/// 假设当前入栈路由为screen1,screen2,screen3,下面代码运行后,入栈路由为screen1,screen4

Navigator.of(context).pushNamedAndRemoveUntil('/screen4', ModalRoute.withName('/screen1'));
1
2
3
4
5
6
7
8

例程说明

home和routes

不能运行,没有home,route中没有"/":

void main() {
  runApp(
      new MaterialApp(
        routes: <String, WidgetBuilder> {
          '/screen1': (BuildContext context) => new Screen1(),
          '/screen2' : (BuildContext context) => new Screen2(),
          '/screen3' : (BuildContext context) => new Screen3(),
          '/screen4' : (BuildContext context) => new Screen4()
        },
      )
  );
}
1
2
3
4
5
6
7
8
9
10
11
12

可以运行


void main() {
  runApp(
      new MaterialApp(
        routes: <String, WidgetBuilder> {
          '/': (BuildContext context) => new Screen1(),
          '/screen1': (BuildContext context) => new Screen1(),
          '/screen2' : (BuildContext context) => new Screen2(),
          '/screen3' : (BuildContext context) => new Screen3(),
          '/screen4' : (BuildContext context) => new Screen4()
        },
      )
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

也可以运行

void main() {
  runApp(
      new MaterialApp(
        home: new Screen1(),
        routes: <String, WidgetBuilder> {
          '/screen1': (BuildContext context) => new Screen1(),
          '/screen2' : (BuildContext context) => new Screen2(),
          '/screen3' : (BuildContext context) => new Screen3(),
          '/screen4' : (BuildContext context) => new Screen4()
        },
      )
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13

不能运行,因为有了home,就不需要在routes中设‘/’

void main() {
  runApp(
      new MaterialApp(
        home: new Screen1(),
        routes: <String, WidgetBuilder> {
          '/': (BuildContext context) => new Screen2(),
          '/screen1': (BuildContext context) => new Screen1(),
          '/screen2' : (BuildContext context) => new Screen2(),
          '/screen3' : (BuildContext context) => new Screen3(),
          '/screen4' : (BuildContext context) => new Screen4()
        },
      )
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

initialRoute

进入Screen2,这是定义的初始路由。

void main() {
  runApp(
      new MaterialApp(
        home: new Screen1(),
        initialRoute: "/screen2",
        routes: <String, WidgetBuilder> {
          '/screen1': (BuildContext context) => new Screen1(),
          '/screen2' : (BuildContext context) => new Screen2(),
          '/screen3' : (BuildContext context) => new Screen3(),
          '/screen4' : (BuildContext context) => new Screen4()
        },
      )
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

如果initialRoute是一个不存在的路由,也能运行,只不过界面是home定义的。

void main() {
  runApp(new MaterialApp(
      home: new Screen1(),
      initialRoute: "/screen7",
      routes: <String, WidgetBuilder>{
        '/screen1': (BuildContext context) => new Screen1(),
        '/screen2': (BuildContext context) => new Screen2(),
        '/screen3': (BuildContext context) => new Screen3(),
        '/screen4': (BuildContext context) => new Screen4()
      },
    )
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13

在同时使用home和initialRoute时,当界面进入initialRoute定义的路由,如果initialRoute与home不同,这时候navigator的_history栈里会有2条路由,一条为"/",一条为initialRoute定义的.

onGenerateRoute

如果在routes中没有找到路由,则使用onGenerateRoute回调建立界面。

void main() {
  runApp(new MaterialApp(
      home: new Screen1(),
      initialRoute: "/screen7",
      routes: <String, WidgetBuilder>{
        '/screen1': (BuildContext context) => new Screen1(),
        '/screen2': (BuildContext context) => new Screen2(),
        '/screen3': (BuildContext context) => new Screen3(),
        '/screen4': (BuildContext context) => new Screen4()
      },
      onGenerateRoute: (RouteSettings settings) {
        return MaterialPageRoute<void>(
          settings: settings,
          builder: (BuildContext context) =>
              Scaffold(body: Center(child: Text(settings.toString()))),
        );
      }));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

onUnknownRoute

类似WEB 404,onGenerateRoute也找不到的路由,会使用这个回调来构建。

void main() {
  runApp(new MaterialApp(
      home: new Screen1(),
      initialRoute: "/screen1",
      routes: <String, WidgetBuilder>{
        '/screen1': (BuildContext context) => new Screen1(),
        '/screen2': (BuildContext context) => new Screen2(),
        '/screen3': (BuildContext context) => new Screen3(),
        '/screen4': (BuildContext context) => new Screen4()
      },
      onUnknownRoute: (RouteSettings settings) {
        return MaterialPageRoute<void>(
          settings: settings,
          builder: (BuildContext context) =>
              Scaffold(body: Center(child: Text('Not Found'))),
        );
      },
    )
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20