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
的簡單的函數調用過程html
調用createState
建立State
對象時, 執行State
類的構造方法(Constructor
)來建立State
對象node
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
在整個過程當中可能會被調用屢次, 可是也只有下面兩種狀況下會被調用StatefulWidget
第一次建立的時候didChangeDependencies
會被調用一次, 會在initState
方法以後會被當即調用InheritedWidget
狀態發生改變時, 也會被調用build
一樣也會被調用屢次didChangeDependencies
方法被調用以後, 會從新調用build
方法, 來看一下咱們當前須要從新渲染哪些Widget
build
就會被調用, 因此通常不要將比較好使的操做放在build
方法中執行執行didUpdateWidget
方法是在當父Widget
觸發重建時,系統會調用didUpdateWidget
方法算法
Widget
再也不使用時,會調用dispose
進行銷燬dispose
裏作一些取消監聽、動畫的操做setState
方法能夠修改在State
中定義的變量setState
方法,會根據最新的狀態(數據)來從新調用build
方法,構建對應的Widgets
setState
內部實際上是經過調用_element.markNeedsBuild();
實現更新Widget
整個過程的代碼以下:api
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
中渲染過程是經過Widget
, Element
和RenderObject
實現的, 下面是FLutter
中的三種樹結構微信
這是Flutter
官網對Widget
的說明markdown
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
下面是Flutter
官網對RenderObject
的說明app
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
下面是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
和類型是不是相同的, 相同的就不須要從新建立, 有須要的話, 只須要更新對應的屬性便可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
這裏來看一下可渲染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) { }
}
複製代碼
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();
}
複製代碼
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
方法構建更多的東西
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
也會有一個引用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);
複製代碼
上面屢次提到的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 {}
複製代碼
Widget
建立出來以後, Flutter
框架必定會根據這個Widget
建立出一個對應的Element
, 每個Widget
都有一個與之對應的Element
Element
對對當前Widget
產生一個引用_widget
element
建立完成後, fromework
會調用mount
方法, 最終調用_widget.build(this)
方法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)
方法Widget
建立出來以後, Flutter
框架必定會根據這個Widget
建立出一個對應的Element
, 每個Widget
都有一個與之對應的Element
element
建立完成後, fromework
會調用mount
方法, 在mount
方法中會經過widget
調用widget.createRenderObject(this)
建立一個renderObject
, 並賦值給_renderObject
RenderObjectElement
對象也會對RenderObject
產生一個引用咱們以前建立的每個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
實現, 看看二者區別
咱們先對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`會從新生成一個新的隨機顏色如今對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
中的前兩個在上面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
自己是一個抽象,不過它也有一個工廠構造器,建立出來一個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
有三個子類
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
能夠幫助咱們訪問某個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();
}
}
複製代碼
歡迎您掃一掃下面的微信公衆號,訂閱個人博客!