一朋友面試,被問到在Flutter中一些因 context
引發的路由異常的問題,爲何包裝一層 Builder
控件以後,路由或點擊彈框事件正常使用了?而後就沒而後了。。。相信不少人都會用,至於爲何,也沒深究。面試
相信不少剛開始玩Flutter的同窗都會在學習過程當中都會寫到相似下面的這種代碼:markdown
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, ), ), ), ), ); } } 複製代碼
開開心心寫完,而後一頓運行:app
void main() => runApp(BuilderA()); 複製代碼
點擊,發現 SnackBar
並無正常彈出,而是出現了下面這種異常:less
════════ Exception caught by gesture
═══════════════════════════════════════════════════════════════
The following assertion was thrown while handling a gesture:
Scaffold.of() called with a context that does not contain a Scaffold.
...ide
網上不少資料都說須要外包一層 Builder
能夠解決這種問題,可是基本上沒說緣由,至於爲何說能夠外包一層 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
控件就能夠了呢?ui
上面那種寫法爲何會異常?要想知道這個問題,咱們首先看這句描述:this
Scaffold.of() called with a context that does not contain a Scaffold.spa
意思是說在不包含Scaffold的上下文中調用了Scaffold.of()。
咱們仔細看看這個代碼,會發現,此處調用的 context
是 BuilderA
的,而在BuilderA
中的 build
方法中咱們才指定了 Scaffold
,所以確實是不存的。debug
咱們把代碼改爲下面這種:
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, ), ), ), ), ), ); } } 複製代碼
運行以後發現確實沒問題了?爲何呢?咱們先來看看 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); } 複製代碼
代碼很簡單,Builder
類繼承 StatelessWidget
,而後經過一個接口回調將本身對應的 context
回調出來,供外部使用。沒了~ 可是!外部調用:
onTap: () { Scaffold.of(context).showSnackBar(SnackBar( content: Text('666666'), )); } 複製代碼
此時的 context
將再也不是 BuilderB
的 context
了,而是 Builder
本身的了!!!
那麼問題又來了~~~憑什麼改爲 Builder
中的 context
就能夠了?我能這個時候就不得不去看看 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()
方法中會根據傳入的 context
去尋找最近的相匹配的祖先 widget
,若是尋找到返回結果,不然拋出異常,拋出的異常就是上面出現的異常!
此處,Builder
就在 Scafflod
節點下,因在 Builder
中調用 Scafflod.of(context)
恰好是根據 Builder
中的 context
向上尋找最近的祖先,而後就找到了對應的 Scafflod
,所以這也就是爲何包裝了一層 Builder
後就能正常的緣由!
Builder
控件的做用,個人理解是在於從新提供一個新的子 context
,經過新的 context
關聯到相關祖先從而達到正常操做的目的。Navigator.of(context)
【注:Navigator
是由 MaterialApp
提供的】 等相似的問題,採用的都是相似的原理,只要搞懂了其中一個,其餘的都不在話下!固然,處理這類問題不單單這一種思路,道路千萬條,找到符合本身的那一條纔是關鍵!