Flutter开发05-路由

前言

管理多个页面时有两个核心概念和类:RouteNavigator

一个route是一个屏幕或页面的抽象,Navigator是管理route的Widget。

Navigator可以通过route入栈和出栈来实现页面之间的跳转。

Flutter的路由有两种方式

  • 基本路由
  • 命名路由

基本路由就相当于Android和iOS原生的页面跳转方式。

命名路由就相当于VUE的Router插件一样,这种方式耦合性更低,功能更强大。

在一个项目中两种方式是可以同时使用的,推荐使用命名路由的方式,项目的结构看起来比较清晰。

基本路由

跳转

1
2
3
4
Navigator.push(context,
MaterialPageRoute(builder: (context) {
return HomePage();
}));

替换形式跳转

适用于登录后跳转到主页面 不能再返回到登录页面

1
2
3
4
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) {
return HomePage();
}));

关闭页面

1
Navigator.pop(context);

除了页面关闭用这个方法,窗口的关闭也是用这个方法,因为Flutter的Dialog的实现方式就是基于路由的。

返回根路由

1
2
3
4
Navigator.of(context).pushAndRemoveUntil(
new MaterialPageRoute(builder: (context) => new HomePage()),
(route) => route == null
);

路由传值

页面

1
2
3
4
5
6
7
8
class MyHomePage extends StatefulWidget {
// 类的构造器,用来接收传递的值
MyHomePage({Key key, this.title}) : super(key: key);
final String title; // 用来储存传递过来的值

@override
_MyHomePageState createState() => new _MyHomePageState();
}

传入值的方式

1
new MyHomePage(title: '带参数跳转')

命名路由

路由定义与初始化

路由定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import 'package:flutter/material.dart';

final Map<String, Function> routes = {
'/': (context) => LoginPage(),
'/home': (context) => HomePage(),
'/search': (context,{arguments}) => SearchPage(arguments: arguments),
};

var onGenerateRoute = (RouteSettings settings) {
final String name = settings.name;
final Function pageContentBuilder = routes[name];
if (pageContentBuilder != null) {
if (settings.arguments != null) {
final Route route = MaterialPageRoute(
builder: (context) =>
pageContentBuilder(context, arguments: settings.arguments));
return route;
} else {
final Route route =
MaterialPageRoute(builder: (context) => pageContentBuilder(context));
return route;
}
}
};

两种路由

  • iOS页面跳转风格 CupertinoPageRoute

  • Android页面跳转风格 MaterialPageRoute

初始化路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import 'package:flutter/material.dart';
import 'route/MyRoutes.dart';

void main() {
runApp(MyApp());
}

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
initialRoute: '/',
onGenerateRoute: onGenerateRoute);
}
}

路由传值

接收参数页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import 'package:flutter/material.dart';

class SearchPage extends StatelessWidget {

final arguments;
SearchPage({this.arguments});

@override
Widget build(BuildContext context) {
return Scaffold(
appBar:AppBar(
title: Text("搜索页面"),
) ,
body: Text("搜索页面内容区域${arguments != null ? arguments['keyword'] : ''}"),
);
}
}

跳转传参

1
2
3
4
//路由跳转
Navigator.pushNamed(context, '/search',arguments: {
"keyword":"资讯"
});

页面跳转

不带参数

1
Navigator.pushNamed(context, "/home");

带参数

1
Navigator.pushNamed(context, '/search', arguments: {"id": 20});

替换形式跳转

1
Navigator.pushReplacementNamed(context, "/home");

关闭页面

1
Navigator.pop(context);

除了页面关闭用这个方法,窗口的关闭也是用这个方法,因为Flutter的Dialog的实现方式就是基于路由的。

返回根路由

1
2
3
4
5
6
7
//flutter 登录后跳转到根路由
Navigator.of(context).pushNamedAndRemoveUntil(
'/home',
(route) => false,//true 保留当前栈 false 销毁所有 只留下HomePage
arguments: {
},
);

导航返回拦截

为了避免用户误触返回按钮而导致APP退出,在很多APP中都拦截了用户点击返回键的按钮,然后进行一些防误触判断,比如当用户在某一个时间段内点击两次时,才会认为用户是要退出(而非误触)。Flutter中可以通过WillPopScope来实现返回按钮拦截,我们看看WillPopScope的默认构造函数:

1
2
3
4
5
const WillPopScope({
...
@required WillPopCallback onWillPop,
@required Widget child
})

onWillPop是一个回调函数,当用户点击返回按钮时被调用(包括导航返回按钮及Android物理返回按钮)。该回调需要返回一个Future对象,如果返回的Future最终值为false时,则当前路由不出栈(不会返回);最终值为true时,当前路由出栈退出。我们需要提供这个回调来决定是否退出。

示例

为了防止用户误触返回键退出,我们拦截返回事件。当用户在1秒内点击两次返回按钮时,则退出;如果间隔超过1秒则不退出,并重新记时。

代码如下:

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
import 'package:flutter/material.dart';

class LoginPage extends StatefulWidget {
@override
LoginPageState createState() {
return new LoginPageState();
}
}

class LoginPageState extends State<LoginPage> {
DateTime _lastPressedAt; //上次点击时间

@override
Widget build(BuildContext context) {
return new WillPopScope(
onWillPop: () async {
if (_lastPressedAt == null ||
DateTime.now().difference(_lastPressedAt) > Duration(seconds: 1)) {
//两次点击间隔超过1秒则重新计时
_lastPressedAt = DateTime.now();
return false;
}
return true;
},
child: Scaffold(
body: Text("这是登录页面"),
)
);
}
}