Flutter路由官方文档学习笔记

在Flutter中,screen和pages都叫做路由(routes)。一个route就是一个widget,使用Navigator导航到新的一个route。

1.导航到新的页面并返回

两个route之间的导航由以下步骤:

  • 创建两个route
  • 使用Navigator.push()导航到第二个route
  • 使用Navigator.pop()返回到第一个route

1.1 创建两个routes

class FirstRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('First Route'),
      ),
      body: Center(
        child: RaisedButton(
          child: Text('Open route'),
          onPressed: () {
            // Navigate to second route when tapped.
          },
        ),
      ),
    );
  }
}

class SecondRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Second Route"),
      ),
      body: Center(
        child: RaisedButton(
          onPressed: () {
            // Navigate back to first route when tapped.
          },
          child: Text('Go back!'),
        ),
      ),
    );
  }
}

1.2 使用Navigator.push()导航到第二个route

// Within the `FirstRoute` widget
onPressed: () {
  Navigator.push(
    context,
    MaterialPageRoute(builder: (context) => SecondRoute()),
  );
}

1.3 使用Navigator.pop()返回到第一个route

// Within the SecondRoute widget
onPressed: () {
  Navigator.pop(context);
}

2. 使用命名routes进行导航

上面通过使用创建route并将其推送到导航器来导航到新的页面。

如果你的应用中需要由很多地方导航到同一个页面,如果在使用以上的方法那么代码就会重复。解决办法就是定义命名路由,通过命名路由进行导航。

使用命名路由步骤:

  • 创建两个页面
  • 定义routes
  • 使用Navigator.pushNamed()导航到第二个页面
  • 使用Navigator.pop()返回到第一个页面

2.1 创建两个页面

代码同上1.1

2.2 定义routes

通过MaterialApp构造函数提供的附加属性来定义routes:initialRoutes和routes它本身。

initialRoutes的属性定义应用程序从哪个route开始。
routes属性定义可用的命名路由以及导航到这些路由时要构建的widget。

MaterialApp(
  // Start the app with the "/" named route. In this case, the app starts
  // on the FirstScreen widget.
  initialRoute: '/',
  routes: {
    // When navigating to the "/" route, build the FirstScreen widget.
    '/': (context) => FirstScreen(),
    // When navigating to the "/second" route, build the SecondScreen widget.
    '/second': (context) => SecondScreen(),
  },
);

2.3 使用Navigator.pushNamed()导航到第二个页面

// Within the `FirstScreen` widget
onPressed: () {
  // Navigate to the second screen using a named route.
  Navigator.pushNamed(context, '/second');
}

2.4 使用Navigator.pop()返回到第一个页面

代码同上1.3

3.将参数传递给命名路由

Navigator提供了使用公共标识符从应用程序的任何地方传递给命名路由。

使用Navigator.pushNamed()方法的arguments参数完成参数传递。

以下步骤将参数传递给命名路由并使用ModalRoute.of()和onGenerateRoute()读取参数:

  • 定义需要传递的参数。
  • 创建一个提取参数的widget。
  • 在路由表中注册widget。
  • 导航到widget。

3.1 定义需要传递的参数

// You can pass any object to the arguments parameter.
// In this example, create a class that contains a customizable
// title and message.
class ScreenArguments {
  final String title;
  final String message;

  ScreenArguments(this.title, this.message);
}

3.2 创建一个提取参数的widget

创建一个widget从ScreenArguments获取并显示title和message。要访问ScreenArguments,请使用ModalRoute.of()方法。此方法返回带有参数的当前路由。

// A widget that extracts the necessary arguments from the ModalRoute.
class ExtractArgumentsScreen extends StatelessWidget {
  static const routeName = '/extractArguments';

  @override
  Widget build(BuildContext context) {
    // Extract the arguments from the current ModalRoute settings and cast
    // them as ScreenArguments.
    final ScreenArguments args = ModalRoute.of(context).settings.arguments;

    return Scaffold(
      appBar: AppBar(
        title: Text(args.title),
      ),
      body: Center(
        child: Text(args.message),
      ),
    );
  }
}

3.3 在路由表中注册widget

MaterialApp(
  routes: {
    ExtractArgumentsScreen.routeName: (context) => ExtractArgumentsScreen(),
  },     
);

3.4 导航到widget

// A button that navigates to a named route. The named route
// extracts the arguments by itself.
RaisedButton(
  child: Text("Navigate to screen that extracts arguments"),
  onPressed: () {
    // When the user taps the button, navigate to the specific route
    // and provide the arguments as part of the RouteSettings.
    Navigator.pushNamed(
      context,
      ExtractArgumentsScreen.routeName,
      arguments: ScreenArguments(
        'Extract Arguments Screen',
        'This message is extracted in the build method.',
      ),
    );
  },
);

使用onGenerateRoute提取参数

也可以在onGenerateRoute()函数中提取参数并将它们传递给widget,而不是直接在窗口小部件中提取参数。
onGenerateRoute()函数根据给定的RouteSettings创建正确的路由。

MaterialApp(
  // Provide a function to handle named routes. Use this function to
  // identify the named route being pushed, and create the correct
  // screen.
  onGenerateRoute: (settings) {
    // If you push the PassArguments route
    if (settings.name == PassArgumentsScreen.routeName) {
      // Cast the arguments to the correct type: ScreenArguments.
      final ScreenArguments args = settings.arguments;

      // Then, extract the required data from the arguments and
      // pass the data to the correct screen.
      return MaterialPageRoute(
        builder: (context) {
          return PassArgumentsScreen(
            title: args.title,
            message: args.message,
          );
        },
      );
    }
  },
);

4. 带数据从页面返回

有些时候我们需要从打开页面带数据返回到导航页面。

使用Navigator.pop()方法带回数据的步骤:

  • 定义主屏幕
  • 添加一个启动选择屏幕的按钮
  • 使用两个按钮显示选择屏幕
  • 点击按钮时,关闭选择屏幕
  • 在主屏幕上显示选择的snackbar

4.1 定义主屏幕

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Returning Data Demo'),
      ),
      // Create the SelectionButton widget in the next step.
      body: Center(child: SelectionButton()),
    );
  }
}

4.2 添加一个启动选择屏幕的按钮

创建SelectionButton按钮,它将做以下两件事:

  • 点击时打开选中页面
  • 等待选中页面返回结果
class SelectionButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return RaisedButton(
      onPressed: () {
        _navigateAndDisplaySelection(context);
      },
      child: Text('Pick an option, any option!'),
    );
  }

  // A method that launches the SelectionScreen and awaits the
  // result from Navigator.pop.
  _navigateAndDisplaySelection(BuildContext context) async {
    // Navigator.push returns a Future that completes after calling
    // Navigator.pop on the Selection Screen.
    final result = await Navigator.push(
      context,
      // Create the SelectionScreen in the next step.
      MaterialPageRoute(builder: (context) => SelectionScreen()),
    );
  }
}

4.3 使用两个按钮显示选择屏幕

现在,构建一个包含两个按钮的选择屏幕。当用户点击按钮时,该应用关闭选择屏幕并让主屏幕知道点击了哪个按钮。

class SelectionScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Pick an option'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: RaisedButton(
                onPressed: () {
                  // Pop here with "Yep"...
                },
                child: Text('Yep!'),
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: RaisedButton(
                onPressed: () {
                  // Pop here with "Nope"
                },
                child: Text('Nope.'),
              ),
            )
          ],
        ),
      ),
    );
  }
}

4.4 点击按钮时,关闭选择屏幕

现在,更新两个按钮的onPressed()回调。要将数据返回到第一个屏幕,请使用Navigator.pop()方法,该方法接受名为result的可选第二个参数。任何结果都会在SelectionButton中返回到Future。

Yep button
RaisedButton(
  onPressed: () {
    // The Yep button returns "Yep!" as the result.
    Navigator.pop(context, 'Yep!');
  },
  child: Text('Yep!'),
);
Nope button
RaisedButton(
  onPressed: () {
    // The Nope button returns "Nope!" as the result.
    Navigator.pop(context, 'Nope!');
  },
  child: Text('Nope!'),
);

4.5 在主屏幕上显示选择的snackbar

现在您正在启动选择屏幕并等待结果,您将需要对返回的信息执行某些操作。

_navigateAndDisplaySelection(BuildContext context) async {
  final result = await Navigator.push(
    context,
    MaterialPageRoute(builder: (context) => SelectionScreen()),
  );

  // After the Selection Screen returns a result, hide any previous snackbars
  // and show the new result.
  Scaffold.of(context)
    ..removeCurrentSnackBar()
    ..showSnackBar(SnackBar(content: Text("$result")));
}

5. 将数据发送到新页面

通常,您不仅要导航到新屏幕,还要将数据传递到屏幕。例如,您可能希望传递有关已点击的项目的信息

请记住:屏幕只是小部件。在此示例中,创建待办事项列表。轻触待办事项时,导航到显示有关待办事项信息的新屏幕(widget)。此配方使用以下步骤:

  • 定义待办事项类
  • 显示待办事项列表
  • 创建一个可以显示待办事项信息的详细信息屏幕
  • 导航并将数据传递到详细信息屏幕

5.1 定义待办事项类

首先,您需要一种简单的方式来表示待办事项。对于此示例,请创建一个包含两个数据的类:标题和说明。

class Todo {
  final String title;
  final String description;

  Todo(this.title, this.description);
}

5.2 显示待办事项列表

其次,显示待办事项列表。在此示例中,生成20个待办事项并使用ListView显示它们。

生成待办事项列表
final todos = List<Todo>.generate(
  20,
  (i) => Todo(
        'Todo $i',
        'A description of what needs to be done for Todo $i',
      ),
);
使用ListView显示待办事项列表
ListView.builder(
  itemCount: todos.length,
  itemBuilder: (context, index) {
    return ListTile(
      title: Text(todos[index].title),
    );
  },
);

5.3 创建一个可以显示待办事项信息的详细信息屏幕

现在,创建第二个屏幕。屏幕标题包含待办事项的标题,屏幕正文显示说明。

class DetailScreen extends StatelessWidget {
  // Declare a field that holds the Todo.
  final Todo todo;

  // In the constructor, require a Todo.
  DetailScreen({Key key, @required this.todo}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // Use the Todo to create the UI.
    return Scaffold(
      appBar: AppBar(
        title: Text(todo.title),
      ),
      body: Padding(
        padding: EdgeInsets.all(16.0),
        child: Text(todo.description),
      ),
    );
  }
}

5.4 导航并将数据传递到详细信息屏幕

有了DetailScreen,您就可以执行导航了。在此示例中,当用户点击列表中的待办事项时,导航到DetailScreen。将待办事项传递给DetailScreen。

ListView.builder(
  itemCount: todos.length,
  itemBuilder: (context, index) {
    return ListTile(
      title: Text(todos[index].title),
      // When a user taps the ListTile, navigate to the DetailScreen.
      // Notice that you're not only creating a DetailScreen, you're
      // also passing the current todo to it.
      onTap: () {
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (context) => DetailScreen(todo: todos[index]),
          ),
        );
      },
    );
  },
);

参考
flutter.dev Navigation
flutter_route sample

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342