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/

最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!

歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!