flutter系列之:構建Widget的上下文環境BuildContext詳解
簡介
我們知道Flutter中有兩種Widget,分別是StatelessWidget和StatefulWidget,StatelessWidget中有一個build方法來創建對應的Widget,雖然StatefulWidget中沒有對應的build方法,但是和StatefulWidget對應的State中也有同樣的build方法。
這個build方法就是用來創建Widget的核心方法。
我們來看下build方法的定義:
Widget build(BuildContext context);
build方法傳入一個BuildContext對象,返回一個Widget對象,也就是說這個BuildContext中包含了要創建的Widget的所有資訊。這個BuildContext被稱為是Widget的上下文構建環境。
那麼BuildContext有什麼特性呢?我們又該如何使用BuildContext呢?一起來看看吧。
BuildContext的本質
還記得flutter中的三顆樹嗎?
他們分別是Widgets樹,Element樹和Render樹。其中Widgets樹和Element樹是一一對應的。而Render樹和Element中的RenderObjectElement是一一對應的。
事實上BuildContext就是一個Element對象。怎麼說呢?
我們先看下BuildContext的定義:
abstract class BuildContext {
Widget get widget;
...
}
BuildContext是一個抽象類,我們再看一下Element類的定義:
abstract class Element extends DiagnosticableTree implements BuildContext {
可以看到,Element對象實現了BuildContext介面,而每一個BuildContext都有一個和其綁定的Widget對象。
經過複雜的關係傳遞運算,我們可以知道Element對象和Widget對象從程式碼層面來說,確實是一一對應的。
BuildContext和InheritedWidget
InheritedWidget是一種widget用來在tree中向下傳遞變動資訊,在tree的子節點中,可以通過調用BuildContext.dependOnInheritedWidgetOfExactType在子節點中查找最近的父InheritedWidget,從而將當前的BuildContext綁定的widget和InheritedWidget建立綁定關係,從而在下次InheritedWidget發生變動的時候,會自動觸發BuildContext綁定的widget的rebuild方法。
聽起來好像很複雜的樣子,但是實際上很簡單,我們舉個例子,首先我們需要定義一個Widget用來繼承InheritedWidget:
class FrogColor extends InheritedWidget {
const FrogColor({
Key? key,
required this.color,
required Widget child,
}) : super(key: key, child: child);
final Color color;
static FrogColor of(BuildContext context) {
final FrogColor? result = context.dependOnInheritedWidgetOfExactType<FrogColor>();
assert(result != null, 'No FrogColor found in context');
return result!;
}
@override
bool updateShouldNotify(FrogColor old) => color != old.color;
}
在這個方法中,我們需要定義一個of方法,這個該方法中,我們調用context.dependOnInheritedWidgetOfExactType方法,用來查找離BuildContext最近的FrogColor。
然後可以這樣使用:
class MyPage extends StatelessWidget {
const MyPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: FrogColor(
color: Colors.green,
child: Builder(
builder: (BuildContext innerContext) {
return Text(
'Hello Frog',
style: TextStyle(color: FrogColor.of(innerContext).color),
);
},
),
),
);
}
}
我們的本意是希望child中的Text組件的style根據父widget中的FrogColor的color來進行變化。所以在子組件的style中調用了FrogColor.of(innerContext)方法,對InheritedWidget進行查找,同時建立綁定關係。
在BuildContext中,有兩個查找並且進行綁定的方法,他們是:
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect });
T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({ Object? aspect });
兩者的區別是,後者限定了查找的類型。
除了dependOn之外,BuildContext還提供了兩個查找的方法:
InheritedElement? getElementForInheritedWidgetOfExactType<T extends InheritedWidget>();
T? findAncestorWidgetOfExactType<T extends Widget>();
T? findAncestorStateOfType<T extends State>();
T? findRootAncestorStateOfType<T extends State>();
T? findAncestorRenderObjectOfType<T extends RenderObject>();
他們和depend的區別是,他們不會建立依賴關係,只是單純的進行查找。
BuildContext的層級關係
因為每個widget都有一個BuildContext,所以我們在使用的過程中一定要注意傳入的BuildContext到底綁定的是哪個widget。
如下面的程式碼所示:
class MyPage extends StatelessWidget {
const MyPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: FrogColor(
color: Colors.green,
child: Builder(
builder: (BuildContext innerContext) {
return Text(
'Hello Frog',
style: TextStyle(color: FrogColor.of(innerContext).color),
);
},
),
),
);
}
}
在FrogColor的child中,我們創建了一個新的Builder,並提供了一個新的innerContext。
為什麼要這樣做呢?因為如果我們不創建子innnerContext的話,使用的context就是Scaffold的,這樣FrogColor.of將會找不到要找的對象,從而報錯。
所以我們在使用BuildContext的時候,一定要注意。
總結
BuildContext是構建Widget的基礎,它也提供了一些非常有用的查找和綁定的功能,希望能對大家有所幫助。
更多內容請參考 //www.flydean.com/04-flutter-buildcontext/
最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!