Flutter-為什么包裝一層Builder控件之后惫撰,路由或點(diǎn)擊彈框事件正常使用了羔沙?

事故回放

一朋友面試,被問(wèn)到在Flutter中一些因 context 引起的路由異常的問(wèn)題厨钻,為什么包裝一層 Builder 控件之后扼雏,路由或點(diǎn)擊彈框事件正常使用了?然后就沒(méi)然后了夯膀。诗充。。相信很多人都會(huì)用诱建,至于為什么蝴蜓,也沒(méi)深究。

相信很多剛開(kāi)始玩Flutter的同學(xué)都會(huì)在學(xué)習(xí)過(guò)程中都會(huì)寫(xiě)到類(lèi)似下面的這種代碼:

import 'package:flutter/material.dart';

class BuilderA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: GestureDetector(
          onTap: () {
            Scaffold.of(context).showSnackBar(SnackBar(
              content: Text('666666'),
            ));
          },
          child: Center(
            child: Container(
              width: 100,
              height: 100,
              color: Colors.red,
            ),
          ),
        ),
      ),
    );
  }
}

開(kāi)開(kāi)心心寫(xiě)完俺猿,然后一頓運(yùn)行:

void main() => runApp(BuilderA());

點(diǎn)擊茎匠,發(fā)現(xiàn) SnackBar 并沒(méi)有正常彈出,而是出現(xiàn)了下面這種異常:

════════ Exception caught by gesture
═══════════════════════════════════════════════════════════════

The following assertion was thrown while handling a gesture:

<font color=red>Scaffold.of() called with a context that does not contain a Scaffold.</font>

...

網(wǎng)上很多資料都說(shuō)需要外包一層 Builder 可以解決這種問(wèn)題押袍,但是基本上沒(méi)說(shuō)原因诵冒,至于為什么說(shuō)可以外包一層 Builder 就可以解決,我想大部分只是看了 Scaffold 的源碼中的注釋了解到的:

scaffold.dart 第1209行到1234行:
...
/// {@tool snippet --template=stateless_widget_material}
/// When the [Scaffold] is actually created in the same `build` function, the
/// `context` argument to the `build` function can't be used to find the
/// [Scaffold] (since it's "above" the widget being returned in the widget
/// tree). In such cases, the following technique with a [Builder] can be used
/// to provide a new scope with a [BuildContext] that is "under" the
/// [Scaffold]:
///
/// ```dart
/// Widget build(BuildContext context) {
///   return Scaffold(
///     appBar: AppBar(
///       title: Text('Demo')
///     ),
///     body: Builder(
///       // Create an inner BuildContext so that the onPressed methods
///       // can refer to the Scaffold with Scaffold.of().
///       builder: (BuildContext context) {
///         return Center(
///           child: RaisedButton(
///             child: Text('SHOW A SNACKBAR'),
///             onPressed: () {
///               Scaffold.of(context).showSnackBar(SnackBar(
///                 content: Text('Have a snack!'),
///               ));
///             },
...

那到底是什么原因外包一層 Builder 控件就可以了呢谊惭?


原因分析

異常原因

上面那種寫(xiě)法為什么會(huì)異常汽馋?要想知道這個(gè)問(wèn)題,我們首先看這句描述:

Scaffold.of() called with a context that does not contain a Scaffold.

意思是說(shuō)在不包含Scaffold的上下文中調(diào)用了Scaffold.of()圈盔。

我們仔細(xì)看看這個(gè)代碼惭蟋,會(huì)發(fā)現(xiàn),此處調(diào)用的 contextBuilderA 的药磺,而在BuilderA中的 build 方法中我們才指定了 Scaffold 告组,因此確實(shí)是不存的。

為什么包一層Builder就沒(méi)問(wèn)題了癌佩?

我們把代碼改成下面這種:

class BuilderB extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Builder(
          builder: (context) => GestureDetector(
            onTap: () {
              Scaffold.of(context).showSnackBar(SnackBar(
                content: Text('666666'),
              ));
            },
            child: Center(
              child: Container(
                width: 100,
                height: 100,
                color: Colors.red,
              ),
            ),
          ),
        ),
      ),
    );
  }
}

運(yùn)行之后發(fā)現(xiàn)確實(shí)沒(méi)問(wèn)題了木缝?為什么呢?我們先來(lái)看看 Builder 源碼:

// ##### framework.dart文件下
typedef WidgetBuilder = Widget Function(BuildContext context);


// ##### basic.dart文件下
class Builder extends StatelessWidget {
  /// Creates a widget that delegates its build to a callback.
  ///
  /// The [builder] argument must not be null.
  const Builder({
    Key key,
    @required this.builder,
  }) : assert(builder != null),
       super(key: key);

  /// Called to obtain the child widget.
  ///
  /// This function is called whenever this widget is included in its parent's
  /// build and the old widget (if any) that it synchronizes with has a distinct
  /// object identity. Typically the parent's build method will construct
  /// a new tree of widgets and so a new Builder child will not be [identical]
  /// to the corresponding old one.
  final WidgetBuilder builder;

  @override
  Widget build(BuildContext context) => builder(context);
}

代碼很簡(jiǎn)單围辙,Builder 類(lèi)繼承 StatelessWidget 我碟,然后通過(guò)一個(gè)接口回調(diào)將自己對(duì)應(yīng)的 context 回調(diào)出來(lái),供外部使用姚建。沒(méi)了~
但是矫俺!外部調(diào)用:

onTap: () {
  Scaffold.of(context).showSnackBar(SnackBar(
    content: Text('666666'),
  ));
}

此時(shí)的 context 將不再是 BuilderBcontext 了,而是 Builder 自己的了!@逋小友雳!

那么問(wèn)題又來(lái)了~~~憑什么改成 Builder 中的 context 就可以了?我能這個(gè)時(shí)候就不得不去看看 Scaffold.of(context) 的源碼了:

...

static ScaffoldState of(BuildContext context, { bool nullOk = false }) {
    assert(nullOk != null);
    assert(context != null);
    final ScaffoldState result = context.ancestorStateOfType(const TypeMatcher<ScaffoldState>());
    if (nullOk || result != null)
      return result;
    throw FlutterError(
      
...省略不重要的
  @override
  State ancestorStateOfType(TypeMatcher matcher) {
    assert(_debugCheckStateIsActiveForAncestorLookup());
    Element ancestor = _parent;
    while (ancestor != null) {
      if (ancestor is StatefulElement && matcher.check(ancestor.state))
        break;
      ancestor = ancestor._parent;
    }
    final StatefulElement statefulAncestor = ancestor;
    return statefulAncestor?.state;
  } 

上面的核心部分揭露了原因:
of() 方法中會(huì)根據(jù)傳入的 context 去尋找最近的相匹配的祖先 widget铅匹,如果尋找到返回結(jié)果押赊,否則拋出異常,拋出的異常就是上面出現(xiàn)的異常包斑!

此處流礁,Builder 就在 Scafflod 節(jié)點(diǎn)下,因在 Builder 中調(diào)用 Scafflod.of(context) 剛好是根據(jù) Builder 中的 context 向上尋找最近的祖先罗丰,然后就找到了對(duì)應(yīng)的 Scafflod神帅,因此這也就是為什么包裝了一層 Builder 后就能正常的原因!

總結(jié)時(shí)刻

  • Builder 控件的作用萌抵,我的理解是在于重新提供一個(gè)新的子 context 找御,通過(guò)新的 context 關(guān)聯(lián)到相關(guān)祖先從而達(dá)到正常操作的目的。
  • 同樣的對(duì)于路由跳轉(zhuǎn) Navigator.of(context)【注:Navigator 是由 MaterialApp 提供的】 等類(lèi)似的問(wèn)題谜嫉,采用的都是類(lèi)似的原理萎坷,只要搞懂了其中一個(gè),其他的都不在話下沐兰!

當(dāng)然哆档,處理這類(lèi)問(wèn)題不僅僅這一種思路,道路千萬(wàn)條住闯,找到符合自己的那一條才是關(guān)鍵瓜浸!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市比原,隨后出現(xiàn)的幾起案子插佛,更是在濱河造成了極大的恐慌,老刑警劉巖量窘,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件雇寇,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡蚌铜,警方通過(guò)查閱死者的電腦和手機(jī)锨侯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)冬殃,“玉大人囚痴,你說(shuō)我怎么就攤上這事∩笤幔” “怎么了深滚?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵奕谭,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我痴荐,道長(zhǎng)血柳,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任蹬昌,我火速辦了婚禮混驰,結(jié)果婚禮上攀隔,老公的妹妹穿的比我還像新娘皂贩。我一直安慰自己,他們只是感情好昆汹,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布明刷。 她就那樣靜靜地躺著,像睡著了一般满粗。 火紅的嫁衣襯著肌膚如雪辈末。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,292評(píng)論 1 301
  • 那天映皆,我揣著相機(jī)與錄音挤聘,去河邊找鬼。 笑死捅彻,一個(gè)胖子當(dāng)著我的面吹牛组去,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播步淹,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼从隆,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了缭裆?” 一聲冷哼從身側(cè)響起键闺,我...
    開(kāi)封第一講書(shū)人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎澈驼,沒(méi)想到半個(gè)月后辛燥,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡缝其,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年挎塌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片氏淑。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡勃蜘,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出假残,到底是詐尸還是另有隱情缭贡,我是刑警寧澤炉擅,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站阳惹,受9級(jí)特大地震影響谍失,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜莹汤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一快鱼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧纲岭,春花似錦抹竹、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至喇闸,卻和暖如春袄琳,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背燃乍。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工唆樊, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人刻蟹。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓逗旁,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親座咆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子痢艺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容