天天看點

Flutter | 和小老弟一起玩轉Widget

Flutter | 和小老弟一起玩轉Widget

目錄概述

Flutter | 和小老弟一起玩轉Widget

什麼是Widget?

在Flutter的世界中,一切都是Widget,即一切都是元件

why? 為什麼一切都是元件,怎麼了解呢?

你可以了解為在Flutter中一切都可以通過組合的方式實作,對于我們開發者,隻需聲明,或者說告訴架構這個元件是什麼,它要怎麼顯示,攜帶了哪些參數,而Widget 就是用來幫你承載配置的東西。

按照傳統的 Android 開發思想,在Android中,ui元件就是普通的一個元件,我聲明什麼顯示什麼,所見即所得, 而在Flutter中,widget 不僅可以表示ui 元件,也可以表示一些功能性元件,比如可以用于手勢監測的 widget(這在Android中相當于一個功能類),或者說用于app主題傳遞的 Theme等。是以我們可以了解為, widget就是一個控件,在Flutter裡,任意都可以通過其實作。

Widget和Element

在Flutter中,如果用官方的解釋,Widget 僅僅是一個描述顯示元素的配置資料(官方解釋),而真正代表螢幕上顯示元素的是 Element(相當于一個紐帶,用于連接配接widget和具體渲染的一個中間人) ,是以可以了解為,**widget隻是ui元素的一個配置資料,并且一個widget可以對應多個Element.**這是因為在實際渲染時,UI 🌲 上的每一個 Element 節點都會對應一個Widget對象。

上面這個描述可能聽起來有些繞口,但是暫時你可以直接認為,widget不是實際螢幕顯示元素,它僅僅隻是描述了要顯示的實際元素的配置屬性,然後在實際運作中,flutter 會将每一個widget與每一個element對應,也就是我們有兩個樹。

是以我們可以總結如下:

  • Widget 實際上就是 Element 的配置資料,Widget樹實際上是一個配置樹,而真正的渲染樹是由 Element 構成,不過,由于Element 是通過Widget生成,它們之間存在對應關系,是以大多數場景下,我們可以大體上認為 Widget樹就是指 UI控件樹或者UI渲染樹。
  • 一個Widget對象可以對應多個 Element 對象,可以了解為,同一份配置(widget) 可以建立多個執行個體 (Element)

Widget主要接口

Widget 本身是一個抽象類,其中最核心的部分就是 定義了 createElement 接口,在實際開發中,我們一般通過繼承 StatelessWidget 或者 StatefulWidget 來間接實作一個新元件。

@immutable
abstract class Widget extends DiagnosticableTree {
  const Widget({ this.key });
  final Key key;

  @protected
  Element createElement();

  @override
  String toStringShort() {
    return key == null ? '$runtimeType' : '$runtimeType-$key';
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;
  }

  static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }
}           

複制

  • widget 繼承自 DianosticableTree ,DiagnosticableTree 即診斷書,主要作用是提供調試資訊。
  • key: 這個 key 屬性類似于React/Vue 的 key,主要的作用是決定是否在下次 build 時複用舊的widget ,決定的條件在 canUpdate() 方法中。
  • createElement() Flutter Framework在建構UI樹時,會先調用此方法生成對應節點的 Element 對象,此方法是 Flutter Framework 隐私調用的。
  • debugFillProgerties(…) 複用父類的方法,主要是設定診斷樹的一些特性。
  • canUpdate(…) 是一個靜态方法,它主要用于在Widget 樹 重新 build 時複用舊的 widget。

有狀态和無狀态

我相信大家都聽說過,有狀态和無狀态這兩個詞,有種傻傻分不清的趕腳。

Flutter 中的 Widget包含兩種,一種是不需要更改狀态的 Widget,也就是 StatelessWidget,另一種是可變狀态的 StatefulWidget,注意這裡所說的狀态都是Widget裡的狀态 State,而管理狀态一般是通過 setState 來管理。

通俗點了解:

  • 有狀态: 互動或者資料改變導緻 Widget改變,例如改變文字
  • **無狀态:**不會被改變的 Widget,比如一個純頁面的展示
需要注意的是,使用 StatefulWidget 時,每次直接setState會導緻整個widget全部重建,是以在使用時,我們應該盡量把 子widget 抽離出去,采用局部重新整理的方式優化,當然這個技巧具體可以百度或者參閱我之前的代碼,并不是什麼騷操作,基本入門技巧吧。

Context

StatelessWidget build 方法有一個 context參數,它是 BuildContext 類的一個執行個體,表示目前 widget 在 widget中的上下文,每一個 widget 都會對應一個 context 對象。實際上, context 是目前widget在widget樹中任意位置中執行相關操作的一個句柄。

比如它提供了從目前 widget 開始向上周遊 widget樹以及按照 widget類型 查找父級widget的方法 findAncestorWidgetOfExactType。

context.findAncestorWidgetOfExactType<xxWidget>();           

複制

State

在Flutter中,一個 StatefulWidget 類會對應一個 State類,State表示與其對應的 statefulWidget 要維護的狀态,State中的保護的狀态資訊可以:

  1. 在widget建構時可以被同步讀取;
  2. 在widget生命周期改變時可以被讀取,當 State 被改變時,可以手動調用 其 setState() 方法通知 Flutter framework狀态發送改變,Flutter framework在收到消息後,會重新調用其 build 方法重新建構 widget 樹,進而達到更新UI的目的.

State 中有兩個常用屬性:

  1. widget: 它表示與該State 示例關聯的 widget 執行個體,由 Flutter framework 動态設定,不過這種關聯并非永久,因為在應用生命周期中,UI樹上的某一個節點 widget 示例在重新建構時可能會變化,但 State 執行個體隻會在第一次插入到樹中時被建立,當在重新建構時,如果 widget 被修改了,Flutter framework 會動态設定State, widget為新的 widget 執行個體。
  2. context StatefulWidget 對應的 BuildContext, 作用同 StatelessWidget 的 BuildContext一緻。

State生命周期

initState()

當 Widget 第一次插入到 Widget樹時被調用。對于每一個 State 對象,Flutter framework隻會調用一次回調。适合做一些一次性的操作,比如狀态初始化,訂閱子樹的事件通知等。

不能在 該回調 中調用 BuildContext.dependOnInheritedWidgetOfExactType,原因是在初始化完成後, Widget 樹中的 InheritFromWidget 也可能會發生變化,是以正确的做法應該在 build 方法或 didChangDependencied() 中調用它。

didChangeDependencies()

當State 對象的依賴發生變化時被調用。

build()

主要用于建構 Widget 子樹時被調用,它會在如下場景被調用:

  1. 在調用 initState 之後
  2. 調用 didUpdateWidget 之後
  3. 調用 setState 之後
  4. 調用 didChangeDependencies 之後
  5. 在 State 對象從樹中一個位置移除後,又重新插入到樹的其它位置之後

reassemble()

此回調是專門為開發調試而提供,在熱重載 (hot reload) 時被調用,此回調在 release 下永遠不會被調用。

didUpdateWidget()

widget重建時,如果新舊 widget 的key相同就會調用此方法

deactivate()

當State對象從樹中被移除時,會調用此方法。在一些場景下,Flutter framework 會将State 對象重新插入到樹中,如包含此 State 對象的子樹在樹的一個位置移動到了另一個位置時。如果移除後沒有重新插入到樹中則緊挨着會調用 disponse 方法。

dispose()

當State對象從樹中被永久移除時調用,通常用于在此回調中釋放資源。

生命周期圖

Flutter | 和小老弟一起玩轉Widget

Process儲存位址

具體動畫示例

Flutter | 和小老弟一起玩轉Widget
Flutter | 和小老弟一起玩轉Widget

具體代碼詳見本篇示例代碼庫。

如何擷取State對象

由于 StatefulWidget 的具體邏輯都在其對應的 State 中,是以很多時候,我們需要擷取 StatefulWidget 對應的 State對象來調用一些方法,比如 Scaffold 元件對應的狀态類 ScaffoldState 中就定義了打開 SncakBar(路由底部提示條)的方法,我們有兩種方法在子 widget 樹中擷取 父級 StatefulWidget的 State 對象。

通過Context擷取

context 對象有一個 findAncestorStateOfType() 方法,該方法可以從目前節點沿着 widget 樹向上查找指定類型的 StatefulWidget 對應的 State 對象。

findAncestorStateOfType

ScaffoldState _state = context.findAncestorStateOfType<ScaffoldState>();           

複制

of靜态方法

ScaffoldState _state = Scaffold.of(context);           

複制

通過GlobalKey

GlobalKey 是 Flutter提供的一種在整個App 中引用 element 的機制。如果 一個 widget 設定了 GlobalKey,那麼我們便可以通過 globalKey.currentWidget 獲得該 widget 對象,globalKey.currentElement 來獲得widget對應的 element 對象,如果目前 widget 是 StatefulWidget ,則可以通過 globalKey.currentState 來獲得該 widget 對應的 state 對象。

示例如下:

  1. 給目标 StatefuleWidget 添加 GlobalKey
  2. 通過 GlobalKey 來擷取 Satate 對象
GlobalKey<ChildState> childState = GlobalKey();

 ...//
  childState.currentState.xxxx           

複制

詳見本篇示例代碼庫。

需要注意的是:GlobalKey開銷較大,如果有其他可選方案,應盡量避免使用它,另外同一個 GlobalKey 在整個 widget樹中必須是唯一,不能重複。

參考資料

[Flutter實戰 - Widget簡介]