路由與導航
路由與導航
在原生的Android中,一個Activity稱為一個路由,在iOS中指的是一個ViewController。如果需要開啟一個新的路由,在Android中可以使用startActivity()方法,並且傳入引數。在iOS中可以使用pushViewController()。
而在Flutter中,使用了Router與Navigator來組成統一的管理,Router是頁面的一個抽象概念,用這個可以建立介面、接收引數以及響應Navigator的開啟和關閉;而Navigator則是用於管理和維護路由棧,開啟路由頁面或者關閉路由頁面,即入棧出棧操作。
在Flutter中,路由分為兩種,一種是基本路由,一種是命名路由。
- 基本路由:無需提前註冊,在切換頁面的時候需要手動構造頁面的例項。
- 命名路由:需要提前註冊路由頁面識別符號,在頁面切換時通過路由識別符號開啟一個新的路由頁面。
基本路由
如果需要開啟一個新的頁面,需要建立一個MaterialPageRoute物件例項,然後呼叫Navigator.push(),那麼Flutter就會把新跳轉的頁面放到棧頂。如果需要關閉新的頁面,那麼使用Navigator.pop()即可。一個樣例程式碼如下:
first_page.dart
import 'package:flutter/material.dart'; import 'second_page.dart'; class FirstPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text("first page"),), body: Center( child: TextButton( child: Text("turn to second page"), onPressed: () => Navigator.push( context, MaterialPageRoute(builder: (context) => SecondPage())), ), ), ); } }
second_page.dart
import 'package:flutter/material.dart'; class SecondPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text("second page"),), body: Center( child: TextButton( child: Text("back to first page"), onPressed: () => Navigator.pop(context), ), ), ); } }
main.dart
import 'package:flutter/material.dart';
import 'package:flutter_router_page/first_page.dart';
void main(List<String> args) => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(home: FirstPage(),);
}
}
這樣就可以做成一個簡單的路由跳轉了。建立新的路由物件使用的是MaterialPageRoute類,該類是PageRoute的子類,定義了路由建立及切換時過渡動畫的相關介面和屬性,並且自帶頁面切換動畫。
命名路由
為了必要頻繁的建立MaterialPageRoute例項,可以使用命名路由的方式。在命名路由中,需要提前建立好對映規則,即路由表。對應路由表中,第一個引數對應頁面的名字,第二個引數對應頁面,且需要以MaterialApp為根。
main.dart
import 'package:flutter/material.dart';
import 'package:flutter_namerouter_page/first_page.dart';
import 'package:flutter_namerouter_page/second_page.dart';
import 'package:flutter_namerouter_page/unknown_page.dart';
void main(List<String> args) => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
/// 路由表結構
routes: {
'first': (context) => FirstPage(),
'second': (context) => SecondPage(),
},
/// 初始化頁面
initialRoute: 'first',
/// 路由表中沒有的頁面會跳轉到的地方
onUnknownRoute: (RouteSettings setting) =>
MaterialPageRoute(builder: (context) => UnKnownPage()),
);
}
}
first_page.dart
import 'package:flutter/material.dart';
class FirstPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("first page"),
),
body: Center(
child: TextButton(
child: Text("turn to second page"),
onPressed: () => {Navigator.pushNamed(context, "second")},
),
),
);
}
}
second_page.dart
import 'package:flutter/material.dart';
class SecondPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("second page"),
),
body: Center(
child: Column(
children: [
TextButton(
child: Text("back to first page"),
onPressed: () => {Navigator.pop(context)},
),
TextButton(
child: Text("turn to first page"),
onPressed: () => Navigator.pushNamed(context, 'first'),
),
TextButton(
onPressed: () => Navigator.pushNamed(context, 'third'),
child: Text("turn to third page")),
],
)),
);
}
}
unknown.dart
import 'package:flutter/material.dart';
class UnKnownPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("unknown page"),
),
body: Center(
child: TextButton(
child: Text("unknown page"),
onPressed: () => Navigator.pop(context),
),
),
);
}
}
路由傳參
在Flutter中,路由類似於Activity一樣。在Android中進行頁面傳參,使用的是Intent進行傳遞,然後使用startActivityForResult()進行傳參。
在Flutter中,使用Navigator.of(context).pushNamed('頁面名稱', arguments: 傳遞的資料).then((value){如果是StatefulWidget的話,那麼可以在此使用setState進行設定。})
在第二個頁面中,使用 ModalRoute.of(context).settings.arguments
獲取頁面傳輸過來的引數,類似於Android中的getIntent() ,然後進行轉換。在第二個頁面中,可以使用 Navigator.pop(context,'傳遞的引數')
路由棧
在Android中,Activity具有四種不同的啟動模式,standard,singleTask,singleTop,singleInstance。在Flutter中,使用了安卓中的啟動模式的管理方式。
在Flutter中,可以單獨移除路由棧中的一個特定的頁面。使用Navigator.removeRoute() & Navigator.removeRouteBelow()
進行移除。
方式1: push() & pushNamed() => pop()
這種方式類似於Android中的standard的模式,模型圖如下:
使用push或者pushNamed後的模型圖
使用pop後的模型圖
方式2: pushReplacement() & pushReplacementNamed()
這種方式用在路由棧頂頁面的替換場景,比如棧頂是b,想替換成c,那麼使用該函式即可。類似於Android中先把棧頂彈出,然後再放入c路由。這種模型的方式所展現的動畫僅僅是路由c的進入動畫,並沒有b的退出動畫。模型圖如下:
方式3: popAndPushNamed()
這種方式類似於方式2,但是在動畫方面有不一樣的地方。使用這種方式,在b彈出的時候,會顯示b的彈出動畫,而c進入的時候,又回顯示c的進入動畫。比方式2多一個彈出動畫。
方式4: pushAndRemoveUntil() & pushNamedAndRemoveUntil()
從路由棧中,新新增一個頁面,並且刪除路由棧中所有之前的路由。也就是說,使用這種方式進行路由的新增,路由棧中僅有一個路由。也可以使用這個方法,進行開啟一個新的頁面,並且制定路由棧中的某一個頁面以上的路由頁面都刪除。
比如執行這條語句Navigator.pushNamedAndRemoveUtil(context,'page_e',ModalRoute.withName('page_b'))
意思是開啟一個d頁面,然後刪除b頁面以上的頁面,示意圖如下:
方式5: popUntil()
一直彈出,直到某一個頁面位置,沒有push的操作。Navigator.popUtil(context,ModalRoute.withName('page_a'))
自定義路由
如果在兩個路由之間跳轉,需要自定義動畫,那麼會使用到自定義路由。自定義路由需要繼承PageRouteBuilder類。該類是所有自定義路由的基類。在PageRouteBuilder中,有幾個屬性比較重要,如下:
編號 | 名稱 | 作用 |
---|---|---|
1 | pageBuilder | 用來建立所需要跳轉的路由頁面 |
2 | opaque | 是否需要遮擋整個頁面 |
3 | transitionsBuilder | 用於自定義專場動畫 |
4 | transitionDuration | 自定義專場動畫的執行時間 |