Flutter命名路由獲取傳參的兩種方式

  • 概述

    普通路由傳遞參數(shù)采用的是硬傳遞的方式:

    Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) {
                    return TipRoute(
                      // 路由參數(shù)
                      text: "我是提示xxxx",
                    );
                  },
                ),
              );k
    

    可以看到解寝,參數(shù)通過新頁面組件的構造方法直接傳入的报咳。

    但是標準的做法通常是建立一個路由表讯檐,通過字符串名字映射路由信息甘萧,因此Flutter提供了一種命名路由方式:

    Future pushNamed(BuildContext context, String routeName,{Object arguments})
    

    我們本文就是整理分析關于它傳值后獲取它的兩種途徑缤弦。

  • 路由映射表

    不管哪種方式,首先我們都需要定義路由映射表锉试,比如:

    static final routes = {
      HOME_PAGE: (context) => MyHomeWidget('Flutter Demo Home Page'),
      MIX_DEMO: (context,{argument}) => MixDemo(title: argument),
    };
    
  • 方式一:通過onGenerateRoute函數(shù)

    這種方式首先在MaterialApp中不能指定routes屬性玻募,因為onGenerateRoute函數(shù)只有在routes不存在或者在其中找不到路由的時候才會調用,具體原理之前有博文介紹過,此處不再贅述拖云。

    這種方式要求映射表不指定routes的類型,雖然按照MaterialApp的routes類型來看靶庙,我們應該定義成:

    Map<String, WidgetBuilder>? routes;
    typedef WidgetBuilder = Widget Function(BuildContext context);
    

    但是如果使用onGenerateRoute方式的話,定義成這樣是不行的娃属,為什么六荒,接著往下看。

    下面我們看onGenerateRoute中該如何寫:

    ///針對命名路由矾端,跳轉前攔截操作,找不到路由的時候才會走這里
    Route<dynamic>? _myGenerateRouteLogic(RouteSettings settings) {
      Function? pageContentBuilder = RouteManager.routes[settings.name];
      if (pageContentBuilder != null) {
        if (settings.arguments != null && settings.arguments is String) {
          var route = MaterialPageRoute(
              builder: (context) => pageContentBuilder(context,
                  argument: settings.arguments as String));
          return route;
        } else {
          var route = MaterialPageRoute(
              builder: (context) => pageContentBuilder(context));
          return route;
        }
      }
    }
    

    可見恬吕,這里會從映射表中按照路由的名字找到對應的pageContentBuilder,這里會用Function接收须床,這里也能理解,如果定義成WidgetBuilder的話渐裂,這里的實際類型就會是WidgetBuilder類型豺旬,而我們這里會根據(jù)arguments是否為空來調用不同參數(shù)的pageContentBuilder方法,使用WidgetBuilder接收是編譯不過去的柒凉。

    現(xiàn)在獲取的pageContentBuilder是Widget Function(dynamic)類型族阅,它可以同時應對帶參數(shù)的和不帶參數(shù)的Function,所以可以通過編譯膝捞。

    這種方式的映射表需要根據(jù)需求添加可選參數(shù)坦刀,必須是可選參數(shù),因為實際上函數(shù)類型還是限制成和WidgetBuilder一樣的蔬咬,只不過是參數(shù)類型用dynamic適配的鲤遥。

    現(xiàn)在我們來梳理一下邏輯,MaterialApp中不設置routes屬性林艘,或者routes指定的路由表中不配置傳參路由頁面(最好不要這么做盖奈,顯得怪怪的),這樣以來路由跳轉時就會走到onGenerateRoute函數(shù)中狐援,在這里通過RouteSettings的name去路由表(這個路由表中必須得有獲取參數(shù)的路由頁面了)中找到生成該路由的pageContentBuilder钢坦,判斷RouteSettings的arguments,從而適配帶參數(shù)和不帶參數(shù)的pageContentBuilder調用啥酱,在pageContentBuilder中把可選參數(shù)通過路由頁面的構造方法傳入爹凹。

    可見,這種方式最終還是通過路由頁面的構造函數(shù)直接傳入的镶殷。

  • 方式二:通過ModalRoute

    第一種方式可以說是另辟蹊徑禾酱,因為onGenerateRoute的初衷是為了處理一些路由表中未找到路由的情況的,用它來實現(xiàn)傳遞參數(shù)有一些驢唇不對馬嘴了,相比于第一種方式宇植,第二種方式就優(yōu)雅很多了得封。

    在路由頁面的build中,我們直接調用ModalRoute.of(context)?.settings.arguments就可以拿到命名路由傳過來的參數(shù)指郁,代碼越少忙上,原理越繞...那它的原理是什么呢?

    @optionalTypeArgs
    static ModalRoute<T>? of<T extends Object?>(BuildContext context) {
      final _ModalScopeStatus? widget = context.dependOnInheritedWidgetOfExactType<_ModalScopeStatus>();
      return widget?.route as ModalRoute<T>?;
    }
    

    可以看到闲坎,這里會在組件樹中通過找到父級中最近的一個_ModalScopeStatus疫粥,然后拿到它的route,我們來找一下 _ModalScopeStatus是什么腰懂。

    最終找到的是:

    @override
    Iterable<OverlayEntry> createOverlayEntries() sync* {
      yield _modalBarrier = OverlayEntry(builder: _buildModalBarrier);
      yield _modalScope = OverlayEntry(builder: _buildModalScope, maintainState: maintainState);
    }
    

    _buildModalScope函數(shù)中最終構造的子樹中含有 _ModalScopeStatus梗逮,createOverlayEntries這個方法是在NavigatorState的 _push流程中會調用的方法,可以理解成建立新路由的面板绣溜。

    Widget _buildModalScope(BuildContext context) {
      // To be sorted before the _modalBarrier.
      return _modalScopeCache ??= Semantics(
        sortKey: const OrdinalSortKey(0.0),
        child: _ModalScope<T>(
          key: _scopeKey,
          route: this,
          // _ModalScope calls buildTransitions() and buildChild(), defined above
        ),
      );
    }
    

    _ModalScope設置了一個route屬性慷彤,指定為當前類, _buildModalScope所在的類正是ModalRoute怖喻,它的RouteSettings屬性正是前面pushNamed方法一步步傳入的底哗,我們來看看RouteSettings在怎么生成的。

    @optionalTypeArgs
    Future<T?> pushNamed<T extends Object?>(
      String routeName, {
      Object? arguments,
    }) {
      return push<T>(_routeNamed<T>(routeName, arguments: arguments)!);
    }
    

    很明顯锚沸,Route由_routeNamed生成跋选,它的核心代碼如下:

    Route<T>? _routeNamed<T>(String name, { required Object? arguments, bool allowNull = false }) {
      if (allowNull && widget.onGenerateRoute == null)
        return null;
     
      final RouteSettings settings = RouteSettings(
        name: name,
        arguments: arguments,
      );
      Route<T>? route = widget.onGenerateRoute!(settings) as Route<T>?;
      if (route == null && !allowNull) {
        //如果沒找到返回onUnknownRoute生成的404路由
        route = widget.onUnknownRoute!(settings) as Route<T>?;
      }
      return route;
    }
    

    可以看到,RouteSettings是在這里創(chuàng)建的哗蜈,它的arguments正是我們調用pushNamed方法時傳入的arguments屬性的值前标,而Route通過widget.onGenerateRoute生成,因為_routeNamed方法在NavigatorState中距潘,所以widget自然是Navigator炼列,Navigator的widget.onGenerateRoute也是構造時傳入的,我們這里是App內的跳轉绽昼,所以找到 _WidgetsAppState中的build中Navigator的構造處唯鸭,發(fā)現(xiàn)這里的onGenerateRoute屬性設置的是一個叫 _onGenerateRoute的函數(shù):

    Route<dynamic>? _onGenerateRoute(RouteSettings settings) {
      final String? name = settings.name;
      final WidgetBuilder? pageContentBuilder = name == Navigator.defaultRouteName && widget.home != null
          ? (BuildContext context) => widget.home!
          : widget.routes![name];
    
      if (pageContentBuilder != null) {
        assert(
          widget.pageRouteBuilder != null,
          'The default onGenerateRoute handler for WidgetsApp must have a '
          'pageRouteBuilder set if the home or routes properties are set.',
        );
        final Route<dynamic> route = widget.pageRouteBuilder!<dynamic>(
          settings,
          pageContentBuilder,
        );
        assert(route != null, 'The pageRouteBuilder for WidgetsApp must return a valid non-null Route.');
        return route;
      }
      if (widget.onGenerateRoute != null)
        return widget.onGenerateRoute!(settings);
      return null;
    }
    

    可以看到,這里會通過name去App指定的routes映射表中取pageContentBuilder硅确,route又通過widget.pageRouteBuilder產生目溉,找到_MaterialAppState的 _buildWidgetApp中發(fā)現(xiàn)pageRouteBuilder是:

    pageRouteBuilder: <T>(RouteSettings settings, WidgetBuilder builder) {
      return MaterialPageRoute<T>(settings: settings, builder: builder);
    },
    

    可以看到這里和我們第一種方式的onGenerateRoute中的構造route的方式差不多,只不過這里的MaterialPageRoute會把RouteSettings傳進去菱农,而我們第一種方式不用傳是因為我們那時是直接通過路由頁面的構造函數(shù)傳入?yún)?shù)的缭付,而這里會把settings傳入是因為之后是通過route找到settings后再取參數(shù)。

    而MaterialPageRoute最終繼承自ModalRoute循未,所以dependOnInheritedWidgetOfExactType會取到route從而取到settings陷猫。

  • 總結

    關于路由參數(shù)的獲取我們這里整理分析了兩種方式秫舌,第一種屬于旁門招數(shù),而第二種也是官方推薦使用的方式绣檬,它通過把參數(shù)保存在組件樹的當前Route中足陨,然后通過dependOnInheritedWidgetOfExactType獲取最近的Route持有者的方式來獲取參數(shù)。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末娇未,一起剝皮案震驚了整個濱河市墨缘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌零抬,老刑警劉巖镊讼,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異平夜,居然都是意外死亡蝶棋,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門忽妒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來玩裙,“玉大人,你說我怎么就攤上這事段直∠仔铮” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵坷牛,是天一觀的道長。 經常有香客問我很澄,道長京闰,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任甩苛,我火速辦了婚禮蹂楣,結果婚禮上,老公的妹妹穿的比我還像新娘讯蒲。我一直安慰自己痊土,他們只是感情好,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布墨林。 她就那樣靜靜地躺著赁酝,像睡著了一般。 火紅的嫁衣襯著肌膚如雪旭等。 梳的紋絲不亂的頭發(fā)上酌呆,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機與錄音搔耕,去河邊找鬼隙袁。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的菩收。 我是一名探鬼主播梨睁,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼娜饵!你這毒婦竟也來了坡贺?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤划咐,失蹤者是張志新(化名)和其女友劉穎拴念,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體褐缠,經...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡政鼠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了队魏。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片公般。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖胡桨,靈堂內的尸體忽然破棺而出官帘,到底是詐尸還是另有隱情,我是刑警寧澤昧谊,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布刽虹,位于F島的核電站,受9級特大地震影響呢诬,放射性物質發(fā)生泄漏涌哲。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一尚镰、第九天 我趴在偏房一處隱蔽的房頂上張望阀圾。 院中可真熱鬧,春花似錦狗唉、人聲如沸初烘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽肾筐。三九已至,卻和暖如春缸剪,著一層夾襖步出監(jiān)牢的瞬間局齿,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工橄登, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留抓歼,地道東北人讥此。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像谣妻,于是被迫代替她去往敵國和親萄喳。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內容