從零開始的Flutter之旅: Navigator

2020_06_28_07_32_2.jpeg

往期回顧

從零開始的Flutter之旅: StatelessWidgetgit

從零開始的Flutter之旅: StatefulWidgetgithub

從零開始的Flutter之旅: InheritedWidgetweb

從零開始的Flutter之旅: Providersegmentfault

這篇文章是從零開始系列的第五期,前面咱們講到了Widget與結合數據共享的Provider處理。微信

此次咱們接着來了解一下路由導航Navigator的相關信息。網絡

Flutter中的路由管理與原生開發相似,都會維護一個路由棧,經過push入棧打開一個新的頁面,而後再經過pop出棧關閉老的頁面。架構

示例

咱們直接到 flutter_github中找個簡單的實例。框架

void _goToLogin() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    String authorization = prefs.getString(SP_AUTHORIZATION);
    String token = prefs.getString(SP_ACCESS_TOKEN);
    if ((authorization != null && authorization.isNotEmpty) ||
        (token != null && token.isNotEmpty)) {
      Navigator.of(context).push(MaterialPageRoute(builder: (context) {
        return HomePage();
      }));
    } else {
      Navigator.of(context).push(MaterialPageRoute(builder: (context) {
        return LoginPage();
      }));
    }
  }

上面的方法是判斷是否已經登陸了。若是登陸了經過過Navigator跳轉到HomePage頁面,不然跳轉到LoginPage頁面。less

用法很簡單經過push傳遞一個Route。這裏對應的是MaterialPageRoute。它會提供一個builder方法,咱們直接在builder中返回想要跳轉的頁面實例便可。async

它繼承於PageRoute,PageRoute是一個抽象類,它提供了路由切換時的過渡動畫效果與相應的接口。而MaterialPageRoute經過這些接口來實現不一樣平臺上對應風格的路由切換動畫效果。例如:

  1. Android平臺,push時頁面會從屏幕底部滑動到頂部進入,pop時頁面會從屏幕頂部滑動到屏幕底部退出。
  2. Ios平臺,push時頁面會從屏幕右側滑動到屏幕左側進入,pop時頁面會從屏幕左側滑動到屏幕右側退出。
若是想自定義切換動畫,能夠仿照MaterialPageRoute,繼承於PageRoute來實現。

Navigator

須要注意的是,push操做會返回一個Future,它是用來接收新的路由關閉時返回的數據。在Android中對應的就是startActivityForResult() 和 onActivityResult()API。

@optionalTypeArgs
  Future<T> push<T extends Object>(Route<T> route) {
    assert(!_debugLocked);
    assert(() {
      _debugLocked = true;
      return true;
    }());
    ....
    ....
  }

對應的另外一個是pop操做,出棧是能夠向以前的頁面傳遞數據,在Android中對應的就是setResult() Api

@optionalTypeArgs
  bool pop<T extends Object>([ T result ]) {
    assert(!_debugLocked);
    assert(() {
      _debugLocked = true;
      return true;
    }());
    final Route<dynamic> route = _history.last;
    assert(route._navigator == this);
    bool debugPredictedWouldPop;
    ...
    ...
  }

除了上面兩個經常使用的,還有下面幾個特殊的操做

  1. pushReplacement: 將當前的路由頁面進行替換成新的路由頁面, 以前的路由將會失效。
  2. pushAndRemoveUntil: 加入一個新的路由,同時它接收一個判斷條件,若是知足條件將會移除以前全部的路由。

這些都是根據特定場景使用,例如文章最開始的登陸判斷示例。這段判斷代碼其實在App啓動時的引導頁面中,因此無論最終跳轉到哪一個頁面,最終這個引導頁面都須要從路由中消失,因此這裏就能夠經過pushReplacement來開啓新的路由頁面。

Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (context){
        return HomePage();
      }));

傳參

路由跳轉頁面天然少不了參數的傳遞,經過上面的方式進行路由跳轉,傳參也很是簡單,能夠直接經過實例類進行傳參。

我這裏以flutter_github中的WebViePage爲例。

class WebViewPage extends BasePage<_WebViewState> {
  final String url;
  final String requestUrl;
  final String title;
 
  WebViewPage({@required this.title, this.url = '', this.requestUrl = ''});
 
  @override
  _WebViewState createBaseState() => _WebViewState(title, url, requestUrl);
}

上面是WebViewPage參數的接收,直接經過實例化進行參數傳遞

contentTap(int index, BuildContext context) {
    NotificationModel item = _notifications[index];
    if(item.unread) _markThreadRead(index, context);
    Navigator.push(context, MaterialPageRoute(builder: (_) {
      return WebViewPage(
        title: item.subject?.title ?? '',
        requestUrl: item.subject?.url ?? '',
      );
    }));
  }

這裏是經過點擊文本跳轉到WebViewPage頁面,使用push操做來導航到WebViewPage頁面,同時在實例化時將相應的參數傳遞過去。

以上是相對比較原始的方法進行參數傳遞,還有另外一種

作個Android的朋友都知道在Activity頁面跳轉時能夠同Intent進行參數傳遞,而接受頁面也能夠經過Intent來獲取傳遞過來的參數。

在Flutter中也有相似的傳參方式。咱們能夠經過MaterialPageRoute中的settings來構建一個arguments對象,將其傳遞到跳轉的頁面中。

將上面的代碼進行改版

contentTap(int index, BuildContext context) {
    NotificationModel item = _notifications[index];
    if (item.unread) _markThreadRead(index, context);
    Navigator.push(
        context,
        MaterialPageRoute(
            builder: (_) {
              return WebViewPage();
            },
            settings: RouteSettings(
                arguments: {WebViewPage.ARGS_TITLE: item.subject?.title ?? '', WebViewPage.ARGS_REQUEST_URL: item.subject?.url ?? ''})));
  }

這是參數傳遞,下面是WebViewPage中對參數的接收處理

Map<String, String> arguments = ModalRoute.of(context).settings.arguments;
    _title = arguments[WebViewPage.ARGS_TITLE];
    _url = arguments[WebViewPage.ARGS_URL];
    vm.requestUrl = arguments[WebViewPage.ARGS_REQUEST_URL];

在接收頁面參數是經過ModalRoute來獲取的,獲取到的arguments就是上面傳遞過來的參數map數據。

ModalRoute.of()內部運用的是context.dependOnInheritedWidgetOfExactType()

是否是有點眼熟?若是不記得的話推薦從新溫習一遍從零開始的Flutter之旅: InheritedWidget

以上都是非命名路由,下面咱們再來了解一下命名路由的使用與參數方式。

命名路由

命名路由,顧名思義經過提早註冊好的名稱來跳轉到對應的頁面。

首頁咱們須要註冊一個路由表,約定好名稱與頁面的一一對應。

而路由表能夠經過routes來定義

final Map<String, WidgetBuilder> routes;

經過定義,應該很好理解。它是一個map,key表明路由名稱,value表明具體的頁面實例。

flutter_github中的GithubApp爲例。

class _GithubAppState extends State<GithubApp> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Github',
      theme: ThemeData.light(),
      initialRoute: welcomeRoute.routeName,
      routes: {
        welcomeRoute.routeName: (BuildContext context) => WelcomePage(),
        loginRoute.routeName: (BuildContext context) => LoginPage(),
        homeRoute.routeName: (BuildContext context) => HomePage(),
        repositoryRoute.routeName: (BuildContext context) => RepositoryPage(),
        followersRoute.routeName: (BuildContext context) =>
            FollowersPage(followersRoute.pageType),
        followingRoute.routeName: (BuildContext context) =>
            FollowersPage(followingRoute.pageType),
        webViewRoute.routeName: (BuildContext context) => WebViewPage(),
      },
    );
  }
}

須要注意的有兩點

  1. initialRoute是用來初始化路由頁面,它接收的也是對應路由頁面的註冊名稱
  2. routes就是註冊的路由表,只需經過key、value的方式來註冊對應的路由頁面。

爲了方便管理路由的跳轉,這裏使用了AppRoutes來統一管理路由的名稱

class AppRoutes {
  final String routeName;
  final String pageTitle;
  final String pageType;
 
  const AppRoutes(this.routeName, {this.pageTitle, this.pageType});
}
 
class PageType {
  static const String followers = 'followers';
  static const String following = 'following';
}
 
const AppRoutes welcomeRoute = AppRoutes('/');
 
const AppRoutes loginRoute = AppRoutes('/login');
 
const AppRoutes homeRoute = AppRoutes('/home');
 
const AppRoutes repositoryRoute =
    AppRoutes('/repository', pageTitle: 'repository');
 
const AppRoutes followersRoute = AppRoutes('/followers',
    pageTitle: 'followers', pageType: PageType.followers);
const AppRoutes followingRoute = AppRoutes('/following',
    pageTitle: 'following', pageType: PageType.following);
 
const AppRoutes webViewRoute = AppRoutes('/webview', pageTitle: 'WebView');

如今咱們已經註冊好了須要跳轉的頁面路由,接下來使用命名路由的方式來替換以前介紹的路由方式。

void _goToLogin() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    String authorization = prefs.getString(SP_AUTHORIZATION);
    String token = prefs.getString(SP_ACCESS_TOKEN);
    if ((authorization != null && authorization.isNotEmpty) ||
        (token != null && token.isNotEmpty)) {
      Navigator.pushReplacementNamed(context, homeRoute.routeName);
    } else {
      Navigator.pushReplacementNamed(context, loginRoute.routeName);
    }
  }

在登陸狀態判斷跳轉的過程當中,能夠直接經過pushReplacementNamed()來跳轉到對應的頁面。與以前的區別是,咱們只需傳遞對應跳轉頁面的路由名稱。由於已經有了路由註冊表,因此會本身轉變成相應的頁面。

對應的方法還有pushNamed()與pushNamedAndRemoveUntil()

對於命名路由的參數傳遞與以前最後面介紹的參數傳遞方式相似,例如

Navigator.of(context).pushNamed(webViewRoute.routeName, 
        arguments: {WebViewPage.ARGS_TITLE: item.subject?.title ?? '', WebViewPage.ARGS_REQUEST_URL: item.subject?.url ?? ''});

基本上相似,也是傳遞一個arguments,對應的頁面接收參數的方式保存不變。

onGenerateRoute

命名路由中還有一個須要注意的是onGenerateRoute

class _GithubAppState extends State<GithubApp> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
    ...
      onGenerateRoute: (RouteSettings setting) {
        return MaterialPageRoute(builder: (context) {
          String routeName = setting.name;
          // todo navigator
        });
      },
    );
  }
}

它的回調條件是:跳轉的頁面沒有在routes中進行路由註冊

經過該回調方法,咱們能夠在這裏進行路由攔截,再統一作一些頁面跳轉的邏輯處理。

Navigator方面的知識就介紹到這裏,若是文章中有不足的地方歡迎指出,或者說你這其中有什麼疑問也能夠留言與我,我將力所能及的進行解答。

推薦項目

下面介紹一個完整的Flutter項目,對於新手來講是個不錯的入門。

flutter_github,這是一個基於Flutter的Github客戶端同時支持Android與IOS,支持帳戶密碼與認證登錄。使用dart語言進行開發,項目架構是基於Model/State/ViewModel的MSVM;使用Navigator進行頁面的跳轉;網絡框架使用了dio。項目正在持續更新中,感興趣的能夠關注一下。

flutter_github_preview.png

固然若是你想了解Android原生,相信flutter_github的純Android版本AwesomeGithub是一個不錯的選擇。

若是你喜歡個人文章模式,或者對我接下來的文章感興趣,建議您關注個人微信公衆號:【Android補給站】

或者掃描下方二維碼,與我創建有效的溝通,同時更快更準的收到個人更新推送。

Android補給站.jpg

相關文章
相關標籤/搜索