Flutter中Widget的生命週期和渲染原理

widget

FlutterWidget的生命週期

  • StatelessWidget是通過構造函數(Constructor)接收父Widget直接傳入值,然後調用build方法來構建,整個過程非常簡單
  • StatefulWidget需要通過State來管理其數據,並且還要監控狀態的改變決定是否重新build整個Widget
  • 這裏主要討論StatefulWidget的生命週期,就是它從創建到顯示再到更新最後到銷燬的整個過程
  • StatefulWidget本身由兩個類組成的:StatefulWidgetState
  • StatefulWidget中的相關方法主要就是
    • 執行StatefulWidget的構造函數(Constructor)來創建出StatefulWidget
    • 執行StatefulWidgetcreateState方法,來創建一個維護StatefulWidgetState對象
    • 所以我們探討StatefulWidget的生命週期, 最終是探討State的生命週期
  • 那麼爲什麼Flutter在設計的時候, StatefulWidgetbuild方法要放在State中而不是自身呢
    • 首先build出來的Widget是需要依賴State中的變量(數據/自定義的狀態)的
    • Flutter在運行過程中, Widget是不斷的創建和銷燬的, 當我們自己的狀態改變時, 我們只希望刷新當前Widget, 並不希望創建新的State

圖片來源網絡

上面圖片大概列出了StatefulWidget的簡單的函數調用過程

constructor

調用createState創建State對象時, 執行State類的構造方法(Constructor)來創建State對象

initState

  • initStateStatefulWidget創建完後調用的第一個方法,而且只執行一次
  • 類似於iOSviewDidLoad,所以在這裏View並沒有完成渲染
  • 我們可以在這個方法中執行一些數據初始化的操作,或者發送網絡請求
  @override
  void initState() {
    // 這裏必須調用super的方法
    super.initState();
    print('4. 調用_HomeScreenState----initState');
  }
  • 這個方法是重寫父類的方法,必須調用super,因爲父類中會進行一些其他操作
  • 另一點在源碼中, 會看到這個方法中有一個mustCallSuper的註解, 這裏就限制了必須調用父類的方法
  @protected
  @mustCallSuper
  void initState() {
    assert(_debugLifecycleState == _StateLifecycle.created);
  }

didChangeDependencies

  • didChangeDependencies在整個過程中可能會被調用多次, 但是也只有下面兩種情況下會被調用
  1. StatefulWidget第一次創建的時候didChangeDependencies會被調用一次, 會在initState方法之後會被立即調用
  2. 從其他對象中依賴一些數據發生改變時, 比如所依賴的InheritedWidget狀態發生改變時, 也會被調用

build

  • build同樣也會被調用多次
  • 在上述didChangeDependencies方法被調用之後, 會重新調用build方法, 來看一下我們當前需要重新渲染哪些Widget
  • 當每次所依賴的狀態發生改變的時候build就會被調用, 所以一般不要將比較好使的操作放在build方法中執行

didUpdateWidget

執行didUpdateWidget方法是在當父Widget觸發重建時,系統會調用didUpdateWidget方法

dispose

  • 當前的Widget不再使用時,會調用dispose進行銷燬
  • 這時候就可以在dispose裏做一些取消監聽、動畫的操作
  • 到這裏, 也就意味着整個生命週期的過程也就結束了

setState

  • setState方法可以修改在State中定義的變量
  • 當我們手動調用setState方法,會根據最新的狀態(數據)來重新調用build方法,構建對應的Widgets
  • setState內部其實是通過調用_element.markNeedsBuild();實現更新Widget

整個過程的代碼如下:

class HomeScreen extends StatefulWidget {
  HomeScreen() {
    print('1. 調用HomeScreen---constructor');
  }
  @override
  _HomeScreenState createState() {
    print('2. 調用的HomeScreen---createState');
    return _HomeScreenState();
  }
}

class _HomeScreenState extends State<HomeScreen> {

  int _counter = 0;

  _HomeScreenState() {
    print('3. 調用_HomeScreenState----constructor');
  }

  @override
  void initState() {
    // 這裏必須調用super的方法
    super.initState();
    print('4. 調用_HomeScreenState----initState');
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print('調用_HomeScreenState----didChangeDependencies');
  }
  
  @override
  Widget build(BuildContext context) {
    print('5. 調用_HomeScreenState----build');
    return Scaffold(
      appBar: AppBar(title: Text('生命週期', style: TextStyle(fontSize: 20))),
      body: Center(
        child: Column(
          children: <Widget>[
            Text('當前計數: $_counter', style: TextStyle(fontSize: 20),),
            RaisedButton(
              child: Text('點擊增加計數', style: TextStyle(fontSize: 20),),
              onPressed: () {
                setState(() {
                  _counter++;
                });
              }
            )
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    super.dispose();

    print('6. 調用_HomeScreenState---dispose');
  }
}

打印結果如下:

flutter: 1. 調用HomeScreen---constructor
flutter: 2. 調用的HomeScreen---createState
flutter: 3. 調用_HomeScreenState----constructor
flutter: 4. 調用_HomeScreenState----initState
flutter: 調用_HomeScreenState----didChangeDependencies
flutter: 5. 調用_HomeScreenState----build

// 每次調用setState, 都會執行build
flutter: 5. 調用_HomeScreenState----build
flutter: 5. 調用_HomeScreenState----build

Flutter渲染原理

Flutter中渲染過程是通過Widget, ElementRenderObject實現的, 下面是FLutter中的三種樹結構

Flutter

Widget

這是Flutter官網對Widget的說明

Flutter widgets are built using a modern framework that takes inspiration from React. The central idea is that you build your UI out of widgets. Widgets describe what their view should look like given their current configuration and state. When a widget’s state changes, the widget rebuilds its description, which the framework diffs against the previous description in order to determine the minimal changes needed in the underlying render tree to transition from one state to the next.

  • FlutterWidgets的靈感來自React,中心思想是使用這些Widgets來搭建自己的UI界面
  • 通過當前Widgets的配置和狀態描述這個頁面應該展示成什麼樣子
  • 當一個Widget發生改變時,Widget就會重新build它的描述,框架會和之前的描述進行對比,來決定使用最小的改變在渲染樹中,從一個狀態到另一個狀態
  • 從這段說明中大概意思也就是
    • Widgets只是頁面描述層面的, 並不涉及渲染層面的東西, 而且如果所依賴的配置和狀態發生變化的時候, 該Widgets會重新build
    • 而對於渲染對象來說, 只會使用最小的開銷重新渲染髮生改變的部分而不是全部重新渲染
  • Widget Tree樹結構
    • 在整個Flutter項目結構也是由很多個Widget構成的, 本質上就是一個Widget Tree
    • 在上面的類似Widget Tree結構中, 很可能會有大量的Widget在樹結構中存在引用關係, 而且每個Widget所依賴的配置和狀態發生改變的時候, Widget都會重新build, Widget會被不斷的銷燬和重建,那麼意味着這棵樹非常不穩定
    • 所以Flutter Engin也不可能直接把Widget渲染到界面上, 這事極其損耗性能的, 所以在渲染層面Flutter引用了另外一個樹結構RenderObject Tree

RenderObject

下面是Flutter官網對RenderObject的說明

An object in the render tree.

The RenderObject class hierarchy is the core of the rendering library’s reason for being.

RenderObjects have a parent, and have a slot called parentData in which the parent RenderObject can store child-specific data, for example, the child position. The RenderObject class also implements the basic layout and paint protocols.

  • 每一個RenderObject都是渲染樹上的一個對象
  • RenderObject層是渲染庫的核心, 最終Flutter Engin是把RenderObject真正渲染到界面上的
  • RenderObject Tree
    • 在渲染過程中, 最終都會把Widget轉成RenderObject, Flutter最後在解析的時候解析的也是我們的RenderObject Tree, 但是並不是每一個Widget都會有一個與之對應的RenderObject
    • 因爲很多的Widget都不是殼渲染的Widget, 而是類似於一個盒子的東西, 對其他Widget進行包裝的作用

Element

下面是Flutter官網對Element的說明

An instantiation of a Widget at a particular location in the tree.

Widgets describe how to configure a subtree but the same widget can be used to configure multiple subtrees simultaneously because widgets are immutable. An Element represents the use of a widget to configure a specific location in the tree. Over time, the widget associated with a given element can change, for example, if the parent widget rebuilds and creates a new widget for this location.

Elements form a tree. Most elements have a unique child, but some widgets (e.g., subclasses of RenderObjectElement) can have multiple children.

  • ElementWidget在樹中具有特定位置的是實例化
  • Widget描述如何配置子樹和當前頁面的展示樣式, 每一個Element代表了在Element Tree中的特定位置
  • 如果Widget所依賴的配置和狀態發生改變的時候, 和Element關聯的Widget是會發生改變的, 但是Element的特定位置是不會發生改變的
  • Element Tree中的每一個Element是和Widget Tree中的每一個Widget一一對應的
    • Element Tree類似於HTML中的虛擬DOM, 用於判斷和決定哪些RenderObject是需要更新的
    • Widget Tree所依賴的狀態發生改變(更新或者重新創建Widget)的時候, Element根據拿到之前所保存的舊的Widget和新的Widget做一個對比, 判斷兩者的Key和類型是否是相同的, 相同的就不需要重新創建, 有需要的話, 只需要更新對應的屬性即可

對象的創建過程

Widget

  • FlutterWidget有可渲染的和不可渲染的(組件Widget)
    • 組件Widget: 類似Container…等等
    • 可渲染Widget: 類似Padding…等等
  • 下面我們先看一下組件Widget(Container)的實現過程和繼承關係
// 繼承關係Container --> StatelessWidget --> Widget
class Container extends StatelessWidget {
  @override
  Widget build(BuildContext context) {}
}

// 抽象類
abstract class StatelessWidget extends Widget {
  @override
  StatelessElement createElement() => StatelessElement(this);
  
  @protected
  Widget build(BuildContext context);
}
  • 從上面的代碼可以看到, 繼承關係比較簡單, 並沒有創建RenderObject對象
  • 我們經常使用StatelessWidgetStatefulWidget,這種Widget只是將其他的Widgetbuild方法中組裝起來,並不是一個真正可以渲染的Widget

RenderObject

這裏來看一下可渲染Widget的繼承關係和相關源代碼, 這裏以Padding爲例

// 繼承關係: Padding --> SingleChildRenderObjectWidget --> RenderObjectWidget --> Widget
class Padding extends SingleChildRenderObjectWidget {
  @override
  RenderPadding createRenderObject(BuildContext context) {
    return RenderPadding(
      padding: padding,
      textDirection: Directionality.of(context),
    );
  }

  @override
  void updateRenderObject(BuildContext context, RenderPadding renderObject) {
    renderObject
      ..padding = padding
      ..textDirection = Directionality.of(context);
  }
}

abstract class SingleChildRenderObjectWidget extends RenderObjectWidget {
  @override
  SingleChildRenderObjectElement createElement() => SingleChildRenderObjectElement(this);
}

abstract class RenderObjectWidget extends Widget {
  @override
  RenderObjectElement createElement();

  @protected
  RenderObject createRenderObject(BuildContext context);

  @protected
  void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }

  @protected
  void didUnmountRenderObject(covariant RenderObject renderObject) { }
}
  • Padding的類中,我們找不到任何和渲染相關的代碼,這是因爲Padding僅僅作爲一個配置信息,這個配置信息會隨着我們設置的屬性不同,頻繁的銷燬和創建
  • 所以真正的渲染相關的代碼那就只能在RenderObject裏面了
  • 上面代碼中, 在Padding類裏面有一個核心方法createRenderObject是用於創建一個RenderObject
  • 而且方法createRenderObject是來源於RenderObjectWidget這個抽象類裏面的一個抽象方法
  • 抽象方法是必須被子類實現的,但是它的子類SingleChildRenderObjectWidget也是一個抽象類,所以可以不實現父類的抽象方法
  • 但是Padding不是一個抽象類,必須在這裏實現對應的抽象方法,而它的實現就是下面的實現
// 這裏目的是爲了創建一個RenderPadding
RenderPadding createRenderObject(BuildContext context) {
    return RenderPadding(
      padding: padding,
      textDirection: Directionality.of(context),
    );
}

上面這段代碼中, 最終是創建了一個RenderPadding, 而這個RenderPadding又是什麼呢? 下面看看他的繼承關係和相關源代碼

// 繼承關係: RenderPadding --> RenderShiftedBox --> RenderBox --> RenderObject
class RenderPadding extends RenderShiftedBox {}

abstract class RenderShiftedBox extends RenderBox with RenderObjectWithChildMixin<RenderBox> {}

abstract class RenderBox extends RenderObject {}

RenderObject又是如何實現佈局和渲染的呢

// 當外面修改padding時
RenderPadding createRenderObject(BuildContext context) {
    return RenderPadding(
      padding: padding,
      textDirection: Directionality.of(context),
    );
}

// RenderPadding類裏面會調用padding屬性的set方法
set padding(EdgeInsetsGeometry value) {
    if (_padding == value)
      // 如果傳過來的值和之前的一樣, 就不會被重新渲染, 直接return
      return;
    _padding = value;
    _markNeedResolution();
}

// 內部會調用markNeedsLayout
void _markNeedResolution() {
    _resolvedPadding = null;
    markNeedsLayout();
}

// 這裏是RenderObject裏面的一些核心方法
abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
  // markNeedsLayout是RenderObject類裏面的方法
  // markNeedsLayout的目的就是標記在下一幀繪製時,需要重新佈局performLayout
  void markNeedsLayout() {
    if (_needsLayout) {
      return;
    }
    
    if (_relayoutBoundary != this) {
      markParentNeedsLayout();
    } else {
      _needsLayout = true;
      if (owner != null) {
        owner._nodesNeedingLayout.add(this);
        owner.requestVisualUpdate();
      }
    }
  }

  // 
  void layout(Constraints constraints, { bool parentUsesSize = false }) {
    RenderObject relayoutBoundary;
    if (!parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject) {
      relayoutBoundary = this;
    } else {
      relayoutBoundary = (parent as RenderObject)._relayoutBoundary;
    }
    if (!_needsLayout && constraints == _constraints && relayoutBoundary == _relayoutBoundary) {
      return;
    }
    _constraints = constraints;
    if (_relayoutBoundary != null && relayoutBoundary != _relayoutBoundary) {
      visitChildren(_cleanChildRelayoutBoundary);
    }
    _relayoutBoundary = relayoutBoundary;
    if (sizedByParent) {
      try {
        performResize();
      } catch (e, stack) {
        _debugReportException('performResize', e, stack);
      }
    }
    RenderObject debugPreviousActiveLayout;
    try {
      performLayout();
      markNeedsSemanticsUpdate();
    } catch (e, stack) {
      _debugReportException('performLayout', e, stack);
    }
    _needsLayout = false;
    markNeedsPaint();
  }
  
  // RenderObject還有一個可被子類重寫的paint方法
  void paint(PaintingContext context, Offset offset) { }
}

Element

  • 在上面介紹Widget中提到過我們寫的大量的Widget在樹結構中存在引用關係,但是Widget會被不斷的銷燬和重建,那麼意味着這棵樹非常不穩定
  • 如果Widget所依賴的配置和狀態發生改變的時候, 和Element關聯的Widget是會發生改變的, 但是Element的特定位置是不會發生改變的
  • ElementWidget在樹中具有特定位置的是實例化, 是維繫整個Flutter應用程序的樹形結構的穩定
  • 接下來看下Element是如何被創建和引用的, 這裏還是以ContainerPadding爲例
// 在Container的父類StatelessWidget中, 實例化了其父類的一個抽象方法
// 繼承關係: StatelessElement --> ComponentElement --> Element
abstract class StatelessWidget extends Widget {
  // 實例化父類的抽象方法, 並把當前Widget作爲參數傳入了(this)
  @override
  StatelessElement createElement() => StatelessElement(this);
}

// 在Padding的父類SingleChildRenderObjectWidget中, 實例化了其父類的一個抽象方法
// 繼承關係: SingleChildRenderObjectElement --> RenderObjectElement --> Element
abstract class SingleChildRenderObjectWidget extends RenderObjectWidget {
  
  // 實例化父類的抽象方法, 並把當前Widget作爲參數傳入了(this)
  @override
  SingleChildRenderObjectElement createElement() => SingleChildRenderObjectElement(this);
}
  • 在每一次創建Widget的時候,會創建一個對應的Element,然後將該元素插入樹中
  • 上面代碼SingleChildRenderObjectWidget實例化了父類的抽象方法createElement創建一個Element, 並把當前Widget(this)作爲SingleChildRenderObjectElement構造方法的參數傳入
  • 這也就意味着創建出來的Element保存了對當前Widget的引用
  • 在創建完一個Element之後,Framework會調用mount方法來將Element插入到樹中具體的位置
  • 這是在Element類中的mount方法, 這裏主要的作用就是把自己做一個掛載操作
  /// Add this element to the tree in the given slot of the given parent.
  ///
  /// The framework calls this function when a newly created element is added to
  /// the tree for the first time. Use this method to initialize state that
  /// depends on having a parent. State that is independent of the parent can
  /// more easily be initialized in the constructor.
  ///
  /// This method transitions the element from the "initial" lifecycle state to
  /// the "active" lifecycle state.
  @mustCallSuper
  void mount(Element parent, dynamic newSlot) {
    _parent = parent;
    _slot = newSlot;
    _depth = _parent != null ? _parent.depth + 1 : 1;
    _active = true;
    if (parent != null) // Only assign ownership if the parent is non-null
      _owner = parent.owner;
    final Key key = widget.key;
    if (key is GlobalKey) {
      key._register(this);
    }
    _updateInheritance();
  }

StatelessElement

Container創建出來的是StatelessElement, 下面我們探索一下StatelessElement創建完成後, framework調用mount方法的過程, 這裏只留下了相關核心代碼

abstract class ComponentElement extends Element {
  @override
  void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    
    _firstBuild();
  }

  void _firstBuild() {
    rebuild();
  }
  
  @override
  void performRebuild() {
    if (!kReleaseMode && debugProfileBuildsEnabled)
      Timeline.startSync('${widget.runtimeType}',  arguments: timelineWhitelistArguments);

    Widget built;
    try {
      // 這裏調用的build方法, 當前類也沒有實現, 所以還是隻能到調用者(子類裏面找該方法的實現)
      built = build();
      debugWidgetBuilderValue(widget, built);
    } catch (e, stack) {
      _debugDoingBuild = false;
      
    } finally {
      _dirty = false;
    }
    try {
      _child = updateChild(_child, built, slot);
      
    } catch (e, stack) {
      
      _child = updateChild(null, built, slot);
    }

    if (!kReleaseMode && debugProfileBuildsEnabled)
      Timeline.finishSync();
  }

  @protected
  Widget build();
}


abstract class Element extends DiagnosticableTree implements BuildContext {
  // 構造方法, 接收一個widget參數
  Element(Widget widget)
    : assert(widget != null),
      _widget = widget;

  @override
  Widget get widget => _widget;
  Widget _widget;
  
  void rebuild() {
    if (!_active || !_dirty)
      return;
      
    Element debugPreviousBuildTarget;
    
    // 這裏調用的performRebuild方法, 在當前類並沒有實現, 只能去自己的類裏面查找實現
    performRebuild();
  }

  /// Called by rebuild() after the appropriate checks have been made.
  @protected
  void performRebuild();
}


class StatelessElement extends ComponentElement {
  // 這裏的widget就是之前StatelessWidget中調用createElement創建element時傳過來的this(widget)
  StatelessElement(StatelessWidget widget) : super(widget);

  @override
  StatelessWidget get widget => super.widget as StatelessWidget;

  // 這裏的build方法就是拿到當前的widget, 並且調用自己的build方法
  @override
  Widget build() => widget.build(this);
}

上面的代碼看着有點亂, 下面就理一下

  1. 這裏我們創建的是StatelessElement, 在創建完一個Element之後,Framework會調用mount方法
  2. ComponentElement類中重寫了mount方法, 所以framwork會調用這裏的mount方法
  3. mount方法中直接調用的_firstBuild方法(第一次構建)
  4. _firstBuild方法又是直接調用的rebuild方法(重新構建)
  5. 然而在ComponentElement類中沒有重寫rebuild方法, 所以還是要調用父類的rebuild方法
  6. rebuild方法會調用performRebuild方法, 而且是調用ComponentElement內重寫的performRebuild方法
  7. performRebuild方法內, 會調用build方法, 並用Widget類型的build接收返回值
  8. 而這個build方法在StatelessElement中的實現如下
  9. 也就是說, 在創建Element之後, 創建出來的elment會拿到傳過來的widget, 然後調用widget自己的build方法, 這也就是爲什麼所有的Widget創建出來之後都會調用build方法的原因
Widget build() => widget.build(this);

所以在StatelessElement調用mount煩惱歌發最主要的作用就是掛在之後調用_firstBuild方法, 最終通過widget調用對應widgetbuild方法構建更多的東西

RenderObjectElement

  • 下面看一下可渲染的Widget又是如何創建Element的, 這裏還是以Padding爲例
  • 之前有提到Padding是繼承自SingleChildRenderObjectWidget的, 而createElement方法也是在這個類中被實現的
abstract class SingleChildRenderObjectWidget extends RenderObjectWidget {
  // 這裏是創建了一個SingleChildRenderObjectElement對象
  @override
  SingleChildRenderObjectElement createElement() => SingleChildRenderObjectElement(this);
}
  • 上面的代碼中Padding是通過父類創建了一個SingleChildRenderObjectElement對象
  • SingleChildRenderObjectElement是繼承自RenderObjectElement
  • RenderObjectElement繼承自Element
  • 接下來就是看一下mount方法的調用過程
/// 以下源碼並不全, 這裏只是拷貝了一些核心方法和相關源碼
class SingleChildRenderObjectElement extends RenderObjectElement {
  // 同樣構造函數接收一個widget參數
  SingleChildRenderObjectElement(SingleChildRenderObjectWidget widget) : super(widget);

  @override
  SingleChildRenderObjectWidget get widget => super.widget as SingleChildRenderObjectWidget;

  @override
  void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    _child = updateChild(_child, widget.child, null);
  }
}


// RenderObjectElement類的相關實現
abstract class RenderObjectElement extends Element {
  // 構造函數接收一個widget參數
  RenderObjectElement(RenderObjectWidget widget) : super(widget);
  @override
  RenderObjectWidget get widget => super.widget as RenderObjectWidget;

  /// 創建一個RenderObject類型的變量
  @override
  RenderObject get renderObject => _renderObject;
  RenderObject _renderObject;


  @override
  void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    
    // 在這裏通過傳過來的widget調用createRenderObject創建一個_renderObject
    _renderObject = widget.createRenderObject(this);
    _dirty = false;
  }
}
  • 從上面的代碼看SingleChildRenderObjectElement類中的mount方法核心是調用父類(RenderObjectElement)的mount方法
  • RenderObjectElement中的mount方法, 主要就是通過widget調用它的createRenderObject方法創建一個renderObject
  • 所以對於RenderObjectElement來說, fromework調用mount方法, 其目的就是爲了創建renderObject
  • 這也就意味着Element_renderObject也會有一個引用
  • 也就是說Element不但對_widget有一個引用, 對_renderObject也會有一個引用

StatefulElement

  • 上面提到StatefulWidget是由兩部分構成的StatefulWidgetState
  • StatefulWidget是通過createState方法,來創建一個維護StatefulWidgetState對象
class StatefulElement extends ComponentElement {
  /// 構造函數
  StatefulElement(StatefulWidget widget)
      : _state = widget.createState(),
        super(widget) {
    
    _state._element = this;
    
    _state._widget = widget;
  }

  State<StatefulWidget> get state => _state;
  State<StatefulWidget> _state;
}
  • StatefulElement內定義了一個_state變量, 並且存在對_widget的引用
  • 而在StatefulElement的構造方法中, 直接通過參數widget調用其內部的createState方法, 這個是StatefulWidget中的一個抽象方法(子類必須實現), 相信這個方法都比較熟悉
class HomeScreen extends StatefulWidget {
  HomeScreen() {
    print('1. 調用HomeScreen---constructor');
  }
  @override
  _HomeScreenState createState() {
    return _HomeScreenState();
  }
}

class _HomeScreenState extends State<HomeScreen> {
  @override
  Widget build(BuildContext context) {
    return Container()
  }
}
  • StatefulElement創建完成之後, fromework就會調用mount方法掛載, 這個過程就和上面StatelessElement中的mount方法的調用過程基本一樣了
  • 兩者不同的是:
    • StatelessElement中最後是通過widget調用widget.build(this)方法
    • StatefulElement中最後是通過_state調用_state.build(this)方法, 也就是上面_HomeScreenStatebuild方法
@override
Widget build() => _state.build(this);

BuildContext

上面多次提到的build方法是有參數的, 而且不管是StatelessWidget還是State, 他們build方法的參數都是BuildContext

// StatelessWidget
abstract class StatelessWidget extends Widget {
  @protected
  Widget build(BuildContext context);
}

// State
@optionalTypeArgs
abstract class State<T extends StatefulWidget> with Diagnosticable {
  @protected
  Widget build(BuildContext context);
}

ComponentElement創建完成之後, 會調用mount方法, 最終都會調用對應的build方法

class StatelessElement extends ComponentElement {
  @override
  Widget build() => widget.build(this);
}

class StatefulElement extends ComponentElement {
  @override
  Widget build() => _state.build(this);
}
  • 上面的build方法傳入的參數都是Element, 所以本質上BuildContext就是當前的Element
  • BuildContext主要的作用就是知道我當前構建的這個Widget在這個Element Tree上面的位置信息, 之後就可以沿着這這個Tree喜愛那個上查找相關的信息
  • 下面是兩者的繼承關係
abstract class Element extends DiagnosticableTree implements BuildContext {}

小總結

StatelessElement

  • Widget創建出來之後, Flutter框架一定會根據這個Widget創建出一個對應的Element, 每一個Widget都有一個與之對應的Element
  • Element對對當前Widget產生一個引用_widget
  • element創建完成後, fromework會調用mount方法, 最終調用_widget.build(this)方法

StatefulElement

  • Widget創建出來之後, Flutter框架一定會根據這個Widget創建出一個對應的Element, 每一個Widget都有一個與之對應的Element
  • StatefulElement構造函數中會調用widget.createState()創建一個_state, 並引用_state
  • 並且會把widget賦值給_state的一個引用_widget: _state._widget = widget;, 這樣在State類中就可以通過this.state拿到當前的Widget
  • element創建完成後, fromework會調用mount方法, 最終調用_state.build(this)方法

RenderObjectElement

  • Widget創建出來之後, Flutter框架一定會根據這個Widget創建出一個對應的Element, 每一個Widget都有一個與之對應的Element
  • element創建完成後, fromework會調用mount方法, 在mount方法中會通過widget調用widget.createRenderObject(this)創建一個renderObject, 並賦值給_renderObject
  • 所以創建的RenderObjectElement對象也會對RenderObject產生一個引用

Widget的key

我們之前創建的每一個Widget, 在其構造方法中我們都會看到一個參數Key, name這個Key到底有何作用又何時使用呢

const Scaffold({ Key key, ... })
const Container({ Key key, ... })
const Text({ Key key, ... })

我們先看一個示例需求代碼如下: 希望每次點擊刪除按鈕刪除數組的元素後, ListView中其他item的展示信息不變(包括顏色和字體)

class _HomeScreenState extends State<HomeScreen> {

  List<String> names = ["111111", "222222", "333333"];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Key Demo"),
      ),
      body: ListView(
        children: names.map((name) {
          return ListItemLess(name);
        }).toList(),
      ),

      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.delete),
        onPressed: () {
          setState(() {
            names.removeAt(0);
          });
        }
      ),
    );
  }
}

我們吧ListViewitem分別使用StatelessWidgetStatefulWidget實現, 看看兩者區別

StatelessWidget

我們先對ListItem使用一個StatelessWidget進行實現:

class ListItemLess extends StatelessWidget {
  final String name;
  final Color randomColor = Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256));

  ListItemLess(this.name);

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 60,
      child: Text(name, style: TextStyle(fontSize: 30, color: Colors.white)),
      color: randomColor,
    );
  }
}
  • 通過實踐很明顯, 每次刪除第一個元素後, 雖然也能刪除第一個ListItem, 剩餘的每一個ListItem展示的信息也是對的, 但是他們的顏色卻是每次都會發生變化
  • 這主要就是因爲, 每次刪除之後都會調用setState,也就會重新build,重新build出來的新的StatelessWidget`會重新生成一個新的隨機顏色

StatefulWidget

現在對ListItem使用StatefulWidget實現同樣的功能

class ListItemFul extends StatefulWidget {
  final String name;
  ListItemFul(this.name): super();
  @override
  _ListItemFulState createState() => _ListItemFulState();
}

class _ListItemFulState extends State<ListItemFul> {
  final Color randomColor = Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256));

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 60,
      child: Text(widget.name),
      color: randomColor,
    );
  }
}
  • 我們發現一個很奇怪的現象, 信息展示正常(刪除了第一條數據),但是從顏色上看, 是刪除了最後一條
  • 在我們每次調用setState的時候, Widget都會調用一個canUpdate函數判斷是否需要重建element
  static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }
  • 在刪除第一條數據的時候,Widget對應的Element並沒有改變
  • 而目前是沒有設置Key的, 所以Element中對應的State引用也沒有發生改變
  • 在更新Widget的時候,Widget使用了沒有改變的Element中的State, 也就是之前創建的三個element中的前兩個
  • 這也就是爲什麼刪除之後, 從顏色上看, 刪除的是最後一條

添加Key

在上面ListItemFul的基礎上, 爲每一個ListItemFul加上一個key

class ListItemFulKey extends StatefulWidget {
  final String name;
  ListItemFulKey(this.name, {Key key}): super(key: key);

  @override
  _ListItemFulKeyState createState() => _ListItemFulKeyState();
}


// 在上面使用的時候, 傳入一個不同的key
ListItemFulKey(name, key: ValueKey(name))
  • 最終這就是我們想要實現的效果了
  • 上述代碼中, 爲每一個ListItemFulKey添加了一個key值, 而且每一個的Key值都是不一樣的
  • 在刪除一個元素調用setState方法後, 會重新build的一個Widget Tree
  • Element會拿到新的Widget Tree和原來保存的舊的Widget Tree做一個diff算法
  • 根據runtimeTypekey進行比對, 和新的Widget Tree相同的會被繼續複用, 否則就會調用unnmount方法刪除

Key的分類

  • Key本身是一個抽象,不過它也有一個工廠構造器,創建出來一個ValueKey
  • 直接子類主要有:LocalKeyGlobalKey
    • LocalKey,它應用於具有相同父ElementWidget進行比較,也是diff算法的核心所在;
    • GlobalKey,通常我們會使用GlobalKey某個Widget對應的WidgetStateElement
@immutable
abstract class Key {
  /// 工廠構造函數
  const factory Key(String value) = ValueKey<String>;

  @protected
  const Key.empty();
}

abstract class LocalKey extends Key {
  /// Default constructor, used by subclasses.
  const LocalKey() : super.empty();
}

abstract class GlobalKey<T extends State<StatefulWidget>> extends Key { }

LocalKey

LocalKey有三個子類

ValueKey

  • ValueKey是當我們以特定的值作爲key時使用,比如一個字符串、數字等等

ObjectKey

  • 如果兩個學生,他們的名字一樣,使用name作爲他們的key就不合適了
  • 我們可以創建出一個學生對象,使用對象來作爲key

UniqueKey:

  • 如果我們要確保key的唯一性,可以使用UniqueKey
class ValueKey<T> extends LocalKey {
  const ValueKey(this.value);
}

class ObjectKey extends LocalKey {
  const ObjectKey(this.value);
}

class UniqueKey extends LocalKey {
  UniqueKey();
}

GlobalKey

  • GlobalKey可以幫助我們訪問某個Widget的信息,包括WidgetStateElement等對象, 有點類似於React中的ref
  • 比如我們想在HomePage中訪問HomeContenet中的widget
class HomePage extends StatelessWidget {

  final GlobalKey<_HomeContentState> homeKey = GlobalKey();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("GlobalKey Demo"),
      ),
      body: HomeContent(key: homeKey),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.delete),
        onPressed: () {
          final message = homeKey.currentState.message;
          final name = homeKey.currentState.widget.name;
          print('message = $message, name = $name');
          homeKey.currentState.newPrint();

          final currentCtx = homeKey.currentContext;
          print('currentCtx = $currentCtx');
        }
      ),
    );
  }
}

class HomeContent extends StatefulWidget {

  final String name = 'homeContent';

  HomeContent({ Key key }): super(key: key);

  @override
  _HomeContentState createState() => _HomeContentState();
}

class _HomeContentState extends State<HomeContent> {

  final String message = 'message';

  void newPrint() {
    print('new---print');
  }

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

參考文檔


歡迎您掃一掃下面的微信公衆號,訂閱我的博客!

微信公衆號

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章