Flutter中管理路由棧的方法和應用

本文首先講的Flutter中的路由,而後主要講下Flutter中棧管理的幾種方法。git

  • 瞭解下Route和Navigator
  • 簡單的路由
  • 命名路由
  • 自定義路由
  • Flutter中使用的路由場景
  • Flutter中的路由棧管理
  • 實戰
  • 代碼連接
  • 下一步

瞭解下Route和Navigator

在Flutter中,咱們須要在不一樣屏幕或者頁面之間進行切換和發送數據,這些「screens」或者「pages」被稱爲Route(路由),是由一個Navigator的小部件進行管理。github

Navigator能夠管理包含若干Route對象的堆棧,並提供了管理的方法,日常咱們常常用的就是[Navigator.push]和[Navigator.pop]。bash

儘管咱們能夠本身直接建立一個navigator,可是當咱們建立一個WidgetsApp或者MaterialApp,Flutter會自動默認建立一個Navigator。 因此咱們通常是使用由[WidgetsApp]或者[MaterialApp]所建立的Navigator就好了,而後經過調用[Navigator.of] 來拿到當前的Navigator的狀態NavigatorState,而後調用它的pop或者push方法。框架

簡單的路由

好比要導航到一個新的頁面,咱們能夠建立一個[MaterialPageRoute]的實例,而後調用Navigator.of(context).push()方法就將新頁面添加到堆棧的頂部。less

返回上一個頁面,則調用Navigator.pop(context)就能夠從堆棧中刪除這個屏幕;ide

Navigator.of(context)
                        .push(new MaterialPageRoute(builder: (context) {
                      return new DetailPage();
                    }));
 Navigator.pop(context);
複製代碼

命名路由

若是每次跳轉到一個新的路由頁面,都要跟上面同樣的寫法,建立MaterialPageRoute實例而後調用push方法,這樣的話就太麻煩了。post

因此,Flutter提供了另一種方式來管理路由,可使用命名路由,而後使用Navigator.pushNamed()方法來彈出路由。學習

建立MaterialApp的時候須要傳入一個routes參數,routes本質上是一個Map<String,WidgetBuilder>,key值對應自定義的路徑名字,value值會映射到對應的WidgetBuilder,咱們能夠在WidgetBuilder中建立對應的頁面。動畫

Navigator.pushNamed()方法有兩個參數(BuildContext,String),第一個是上下文,第二個是在路由中預約義的string。ui

特殊狀況處理:當push一個不存在的路由頁面的時候,須要進行提示操做。可使用UnknownRoute的屬性。好比下面的例子, 當push一個不存在的路由的時候,會跳轉到NotFoundPage的頁面。

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      routes: {
        //Map<String, WidgetBuilder>
        "/splash": (context) => new SplashPage(),
        "/login": (context) => new LoginPage(),
        "/home": (context) => new HomePage(),
        "/detail": (context) => new DetailPage(),
      },
      onUnknownRoute: (RouteSettings setting) {
        String name = setting.name;
        print("onUnknownRoute:$name");
        return new MaterialPageRoute(builder: (context) {
          return new NotFoundPage();
        });
      },
      title: 'Flutter Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new SplashPage(),
    );
  }
}

//彈出路由,跳轉到其餘頁面
 Navigator.of(context).pushNamed("/detail");
複製代碼

自定義路由

好比咱們上面使用的是MaterialPageRoute,在頁面切換的時候,會有默認的自適應平臺的過渡動畫。 若是想自定義頁面的進場和出場動畫,那麼須要使用PageRouteBuilder來建立路由。 PageRouteBuilder是主要的部分,一個是「pageBuilder」,用來建立所要跳轉到的頁面,另外一個是「transitionsBuilder」,也就是咱們能夠自定義的轉場效果。

PageRouteBuilder({
    RouteSettings settings,
    @required this.pageBuilder,//構造頁面
    this.transitionsBuilder = _defaultTransitionsBuilder,//建立轉場動畫
    this.transitionDuration = const Duration(milliseconds: 300),//轉場動畫的持續時間
    this.opaque = true,//是不是透明的
    this.barrierDismissible = false,//舉個例子,好比AlertDialog也是利用PageRouteBuilder進行建立的,barrierDismissible若爲false,點擊對話框周圍,對話框不會關閉;若爲true,點擊對話框周圍,對話框自動關閉。
    this.barrierColor,
    this.barrierLabel,
    this.maintainState = true,
  }
複製代碼

只修改單獨一個頁面的過渡動畫,能夠這樣操做,例以下面的代碼,

//              自定義跳轉動畫
              Navigator.push(
                  context,
                  PageRouteBuilder(
                      opaque: false,
                      pageBuilder: (BuildContext context, _, __) {
                        return new HomePage();
                      },
                      transitionsBuilder: (___, Animation<double> animation,
                          ____, Widget child) {
                        return FadeTransition(
                          opacity: animation,
                          child: RotationTransition(
                            turns: Tween<double>(begin: 0.5, end: 1.0)
                                .animate(animation),
                            child: child,
                          ),
                        );
                      }));
複製代碼

Flutter中使用的路由場景

在Flutter中,咱們會使用到這些方法,例如[showDialog()], [showMenu()], and [showModalBottomSheet()]等,這些方法其實本質上是建立了一個路由的頁面後,並調用Navigator的push方法去push到當前的屏幕上。

showDialog()實際上是調用了showGeneralDialog(),因此下面貼了showGeneralDialog的源碼,能夠看出,也是利用了Navigator的push方法的。

這裏插一下,關於對話框的使用,好比列表對話框,自定義對話框的使用和踩坑,能夠看下個人另一篇文章:Flutter之Dialog使用和踩坑

Future<T> showGeneralDialog<T>({
  @required BuildContext context,
  @required RoutePageBuilder pageBuilder,
  bool barrierDismissible,
  String barrierLabel,
  Color barrierColor,
  Duration transitionDuration,
  RouteTransitionsBuilder transitionBuilder,
}) {
  return Navigator.of(context, rootNavigator: true).push<T>(_DialogRoute<T>(
    pageBuilder: pageBuilder,
    barrierDismissible: barrierDismissible,
    barrierLabel: barrierLabel,
    barrierColor: barrierColor,
    transitionDuration: transitionDuration,
    transitionBuilder: transitionBuilder,
  ));
}

複製代碼

Flutter中的路由棧管理

相信你們對棧Stack都有必定的瞭解,push方法是將元素添加到堆棧的頂部,pop方法是刪除頂部元素。

下面用圖文的方式來說解Flutter中幾個管理棧的方法之間的區別。

push()

push(),就是直接將一個元素插入到堆棧的頂部。

這個方法很簡單,而且咱們會常常用到。好比從Screen1中利用navigator的push方法,將Screen2的路由彈到堆棧的頂部。堆棧的狀況以下圖:

能夠利用下面的兩個push方法,實現這個目的。

Navigator.of(context).pushNamed("/111");
Navigator.of(context).push(route);
複製代碼

pop()

pop(), 就是將堆棧的頂部元素進行刪除,回退到上一個界面。

好比上面的例子,在Screen2中利用pop()將頂部的Screen2從堆棧中移除,以後的堆棧以下圖:

Navigator.of(context).pop();
複製代碼

注意:

  • 當利用push()跳轉到一個使用Scaffold的頁面的時候,Scaffold會自動在其AppBar上添加一個「 後退 」按鈕,點擊按鈕會自動調用Navigator.pop()。
  • 在Android中,按下設備後退按鈕也會自動調用Navigator.pop()。

pushReplacementNamed()和popAndPushNamed()

有下面的這種場景,咱們進入到Screen3頁面後,要跳轉到Screen4頁面,不過點擊返回按鈕,並不想回退到Screen3頁面。也就是想將Screen4的元素插入棧頂的同時,將Screen3的元素夜進行移除。

這個時候,咱們就要用到pushReplacementNamed()或者popAndPushNamed(),pushReplacement()均可以實現這個目的。

Navigator.of(context).pushReplacementNamed('/screen4');
Navigator.popAndPushNamed(context, '/screen4');
Navigator.of(context).pushReplacement(newRoute);
複製代碼

pushNamedAndRemoveUntil()

通常會有這種場景,咱們在已經登陸的狀況下,在設置界面會有個退出用戶登陸的按鈕,點擊後會註銷用戶退出登陸,而且會跳轉到登陸界面。那麼路由棧的變化應該會以下圖所示:

若是隻是簡單的進行push一個LoginScreen的操做的話,那麼按返回鍵的話,會回到上一個頁面,這樣的邏輯是不對的。

因此咱們應該刪除掉路由棧中的全部route,而後再彈出LoginScreen。這個時候就要用到pushNamedAndRemoveUntil()或者pushAndRemoveUntil()了。

typedef RoutePredicate = bool Function(Route<dynamic> route);
//第一個參數context是上下文的context,
//第二個參數newRouteName是新的路由所命名的路徑
//第三個參數predicate,返回值是bool類型,按照個人理解,就是用來判斷Until所結
//束的時機,若是爲false的話,就會一直繼續執行Remove的操做,直到爲true的時候,中止Remove操做,而後才執行push操做。
  static Future<T> pushNamedAndRemoveUntil<T extends Object>(BuildContext context, String newRouteName, RoutePredicate predicate) {
    return Navigator.of(context).pushNamedAndRemoveUntil<T>(newRouteName, predicate);
  }
複製代碼
  • 若是想在彈出新路由以前,刪除路由棧中的全部路由,那可使用下面的這種寫法,(Route route) => false,這樣能保證把以前全部的路由都進行刪除,而後才push新的路由。
Navigator.of(context).pushNamedAndRemoveUntil('/LoginScreen', (Route<dynamic> route) => false);
複製代碼
  • 若是想在彈出新路由以前,刪除路由棧中的部分路由。好比只彈出Screen1路由上面的Screen3和Screen2,而後再push新的Screen4,堆棧的狀況以下圖:

利用ModalRoute.withName(name),來執行判斷,能夠看下面的源碼,當所傳的name跟堆棧中的路由所定義的時候,會返回true值,不匹配的話,則返回false。

Navigator.of(context).pushNamedAndRemoveUntil('/screen4',ModalRoute.withName('/screen1'));
 
 //ModalRoute.withName的源碼
   static RoutePredicate withName(String name) {
    return (Route<dynamic> route) {
      return !route.willHandlePopInternally
          && route is ModalRoute
          && route.settings.name == name;
    };
  }
複製代碼

popUntil()

popUntil()方法的過程其實跟上面差很少,就是是少了push一個新頁面的操做,只是單純的進行移除路由操做。

popUntil(RoutePredicate predicate);
Navigator.of(context).popUntil(ModalRoute.withName("/XXX"));

複製代碼

實戰

這裏寫了一個Demo,將上面的幾種管理棧的用法都運用了一下。

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      routes: {
        //Map<String, WidgetBuilder>
        "/splash": (context) => new SplashPage(),
        "/login": (context) => new LoginPage(),
        "/home": (context) => new HomePage(),
        "/detail": (context) => new DetailPage(),
      },
      onUnknownRoute: (RouteSettings setting) {
        String name = setting.name;
        print("onUnknownRoute:$name");
        return new MaterialPageRoute(builder: (context) {
          return new NotFoundPage();
        });
      },
      title: 'Flutter Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new SplashPage(),
    );
  }
}
複製代碼

首先是定義在MaterialApp中定義多個路由,Demo中有多個路由:

  • SplashPage 啓動頁面,設置2秒後自動跳轉到LoginPage
  • LoginPage 登陸頁面,點擊按鈕,模仿登陸成功的動做並跳轉到HomePage
  • HomePage 主頁,顯示一個列表,點擊列表項跳轉到DetailPage
  • DetailPage 詳細信息頁面,而且有一個退出登陸的按鈕
  • NotFoundPage 當沒有路由能夠匹配的時候,彈出這個頁面

下面簡單講一下代碼:

SplashPage->LoginPage,LoginPage->HomePage

用的都是pushReplacementNamed()。由於跳到登陸界面後,不須要返回到SplashPage,因此須要將SplashPage從路由棧中移除。LoginPage->HomePage,也是一樣的道理。

Navigator.of(context).pushReplacementNamed("/login");
Navigator.of(context).pushReplacementNamed("/home");
複製代碼

HomePage->DetailPage

使用的是簡單的pushNamed()就能夠了,不必移除HomePage,由於從DetailPage點擊返回後,須要返回到HomePage界面。

//下面的兩種寫法都是能夠的
Navigator.of(context).pushNamed("/detail");
Navigator.of(context)
         .push(new MaterialPageRoute(builder: (context) {
         return new DetailPage();
       }));
複製代碼

DetailPage->LoginPage

利用的是從DetailPage點擊退出登陸按鈕,會彈出路由棧中的全部路由頁面,而後再將LoginPage的路由插入到棧頂。這樣的話,路由棧中就只剩下LoginPage了,如果點擊返回按鈕的話,默認就會退出應用程序了,由於堆棧爲空了。

Navigator.of(context).pushNamedAndRemoveUntil("/login", (Route<dynamic> route) => false);
複製代碼

代碼連接

github.com/LXD31256949…

歡迎你們關注個人公衆號,會推送關於Flutter和Android學習的一些文章

下一步

學習使用fluro的第三方路由框架,並作下整理和總結。

相關文章
相關標籤/搜索