Flutter Navigator基础使用

Navigator的使用分为两部分:

本节说一些基本操作,如果你已经掌握了Navigator的基本跳转姿势,请移步Flutter Navigator品如用法

1.路由最基本的使用-跳转和退出

大多数App都包含了许多页面用来呈现不同种类的信息。例如:一个app可能存在“商品列表页”,当点击item的时候又会跳转到对应“商品详情”页面。

在Flutter中,页面被称之为route(路由)。在android中路由等同于Activity,ios中路由等同于ViewController,然后在Flutter中路由只是一个widget。

两个路由之间的跳转需要经历下面三个步骤

  1. 创建两个route
  2. Navigate到第二个route使用Navigator.push()
  3. 返回到第一个route使用Navigator.pop()

1.1创建两个路由

首先创建两个页面,由于这只是为了演示最基本的导航功能,所以每个页面只包含一个按钮。点击第一个页面的按钮跳转到第二个页面,点击第二个页面的按钮返回到第一个页面

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: () {
            // 点击跳转到第二个route
          },
        ),
      ),
    );
  }
}

class SecondRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Second Route"),
      ),
      body: Center(
        child: RaisedButton(
          onPressed: () {
            // 点击回到第一个route
          },
          child: Text('Go back!'),
        ),
      ),
    );
  }
}

1.2.使用Navigator.push()来跳转页面

我们使用Navigator.push()来切换页面。这个方法会调价一个新的Route到route栈中,而这个栈是由Navigator来管理的。那么路由栈是从何而来呢?你可以船舰你自己的路由栈,或者直接使用系统提供的MaterialPageRoute。这个组件可以很方便的实现路由跳转动画。

在FirstRoute组件中的onPress方法中,添加相应的跳转代码

onPressed: () {
  Navigator.push(
    context,
    MaterialPageRoute(builder: (context) => SecondRoute()),
  );
}

1.3.使用Navigator.pop()来返回上一个页面

我们使用Navigator.pop()来退出SecondRoute并回到FirstRoute,pop()方法回将当前路由从Route栈中移除

在SecondRoute组件中的onPress方法中,添加相应的返回代码

onPressed: () {
  Navigator.pop(context);
}

完整的代码如下:

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    title: 'Navigation Basics',
    home: FirstRoute(),
  ));
}

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: () {
            Navigator.push(
              context,
              MaterialPageRoute(builder: (context) => SecondRoute()),
            );
          },
        ),
      ),
    );
  }
}

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

2.通过Named Routes(路由表)来做跳转

在第一节中我们学会了如何使用Navigator来做跳转和退出。然而如果我们的项目中出现了多次跳转到同一个页面的情况,这种方式会造成大量代码的重复。解决方案就是使用Named Route。

为了使用Named Routes,我们要使用Navigator.pushNamed()函数,我们还是引用第一节的例子,只是步骤上有细微的差别。

  1. 创建两个route
  2. 定义创建的route
  3. 跳转到SecondRoute使用Navigator.pushNamed()
  4. 返回到第一个页面使用Navigator.pop()

2.1.创建两个Route

和第一节中的代码一模一样,这里不在赘述

2.2.定义创建的route

我们需要在MaterialApp的构造方法中添加两个额外的属性:initialRouteroutes

initialRoutes表示起始的route(可以理解为app打开后显示的第一个页面),routes表示除开initialRoute外的所有route。具体代码如下

MaterialApp(
  // 定义APP启动时第一个显示的页面,在本例中,initialRoute指代FirstScreen
  initialRoute: '/',
  routes: {
    // 当navigating到‘/’ route时,构建FirstScreen widget
    '/': (context) => FirstScreen(),
    // 当navigating 到"/second" route, 构建SecondScreen widget.
    '/second': (context) => SecondScreen(),
  },
);

可以看出使用Named Routes的时候只是给每个Route定义了一个别名,这样我们就可以根据这个别名来进行跳转了

2.3.Navigate到SecondScreen

使用 Navigator.pushNamed() 来跳转,当这个方法被调用的时候,Flutter会去定义的routes中转到别名所对应的route,并进行跳转

onPressed: () {
  // 跳转到SecondScreen
  Navigator.pushNamed(context, '/second');
}

2.4退出页面

使用Navigator.pop()

onPressed: () {
  // 从第二个页面返回
  Navigator.pop(context);
}

完整的代码如下:

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    title: 'Named Routes Demo',
    initialRoute: '/',
    routes: {
      '/': (context) => FirstScreen(),
      '/second': (context) => SecondScreen(),
    },
  ));
}

class FirstScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('First Screen'),
      ),
      body: Center(
        child: RaisedButton(
          child: Text('Launch screen'),
          onPressed: () {
            Navigator.pushNamed(context, '/second');
          },
        ),
      ),
    );
  }
}

class SecondScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Second Screen"),
      ),
      body: Center(
        child: RaisedButton(
          onPressed: () {
            Navigator.pop(context);
          },
          child: Text('Go back!'),
        ),
      ),
    );
  }
}

3.在Named Route中传值

前面两节只是说了如何跳转和退出,但是在实际项目开发中肯定是少不了页面之间数据交互的问题。

Navigator.pushNamed()并不是一个空参函数,一下是该方法的源码

@optionalTypeArgs
  static Future<T> pushNamed<T extends Object>(
    BuildContext context,
    //路由路径
    String routeName, {
    //携带的参数
    Object arguments,
   }) {
    return Navigator.of(context).pushNamed<T>(routeName, arguments: arguments);
  }

arguments是跳转携带的参数,它是一个Object,意味着可以传递任意类型的参数,但是跳转到下一个页面后如何把这个值取出来呢,貌似没有相应的“key”呢。下面介绍完整的传值和取值步骤

  1. 准备好你要传递的数据
  2. 创建一个页面来获取传递的数据
  3. 在routes表中注册这个页面
  4. 跳转

3.1.准备传递的数据

首先,我们定义好要传递的数据。这里我们传递两个参数,title和message,并将它们封装到一个Bean中

class ScreenArguments {
  final String title;
  final String message;

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

3.2.创建一个页面来接受传递的数据

创建一个新的页面来接受传递的参数并显示。至于我们上面提到的如何来获取arguments,我们使用ModalRoute.of()就可以啦。这个方法3可以拿到当前路由携带的参数。

class ExtractArgumentsScreen extends StatelessWidget {
  static const routeName = '/extractArguments';

  @override
  Widget build(BuildContext context) {
    //获取传递的参数
    final ScreenArguments args = ModalRoute.of(context).settings.arguments;

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

3.3.注册新的页面

当然啦,使用pushNamed跳转肯定要在routes表中注册该页面,所以把ExtractArgumentsScreen注册进去

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

3.4跳转

一切准备就绪,我们使用pushNamed来实现跳转,并将准备好的参数传递过去

RaisedButton(
  child: Text("使用pushNamed携带参数跳转"),
  onPressed: () {
    //点击事件,触发跳转
    Navigator.pushNamed(
      context,
      ExtractArgumentsScreen.routeName,
      arguments: ScreenArguments(
        '我是被传递的title',
        '我是被传递的message.',
      ),
    );
  },
);

引申:从3.2节我们得知ModalRoute.of()可以获取传递过来的参数,但是我们的例子中是使用的pushNamed传递的参数,如果使用Navigator.push()传递参数能获取到嘛?例如这样:

Navigator.push(
    context,
    MaterialPageRoute(
        builder: (context) => ExtractArgumentsScreen(),
        settings: RouteSettings(
            arguments: ScreenArguments(
                '我是title',
                '我是message.',
             ),
         ),
     ),
   );

先给出答案:这两种方式传值都可以用ModalRoute.of()来取值,因为这两种跳转方式最终都会调用Future<T> push<T extends Object>(Route<T> route),有兴趣的可以去查看源码,这里不多赘述了。

3.5.跳转的另外一种方式

除了我们以上所说的跳转方式,我们也可以使用onGenerateRoute来进行跳转传值。

在本例中我们传递的值依然是title和message,并且封装成一个ScreenArguments实体类,接受参数的页面需要增加一个构造方法来接收这两个值

class PassArgumentsScreen extends StatelessWidget {
  static const routeName = '/passArguments';

  final String title;
  final String message;

  //定义一个构造方法
  const PassArgumentsScreen({
    Key key,
    @required this.title,
    @required this.message,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Text(message),
      ),
    );
  }
}

在MaterialApp中添加onGnerateRoute属性

MaterialApp(
  
  onGenerateRoute: (settings) {
    // 判断当前route,分别进行处理
    if (settings.name == PassArgumentsScreen.routeName) {
      // 将settings.arguments转换为正确的类型 ScreenArguments.
      final ScreenArguments args = settings.arguments;

      //通过构造方法传值
      return MaterialPageRoute(
        builder: (context) {
          return PassArgumentsScreen(
            title: args.title,
            message: args.message,
          );
        },
      );
    }
  },
);

然后再来看一下使用onGenerateRoute方式做跳转的代码

Navigator.push(
    context,
    MaterialPageRoute(
        builder: (context) => ExtractArgumentsScreen(),
        settings: RouteSettings(
            arguments: ScreenArguments(
                '我是title',
                '我是message.',
             ),
         ),
     ),
   );

4.携带参数退出

我们已经学习了如何跳转,退出,携带参数跳转,接收传递的参数。但是当一个页面退出的时候如何携带参数呢?例如我们打开了一个联系人选择页面,点击一个item后页面退出,我们需要知道选择的联系人信息是什么。

在前面我们说到退出页面只需要调用Navigator.pop(context)就行了,实际上这个方法还有一个可选参数,一起来看下完整的代码

@optionalTypeArgs
static bool pop<T extends Object>(BuildContext context, [ T result ]) {
  return Navigator.of(context).pop<T>(result);
}

这里的第二个参数T就是退出页面时携带的参数,下面我们来详细看一下Navigator.pop()的用法

  1. 创建一个页面,名叫HomeScreen并添加一个按钮跳转到SelectionScreen
  2. 创建SelectionScreen,这个页面包含两个按钮,点击后的效果都是退出页面并携带不同的返回值
  3. 在HomeScreen中接收返回值,并用一个Snackbar显示

4.1 创建一个页面,名叫HomeScreen并添加一个按钮跳转到SelectionScreen

class SelectionButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return RaisedButton(
      onPressed: () {
        _navigateAndDisplaySelection(context);
      },
      child: Text('Pick an option, any option!'),
    );
  }

  //点击跳转到SelectionScreen并接收返回值
  _navigateAndDisplaySelection(BuildContext context) async {
    // Navigator.push returns a Future that completes after calling
    // Navigator.pop on the Selection Screen.

//调用Navigator.push()会返回一个Furture,根据这个Future可以接收页面返回的参数,这个下面再讲
    final result = await Navigator.push(
      context,
      // Create the SelectionScreen in the next step.
      MaterialPageRoute(builder: (context) => SelectionScreen()),
    );
  }
}

4.2 创建SelectionScreen,这个页面包含两个按钮,点击后的效果都是退出页面并携带不同的返回值

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: () {
                  // 退出页面,并携带一个返回值‘Yep!' 
                  Navigator.pop(context, 'Yep!');
                },
                child: Text('Yep!'),
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: RaisedButton(
                onPressed: () {
                  // 退出页面,并携带一个返回值 "Nope"
                  Navigator.pop(context, 'Nope!');
                },
                child: Text('Nope.'),
              ),
            )
          ],
        ),
      ),
    );
  }
}

4.3 在HomeScreen中接收返回值,并用一个Snackbar显示

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

  String value = result as String;
  Scaffold.of(context)
    ..removeCurrentSnackBar()
    ..showSnackBar(SnackBar(content: Text("$value")));
}

发布了21 篇原创文章 · 获赞 21 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/u013894711/article/details/100600139