- 原文部落格地址: Flutter中Widget的生命週期和渲染原理
- 之前的
Flutter
系列文章中都有介紹一些常用的Widget
這裡就主要了解Flutter
的渲染原理和Widget
的生命週期
Flutter
中Widget
的生命週期
StatelessWidget
是通過建構函式(Constructor
)接收父Widget
直接傳入值,然後呼叫build
方法來構建,整個過程非常簡單- 而
StatefulWidget
需要通過State
來管理其資料,並且還要監控狀態的改變決定是否重新build
整個Widget
- 這裡主要討論
StatefulWidget
的生命週期,就是它從建立到顯示再到更新最後到銷燬的整個過程 StatefulWidget
本身由兩個類組成的:StatefulWidget
和State
- 在
StatefulWidget
中的相關方法主要就是- 執行
StatefulWidget
的建構函式(Constructor
)來建立出StatefulWidget
- 執行
StatefulWidget
的createState
方法,來建立一個維護StatefulWidget
的State
物件 - 所以我們探討
StatefulWidget
的生命週期, 最終是探討State
的生命週期
- 執行
- 那麼為什麼
Flutter
在設計的時候,StatefulWidget
的build
方法要放在State
中而不是自身呢- 首先
build
出來的Widget
是需要依賴State
中的變數(資料/自定義的狀態)的 Flutter
在執行過程中,Widget
是不斷的建立和銷燬的, 當我們自己的狀態改變時, 我們只希望重新整理當前Widget
, 並不希望建立新的State
- 首先
上面圖片大概列出了StatefulWidget
的簡單的函式呼叫過程
constructor
呼叫createState
建立State
物件時, 執行State
類的構造方法(Constructor
)來建立State
物件
initState
initState
是StatefulWidget
建立完後呼叫的第一個方法,而且只執行一次- 類似於
iOS
的viewDidLoad
,所以在這裡View
並沒有完成渲染 - 我們可以在這個方法中執行一些資料初始化的操作,或者傳送網路請求
@override
void initState() {
// 這裡必須呼叫super的方法
super.initState();
print('4. 呼叫_HomeScreenState----initState');
}
複製程式碼
- 這個方法是重寫父類的方法,必須呼叫
super
,因為父類中會進行一些其他操作 - 另一點在原始碼中, 會看到這個方法中有一個
mustCallSuper
的註解, 這裡就限制了必須呼叫父類的方法
@protected
@mustCallSuper
void initState() {
assert(_debugLifecycleState == _StateLifecycle.created);
}
複製程式碼
didChangeDependencies
didChangeDependencies
在整個過程中可能會被呼叫多次, 但是也只有下面兩種情況下會被呼叫
- 在
StatefulWidget
第一次建立的時候didChangeDependencies
會被呼叫一次, 會在initState
方法之後會被立即呼叫 - 從其他物件中依賴一些資料發生改變時, 比如所依賴的
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
, Element
和RenderObject
實現的, 下面是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.
Flutter
的Widgets
的靈感來自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.
Element
是Widget
在樹中具有特定位置的是例項化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
- 在
Flutter
中Widget
有可渲染的和不可渲染的(元件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
物件 - 我們經常使用
StatelessWidget
和StatefulWidget
,這種Widget
只是將其他的Widget
在build
方法中組裝起來,並不是一個真正可以渲染的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
的特定位置是不會發生改變的 Element
是Widget
在樹中具有特定位置的是例項化, 是維繫整個Flutter
應用程式的樹形結構的穩定- 接下來看下
Element
是如何被建立和引用的, 這裡還是以Container
和Padding
為例
// 在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);
}
複製程式碼
上面的程式碼看著有點亂, 下面就理一下
- 這裡我們建立的是
StatelessElement
, 在建立完一個Element
之後,Framework
會呼叫mount
方法 - 在
ComponentElement
類中重寫了mount
方法, 所以framwork
會呼叫這裡的mount
方法 - 在
mount
方法中直接呼叫的_firstBuild
方法(第一次構建) - 在
_firstBuild
方法又是直接呼叫的rebuild
方法(重新構建) - 然而在
ComponentElement
類中沒有重寫rebuild
方法, 所以還是要呼叫父類的rebuild
方法 - 在
rebuild
方法會呼叫performRebuild
方法, 而且是呼叫ComponentElement
內重寫的performRebuild
方法 - 在
performRebuild
方法內, 會呼叫build
方法, 並用Widget
型別的build
接收返回值 - 而這個
build
方法在StatelessElement
中的實現如下 - 也就是說, 在建立
Element
之後, 建立出來的elment
會拿到傳過來的widget
, 然後呼叫widget
自己的build
方法, 這也就是為什麼所有的Widget
建立出來之後都會呼叫build
方法的原因
Widget build() => widget.build(this);
複製程式碼
所以在
StatelessElement
呼叫mount
煩惱歌發最主要的作用就是掛在之後呼叫_firstBuild
方法, 最終通過widget
呼叫對應widget
的build
方法構建更多的東西
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
是由兩部分構成的StatefulWidget
的State
- 而
StatefulWidget
是通過createState
方法,來建立一個維護StatefulWidget
的State
物件
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)
方法, 也就是上面_HomeScreenState
的build
方法
@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);
});
}
),
);
}
}
複製程式碼
我們吧ListView
的item
分別使用StatelessWidget
和StatefulWidget
實現, 看看兩者區別
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
演算法- 根據
runtimeType
和key
進行比對, 和新的Widget Tree
相同的會被繼續複用, 否則就會呼叫unnmount
方法刪除
Key的分類
Key
本身是一個抽象,不過它也有一個工廠構造器,建立出來一個ValueKey
- 直接子類主要有:
LocalKey
和GlobalKey
LocalKey
,它應用於具有相同父Element
的Widget
進行比較,也是diff
演算法的核心所在;GlobalKey
,通常我們會使用GlobalKey
某個Widget
對應的Widget
或State
或Element
@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
的資訊,包括Widget
或State
或Element
等物件, 有點類似於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();
}
}
複製程式碼
參考文件
歡迎您掃一掃下面的微信公眾號,訂閱我的部落格!