我們通常會用屏(Screen)來稱呼一個頁面(Page),一個完整的App應(yīng)該是有多個Page組成的无宿。
在之前的案例(豆瓣)中,我們通過IndexedStack來管理了首頁中的Page切換:
????首頁-書影音-小組-市集-我的
????通過點擊BottomNavigationBarItem來設(shè)置IndexedStack的index屬性來切換
除了上面這種管理頁面的方式者填,我們還需要實現(xiàn)其它功能的頁面跳轉(zhuǎn):比如點擊一個商品跳轉(zhuǎn)到詳情頁刻伊,某個按鈕跳轉(zhuǎn)到發(fā)送朋友圈、微博的編輯頁面描验。
這種頁面的管理和導(dǎo)航白嘁,我們通常會使用路由進行統(tǒng)一管理。
一. 路由管理
1.1. 認識Flutter路由
路由的概念由來已久膘流,包括網(wǎng)絡(luò)路由絮缅、后端路由,到現(xiàn)在廣為流行的前端路由呼股。
????無論路由的概念如何應(yīng)用耕魄,它的核心是一個路由映射表
????比如:名字 detail 映射到 DetailPage 頁面等
????有了這個映射表之后,我們就可以方便的根據(jù)名字來完成路由的轉(zhuǎn)發(fā)(在前端表現(xiàn)出來的就是頁面跳轉(zhuǎn))
在Flutter中彭谁,路由管理主要有兩個類:Route和Navigator
1.2. Route
Route:一個頁面要想被路由統(tǒng)一管理吸奴,必須包裝為一個Route
????官方的說法很清晰:An abstraction for an entry managed by a Navigator.
但是Route是一個抽象類,所以它是不能實例化的
????在上面有一段注釋缠局,讓我們查看MaterialPageRoute來使用
/// See [MaterialPageRoute] for a route that replaces the
/// entire screen with a platform-adaptive transition.
abstractclass Route<T> {
}
事實上MaterialPageRoute并不是Route的直接子類:
????MaterialPageRoute在不同的平臺有不同的表現(xiàn)
????對Android平臺则奥,打開一個頁面會從屏幕底部滑動到屏幕的頂部,關(guān)閉頁面時從頂部滑動到底部消失
????對iOS平臺狭园,打開一個頁面會從屏幕右側(cè)滑動到屏幕的左側(cè)读处,關(guān)閉頁面時從左側(cè)滑動到右側(cè)消失
????當然,iOS平臺我們也可以使用CupertinoPageRoute
MaterialPageRoute -> PageRoute -> ModalRoute -> TransitionRoute -> OverlayRoute -> Route
1.3. Navigator
Navigator:管理所有的Route的Widget唱矛,通過一個Stack來進行管理的
????官方的說法也很清晰:A widget that manages a set of child widgets with a stack discipline.
那么我們開發(fā)中需要手動去創(chuàng)建一個Navigator嗎罚舱?
????并不需要井辜,我們開發(fā)中使用的MaterialApp、CupertinoApp管闷、WidgetsApp它們默認是有插入Navigator的
????所以粥脚,我們在需要的時候,只需要直接使用即可
Navigator.of(context)
Navigator有幾個最常見的方法:
// 路由跳轉(zhuǎn):傳入一個路由對象
Future<T> push<T extendsObject>(Route<T> route)
// 路由跳轉(zhuǎn):傳入一個名稱(命名路由)
Future<T> pushNamed<T extendsObject>(
? String routeName, {
? ? Object arguments,
? })
// 路由返回:可以傳入一個參數(shù)
bool pop<T extendsObject>([ T result ])
二. 路由基本使用
1.1. 基本跳轉(zhuǎn)
我們來實現(xiàn)一個最基本跳轉(zhuǎn):
????創(chuàng)建首頁頁面渐北,中間添加一個按鈕阿逃,點擊按鈕跳轉(zhuǎn)到詳情頁面
????創(chuàng)建詳情頁面,中間添加一個按鈕赃蛛,點擊按鈕返回到首頁頁面
核心的跳轉(zhuǎn)代碼如下(首頁中代碼):
// RaisedButton代碼(只貼出核心代碼)
RaisedButton(
? child: Text("打開詳情頁"),
? onPressed: () => _onPushTap(context),
),
// 按鈕點擊執(zhí)行的代碼
_onPushTap(BuildContext context) {
? Navigator.of(context).push(MaterialPageRoute(
? ? builder: (ctx) {
? ? ? return DetailPage();
? ? }
? ));
}
核心的返回代碼如下(詳情頁中代碼):
// RaisedButton代碼(只貼出核心代碼)
RaisedButton(
? child: Text("返回首頁"),
? onPressed: () => _onBackTap(context),
)
// 按鈕點擊執(zhí)行的代碼
_onBackTap(BuildContext context) {
? Navigator.of(context).pop();
}
1.2. 參數(shù)傳遞
在跳轉(zhuǎn)過程中恃锉,我們通常可能會攜帶一些參數(shù)呕臂,比如
????首頁跳到詳情頁破托,攜帶一條信息:a home message
????詳情頁返回首頁,攜帶一條信息:a detail message
首頁跳轉(zhuǎn)核心代碼:
????在頁面跳轉(zhuǎn)時歧蒋,會返回一個Future
????該Future會在詳情頁面調(diào)用pop時土砂,回調(diào)對應(yīng)的then函數(shù),并且會攜帶結(jié)果
_onPushTap(BuildContext context) {
? // 1.跳轉(zhuǎn)代碼
? final future = Navigator.of(context).push(MaterialPageRoute(
? ? builder: (ctx) {
? ? ? return DetailPage("a home message");
? ? }
? ));
? // 2.獲取結(jié)果
? future.then((res) {
? ? setState(() {
? ? ? _message = res;
? ? });
? });
}
詳情頁返回核心代碼:
_onBackTap(BuildContext context) {
? Navigator.of(context).pop("a detail message");
}
1.3. 返回細節(jié)
但是這里有一個問題谜洽,如果用戶是點擊右上角的返回按鈕萝映,如何監(jiān)聽呢?
方法一:自定義返回的按鈕(在詳情頁中修改Scaffold的appBar)
appBar: AppBar(
? title: Text("詳情頁"),
? leading: IconButton(
? ? icon: Icon(Icons.arrow_back),
? ? onPressed: () {
? ? ? Navigator.of(context).pop("a back detail message");
? ? },
? ),
),
方法二:監(jiān)聽返回按鈕的點擊(給Scaffold包裹一個WillPopScope)
WillPopScope有一個onWillPop的回調(diào)函數(shù)阐虚,當我們點擊返回按鈕時會執(zhí)行
這個函數(shù)要求有一個Future的返回值:
????true:那么系統(tǒng)會自動幫我們執(zhí)行pop操作
????false:系統(tǒng)不再執(zhí)行pop操作序臂,需要我們自己來執(zhí)行
return WillPopScope(
? onWillPop: () {
? ? Navigator.of(context).pop("a back detail message");
? ? return Future.value(false);
? },
? child: Scaffold(
? ? appBar: AppBar(
? ? ? title: Text("詳情頁"),
? ? ),
? ? body: Center(
? ? ? child: Column(
? ? ? ? mainAxisAlignment: MainAxisAlignment.center,
? ? ? ? children: <Widget>[
? ? ? ? ? RaisedButton(
? ? ? ? ? ? child: Text("返回首頁"),
? ? ? ? ? ? onPressed: () => _onBackTap(context),
? ? ? ? ? ),
? ? ? ? ? Text(_message, style: TextStyle(fontSize: 20, color: Colors.red),)
? ? ? ? ],
? ? ? ),
? ? ),
? ),
);
三. 命名路由使用
3.1. 基本跳轉(zhuǎn)
我們可以通過創(chuàng)建一個新的Route,使用Navigator來導(dǎo)航到一個新的頁面实束,但是如果在應(yīng)用中很多地方都需要導(dǎo)航到同一個頁面(比如在開發(fā)中奥秆,首頁、推薦咸灿、分類頁都可能會跳到詳情頁)构订,那么就會存在很多重復(fù)的代碼。
在這種情況下避矢,我們可以使用命名路由(named route)
????命名路由是將名字和路由的映射關(guān)系悼瘾,在一個地方進行統(tǒng)一的管理
????有了命名路由,我們可以通過Navigator.pushNamed() 方法來跳轉(zhuǎn)到新的頁面
命名路由在哪里管理呢审胸?可以放在MaterialApp的 initialRoute 和 routes 中
????initialRoute:設(shè)置應(yīng)用程序從哪一個路由開始啟動分尸,設(shè)置了該屬性,就不需要再設(shè)置home屬性了
????routes:定義名稱和路由之間的映射關(guān)系歹嘹,類型為Map<String, WidgetBuilder>
修改MaterialApp中的代碼:
return MaterialApp(
? title: 'Flutter Demo',
? theme: ThemeData(
? ? primarySwatch: Colors.blue, splashColor: Colors.transparent
? ),
? initialRoute: "/",
? routes: {
? ? "/home": (ctx) => HYHomePage(),
? ? "/detail": (ctx) => HYDetailPage()
? },
);
修改跳轉(zhuǎn)的代碼:
_onPushTap(BuildContext context) {
? Navigator.of(context).pushNamed("/detail");
}
在開發(fā)中,為了讓每個頁面對應(yīng)的routeName統(tǒng)一孔庭,我們通常會在每個頁面中定義一個路由的常量來使用:
class HYHomePage extends StatefulWidget {
? staticconstString routeName = "/home";
}
class HYDetailPage extends StatelessWidget {
? staticconstString routeName = "/detail";
}
修改MaterialApp中routes的key
initialRoute: HYHomePage.routeName,
routes: {
? HYHomePage.routeName: (ctx) => HYHomePage(),
? HYDetailPage.routeName: (ctx) => HYDetailPage()
},
3.2. 參數(shù)傳遞
因為通常命名路由尺上,我們會在定義路由時材蛛,直接創(chuàng)建好對象,比如HYDetailPage()
那么怎抛,命名路由如果有參數(shù)需要傳遞呢卑吭?
pushNamed時,如何傳遞參數(shù):
_onPushTap(BuildContext context) {
? Navigator.of(context).pushNamed(HYDetailPage.routeName, arguments: "a home message of naned route");
}
在HYDetailPage中马绝,如何獲取到參數(shù)呢豆赏?
在build方法中ModalRoute.of(context)可以獲取到傳遞的參數(shù)
? Widget build(BuildContext context) {
? ? // 1.獲取數(shù)據(jù)
? ? final message = ModalRoute.of(context).settings.arguments;
? }
3.3. 路由鉤子
3.3.1. onGenerateRoute
假如我們有一個HYAboutPage,也希望在跳轉(zhuǎn)時富稻,傳入對應(yīng)的參數(shù)message掷邦,并且已經(jīng)有一個對應(yīng)的構(gòu)造方法
在HYHomePage中添加跳轉(zhuǎn)的代碼:
RaisedButton(
? child: Text("打開關(guān)于頁"),
? onPressed: () {
? ? Navigator.of(context).pushNamed(HYAboutPage.routeName, arguments: "a home message");
? },
)
HYAboutPage的代碼:
class HYAboutPage extends StatelessWidget {
? staticconstString routeName = "/about";
? finalString message;
? HYAboutPage(this.message);
? @override
? Widget build(BuildContext context) {
? ? return Scaffold(
? ? ? appBar: AppBar(
? ? ? ? title: Text("關(guān)于頁面"),
? ? ? ),
? ? ? body: Center(
? ? ? ? child: Text(message, style: TextStyle(fontSize: 30, color: Colors.red),),
? ? ? ),
? ? );
? }
}
但是我們繼續(xù)使用routes中的映射關(guān)系,就不好進行配置了椭赋,因為HYAboutPage必須要求傳入一個參數(shù)抚岗;
這個時候我們可以使用onGenerateRoute的鉤子函數(shù):
????當我們通過pushNamed進行跳轉(zhuǎn),但是對應(yīng)的name沒有在routes中有映射關(guān)系哪怔,那么就會執(zhí)行onGenerateRoute鉤子函數(shù)宣蔚;
????我們可以在該函數(shù)中,手動創(chuàng)建對應(yīng)的Route進行返回认境;
????該函數(shù)有一個參數(shù)RouteSettings胚委,該類有兩個常用的屬性:
????????name: 跳轉(zhuǎn)的路徑名稱
????????arguments:跳轉(zhuǎn)時攜帶的參數(shù)
onGenerateRoute: (settings) {
? if (settings.name == "/about") {
? ? return MaterialPageRoute(
? ? ? builder: (ctx) {
? ? ? ? return HYAboutPage(settings.arguments);
? ? ? }
? ? );
? }
? returnnull;
},
關(guān)于頁面跳轉(zhuǎn)
3.3.2. onUnknownRoute
如果我們打開的一個路由名稱是根本不存在,這個時候我們希望跳轉(zhuǎn)到一個統(tǒng)一的錯誤頁面叉信。
比如下面的abc是不存在有對應(yīng)的頁面的
????如果沒有進行特殊的處理亩冬,那么Flutter會報錯。
RaisedButton(
? child: Text("打開未知頁面"),
? onPressed: () {
? ? Navigator.of(context).pushNamed("/abc");
? },
)
我們可以創(chuàng)建一個錯誤的頁面:
class UnknownPage extends StatelessWidget {
? @override
? Widget build(BuildContext context) {
? ? return Scaffold(
? ? ? appBar: AppBar(
? ? ? ? title: Text("錯誤頁面"),
? ? ? ),
? ? ? body: Container(
? ? ? ? child: Center(
? ? ? ? ? child: Text("頁面跳轉(zhuǎn)錯誤"),
? ? ? ? ),
? ? ? ),
? ? );
? }
}
并且設(shè)置onUnknownRoute
onUnknownRoute: (settings) {
? return MaterialPageRoute(
? ? builder: (ctx) {
? ? ? return UnknownPage();
? ? }
? );
},
微信掃一掃
關(guān)注該公眾號