Flutter路由使用指北

路由管理

FLutter中的路由倾鲫,和原生組件化的路由一樣粗合,就是頁(yè)面之間的跳轉(zhuǎn),也可以稱之為導(dǎo)航乌昔。app維護(hù)一個(gè)路由棧隙疚,路由入棧(push)操作對(duì)應(yīng)打開一個(gè)新頁(yè)面,路由出棧(pop)操作對(duì)應(yīng)頁(yè)面關(guān)閉操作磕道,而路由管理主要是指如何來管理路由棧供屉。

MaterialPageRoute

MaterialPageRoute是一種模態(tài)路由,可以針對(duì)不同平臺(tái)自適應(yīng)的過渡動(dòng)畫替換整個(gè)屏幕頁(yè)面:

對(duì)于Android溺蕉,打開新頁(yè)面時(shí)伶丐,新頁(yè)面從屏幕底部導(dǎo)入到頂部。關(guān)閉頁(yè)面的時(shí)候疯特,會(huì)從頂部滑動(dòng)到底部消失哗魂。

在iOS上,頁(yè)面從右側(cè)滑入并反向退出漓雅。

下面我們介紹一下MaterialPageRoute 構(gòu)造函數(shù)的各個(gè)參數(shù)的意義:

  MaterialPageRoute({
    WidgetBuilder builder,
    RouteSettings settings,
    bool maintainState = true,
    bool fullscreenDialog = false,
  })

builder 是一個(gè)WidgetBuilder類型的回調(diào)函數(shù)录别,它的作用是構(gòu)建路由頁(yè)面的具體內(nèi)容朽色,返回值是一個(gè)widget。我們通常要實(shí)現(xiàn)此回調(diào)组题,返回新路由的實(shí)例葫男。
settings 包含路由的配置信息,如路由名稱往踢、是否初始路由(首頁(yè))腾誉。
maintainState:默認(rèn)情況下,當(dāng)入棧一個(gè)新路由時(shí)峻呕,原來的路由仍然會(huì)被保存在內(nèi)存中利职,如果想在路由沒用的時(shí)候釋放其所占用的所有資源,可以設(shè)置maintainState為false瘦癌。
fullscreenDialog表示新的路由頁(yè)面是否是一個(gè)全屏的模態(tài)對(duì)話框猪贪,在iOS中,如果fullscreenDialog為true讯私,新頁(yè)面將會(huì)從屏幕底部滑入(而不是水平方向)

基本使用

Flutter為我們提供了導(dǎo)航器Navigator热押。參數(shù)傳入當(dāng)前的BuildContext和要導(dǎo)航的頁(yè)面即可。

  1. 調(diào)用Navigator.push導(dǎo)航到第二個(gè)頁(yè)面
     Navigator.push(
               context, new MaterialPageRoute(builder:       (context) => Page2()));
    
  1. 調(diào)用Navigator.pop返回前一個(gè)頁(yè)面
         Navigator.pop(context, result);
    
    
  1. 關(guān)閉頁(yè)面后獲取結(jié)果
    有時(shí)候我們需要上個(gè)頁(yè)面關(guān)閉時(shí)傳遞一個(gè)返回值斤寇,幸運(yùn)的是桶癣,Navigator的調(diào)用方法都是Future,因此我們可以等待它們的結(jié)果:

    3.1. 等待Navigator運(yùn)行
    3.2. 將返回值傳遞給Navigator.pop函數(shù)
    3.3. 等待完成后娘锁,獲取返回值

    在page1中牙寞,導(dǎo)航到page2,并且await到page2傳遞返回值并pop莫秆,根據(jù)返回值彈出不同的對(duì)話框:

    onPressed: () async {
          var navigationResult = await Navigator.push(
              context, new MaterialPageRoute(builder: (context) => Page2()));
    
          if (navigationResult == 'from_back') {
            showDialog(
                context: context,
                builder: (context) => AlertDialog(
                      title: Text('Navigation from back'),
                    ));
          } else if (navigationResult == 'from_button') {
            showDialog(
                context: context,
                builder: (context) => AlertDialog(
                      title: Text('Navigation from button'),
                    ));
          }
        },
    

    在page2中傳遞返回值并返回:

    Navigator.pop(context, 'from_button');
    
  2. 攔截返回鍵
    如果不想點(diǎn)擊返回鍵關(guān)閉當(dāng)前頁(yè)面间雀,可以使用WillPopScope小部件,用它放在最外層包括住腳手架镊屎。并向onWillPop返回false惹挟。false告訴系統(tǒng)當(dāng)前頁(yè)面不處理返回。

       @override
       Widget build(BuildContext context) {
         return WillPopScope(
           onWillPop: () => Future.value(false),
           child: Scaffold(
             body: Container(
               child: Center(
                 child: Text('Page 2',
                     style: TextStyle(fontSize: 30.0, fontWeight: FontWeight.bold)),
               ),
             ),
           ),
         );
       }
如果想自定義處理返回鍵缝驳,可以在return false 之前自己處理连锯,比如關(guān)閉    當(dāng)前頁(yè)面并傳遞返回值:
      WillPopScope(
            onWillPop: () async {
                // You can await in the calling widget for my_value and handle when complete.
                Navigator.pop(context, 'my_value');
                return false;
              },
              ...
      );

命名路由

基本使用

上面代碼是在沒個(gè)需要導(dǎo)航的地方聲明路由,不能復(fù)用用狱,我們可以先給路由起一個(gè)名字萎庭,再注冊(cè)路由表,然后就可以通過路由名字直接打開新的路由了齿拂,這為路由管理帶來了一種直觀、簡(jiǎn)單的方式肴敛,并且可以復(fù)用署海。

MaterialApp的routes屬性吗购,既是注冊(cè)路由表用的,它對(duì)應(yīng)一個(gè)Map<String, WidgetBuilder>砸狞。

起名:

  static const String page1 = "/page1";
  static const String page2 = "/page2";

聲明路由表:

  Map<String, WidgetBuilder> routes = {
    page1: (context) => Page1(),
    page2: (context) => Page2(),
  };

注冊(cè)路由表:

 @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      routes: routes,
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: Page1(),
    );
  }

然后在需要路由的地方使用命名路由調(diào)用:

Navigator.pushNamed(context, page2)

傳遞參數(shù)

給page3起名:

    page3: (context) => Page3(),

打開路由時(shí)候傳遞參數(shù):

              Navigator.of(context).pushNamed(page3, arguments: "hi");

page3中接收參數(shù):

class Page3 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //獲取路由參數(shù)
    var args = ModalRoute.of(context).settings.arguments;
    return Scaffold(
      body: Container(
        child: Center(
          child: Text('Page 3的參數(shù)是$args',
              style: TextStyle(fontSize: 30.0, fontWeight: FontWeight.bold)),
        ),
      ),
    );
  }
}

構(gòu)造函數(shù)傳參

上面我們明明給page3傳遞了參數(shù)捻勉,但是并非傳遞到構(gòu)造函數(shù)上。我們看構(gòu)造函數(shù)刀森,并不知道傳遞了什么參數(shù)踱启,必須去看路由,并不是很好的做法研底。那怎么給構(gòu)造函數(shù)傳參呢埠偿?

起名:

const String page4 = "/page4";

注冊(cè)路由:

    page4: (context) => Page4(text: ModalRoute.of(context).settings.arguments),

打開路由時(shí)傳遞參數(shù):

              Navigator.of(context).pushNamed(page4, arguments: "hello");

動(dòng)態(tài)路由

MaterialApp還為我們提供了一個(gè)onGenerateRoute參數(shù),未在路由表里注冊(cè)的路由榜晦,會(huì)在這里尋找冠蒋。RouteFactory有一個(gè)RouteSettings參數(shù),并返回一個(gè)Route<dynamic>乾胶。這是我們將用來執(zhí)行所有路由的功能抖剿。

Route<dynamic> Function(RouteSettings settings)

我們可以這樣使用:
先聲明路由表:

Route<dynamic> generateRoute(RouteSettings settings) {
    switch (settings.name) {
        case page5:
            return MaterialPageRoute(builder: (context) => Page5());
        case page6:
            return MaterialPageRoute(builder: (context) => Page6());
        default:
            return MaterialPageRoute(builder: (context) => Page1());
    }

注冊(cè):

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      routes: routes,
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      onGenerateRoute: generateRoute,
      home: Page1(),
    );
  }

打開路由:

              Navigator.of(context).pushNamed(page5);

動(dòng)態(tài)路由傳遞參數(shù)

上面說了,settings可以拿到參數(shù)识窿,我們當(dāng)然就可以傳遞參數(shù)了:

Route<dynamic> generateRoute(RouteSettings settings) {
    print('====${settings.name}');
    switch (settings.name) {
        case page5:
            return MaterialPageRoute(builder: (context) => Page5());
        case page6:
            return MaterialPageRoute(builder: (context) => Page6(text: settings.arguments,));
        default:
            return MaterialPageRoute(builder: (context) => Page1());
    }
}

使用:

              Navigator.of(context).pushNamed(page6, arguments: "world");

so easy斩郎。

處理未定義的路線

有兩種處理未定義路由的方法。

  1. 利用generateRoute喻频,找不到路由名的返回默認(rèn)路由
Route<dynamic> generateRoute(RouteSettings settings) {
    print('====${settings.name}');
    switch (settings.name) {
        case page5:
            return MaterialPageRoute(builder: (context) => Page5());
        case page6:
            return MaterialPageRoute(builder: (context) => Page6(text: settings.arguments,));
        default:
            return MaterialPageRoute(builder: (context) => NotFindPage());
    }
}
  1. 利用onUnknownRoute返回默認(rèn)路由
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Flutter Demo',
        routes: routes,
        theme: ThemeData(
          primarySwatch: Colors.blue,
          visualDensity: VisualDensity.adaptivePlatformDensity,
        ),
        onGenerateRoute: generateRoute,
        onUnknownRoute: (settings) =>
            MaterialPageRoute(builder: (context) => NotFindPage()));
  }

初始路由

打開應(yīng)用第一屏的路由缩宜,也有2種方式,

  1. 可以設(shè)置initialRoute半抱,指定路由表里注冊(cè)的路由名脓恕。
  2. 可以設(shè)置home,對(duì)應(yīng)的page窿侈。
    initialRoute會(huì)覆蓋home炼幔。
 Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Flutter Demo',
        routes: routes,
        theme: ThemeData(
          primarySwatch: Colors.blue,
          visualDensity: VisualDensity.adaptivePlatformDensity,
        ),
        initialRoute: root,
        home: Page2(),
        onGenerateRoute: generateRoute,
        onUnknownRoute: (settings) =>
            MaterialPageRoute(builder: (context) => NotFindPage()));
  }

不使用BuildContext的路由導(dǎo)航

很多情況是,我們已將UI代碼從業(yè)務(wù)邏輯中分離出來(類似于MVVM架構(gòu))史简。viewModel應(yīng)處理所有邏輯乃秀,視圖應(yīng)僅調(diào)用模型上的函數(shù),然后在需要時(shí)使用新狀態(tài)重建自身圆兵。

我們知道Navigator需要BuildContext的參數(shù)跺讯,我們?cè)谶M(jìn)行實(shí)際業(yè)務(wù)邏輯決策的位置進(jìn)行導(dǎo)航,而不是在widget里調(diào)用路由,如果在viewModel里導(dǎo)航殉农,就要傳入context嗎刀脏?下面實(shí)現(xiàn)不要context的導(dǎo)航。

為了遵守MVVM原則超凳,我們將把Navigation功能移動(dòng)到可以從viewModel調(diào)用的服務(wù)中愈污。在lib下創(chuàng)建一個(gè)名為services的新文件夾耀态,并在其中創(chuàng)建一個(gè)名為navigation_service.dart的新文件。

先實(shí)現(xiàn)單利模式:

class NavigationService {
  factory NavigationService.getInstance() => _getInstance();

  NavigationService._internal();

  static NavigationService _instance;

  static NavigationService _getInstance() {
    if (_instance == null) {
      _instance = new NavigationService._internal();
    }
    return _instance;
  }
  }
然后利用navigatorKey實(shí)現(xiàn):
 final GlobalKey<NavigatorState> navigatorKey =
      new GlobalKey<NavigatorState>();

  Future<dynamic> navigateTo(String routeName) {
    return navigatorKey.currentState.pushNamed(routeName);
  }

  void goBack() {
    return navigatorKey.currentState.pop();
  }

我們將NavigationService與應(yīng)用程序鏈接的方式暂雹,通過navigatorKey提供給MaterialApp首装。轉(zhuǎn)到main.dart文件并設(shè)置navigatorKey:

 MaterialApp(
        title: 'Flutter Demo',
        navigatorKey: NavigationService().navigatorKey,
        ...
        )

然后寫一個(gè)viewModel,嘗試導(dǎo)航:

class ViewModel {
  final NavigationService _navigationService = NavigationService();

  Future goPage1() async{
    /// 模擬請(qǐng)求數(shù)據(jù)后調(diào)到首頁(yè)
      await Future.delayed(Duration(seconds: 1));
      _navigationService.navigateTo(page1);
  }

}

在page6里使用viewModel導(dǎo)航:

  onPressed: () {
          viewModel.goPage1();
        },

現(xiàn)在杭跪,將View文件的職責(zé)帶回到了“顯示UI”并將用戶操作傳遞給模型仙逻,而不是“顯示UI”將用戶操作傳遞給模型并進(jìn)行導(dǎo)航。更符合MVVM職責(zé)的劃分涧尿。

這樣做的好處是系奉,隨著導(dǎo)航邏輯的擴(kuò)展,我們的UI將保持不變现斋,并且模型將承載所有邏輯/狀態(tài)管理喜最。

代碼鏈接

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市庄蹋,隨后出現(xiàn)的幾起案子瞬内,更是在濱河造成了極大的恐慌,老刑警劉巖限书,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件虫蝶,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡倦西,警方通過查閱死者的電腦和手機(jī)能真,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來扰柠,“玉大人粉铐,你說我怎么就攤上這事÷钡担” “怎么了蝙泼?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)劝枣。 經(jīng)常有香客問我汤踏,道長(zhǎng),這世上最難降的妖魔是什么舔腾? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任溪胶,我火速辦了婚禮,結(jié)果婚禮上稳诚,老公的妹妹穿的比我還像新娘哗脖。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布懒熙。 她就那樣靜靜地躺著丘损,像睡著了一般。 火紅的嫁衣襯著肌膚如雪工扎。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天衔蹲,我揣著相機(jī)與錄音肢娘,去河邊找鬼。 笑死舆驶,一個(gè)胖子當(dāng)著我的面吹牛橱健,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播沙廉,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼拘荡,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了撬陵?” 一聲冷哼從身側(cè)響起珊皿,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎巨税,沒想到半個(gè)月后蟋定,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡草添,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年驶兜,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片远寸。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡抄淑,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出驰后,到底是詐尸還是另有隱情肆资,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布倡怎,位于F島的核電站迅耘,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏监署。R本人自食惡果不足惜颤专,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望钠乏。 院中可真熱鬧栖秕,春花似錦、人聲如沸晓避。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至暑塑,卻和暖如春吼句,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背事格。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工惕艳, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人驹愚。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓远搪,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親逢捺。 傳聞我的和親對(duì)象是個(gè)殘疾皇子谁鳍,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345