參考:http://www.itdecent.cn/p/6e704112dc67
示例1:StatelessWidget能夠交換色塊
示例2:StatefulWidget不能交換色塊
示例3:StatefulWidget外部傳入顏色能夠交換色塊
首先是基于原生ios的理解當(dāng)時(shí)的想法是 Widget類比UIView 靠 位置都換了顏色為啥不換 ???理解不能
現(xiàn)在想想Widget只是個(gè)配置描述 Element才可以類比UIView
本文以下討論全部是基于runtimetype相同且key相同(null)
1、2的代碼可參考http://www.itdecent.cn/p/6e704112dc67
3的代碼如下 Widget build(BuildContext context) {}進(jìn)行了簡(jiǎn)化方便打斷點(diǎn)調(diào)試
_SwapColorDemo1State
class _SwapColorDemo1State extends State<SwapColorDemo2> {
List<Widget> widgets;
@override
void initState() {
// A.runtimeType == B.runtimeType;
super.initState();
widgets = [
StatefulColorfulTile(defaultColor: Colors.green),
StatefulColorfulTile(defaultColor: Colors.red,)
];
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: swapTile,
child: Row(
children: widgets,
),
);
}
swapTile() {
print("swap");
setState(() {
widgets.insert(1 , widgets.removeAt(0));
});
}
}
StatefulColorfulTile
class StatefulColorfulTile extends StatefulWidget {
final Color defaultColor;
StatefulColorfulTile({this.defaultColor,Key key}) : super(key: key);
@override
StatefulColorfulTileState createState() => StatefulColorfulTileState();
}
class StatefulColorfulTileState extends State<StatefulColorfulTile> {
// final Color defaultColor = UniqueColorGenerator().getColor();
@override
void didUpdateWidget(covariant Widget oldWidget) {
super.didUpdateWidget(oldWidget);
}
@override
Widget build(BuildContext context) {
return SizedBox(
height: 100,
width: 100,
child: Container(
color: widget.defaultColor,
),
);
}
}
對(duì)于:http://www.itdecent.cn/p/6e704112dc67在學(xué)習(xí)key的用法時(shí)對(duì)這個(gè)無(wú)法交換色塊的表現(xiàn)感到很奇怪,但對(duì)解釋有點(diǎn)一臉懵逼
尤其是對(duì)于StatefulWidget的解釋一頭霧水 猛一看其實(shí)差不多 其實(shí)還是不一樣的
1:所以 Element 樹將根據(jù) Widget 樹進(jìn)行對(duì)應(yīng)的更新。
2:Flutter 使用 Element 樹和它對(duì)應(yīng)的控件的 State 去確定要在設(shè)備上顯示的內(nèi)容, 所以 Element 樹沒(méi)有改變
當(dāng)我們交換行中的兩個(gè)色塊時(shí),F(xiàn)lutter 遍歷 Widget 樹,看看骨架結(jié)構(gòu)是否相同。它從 Row Widget 開始,然后移動(dòng)到它的子 Widget,Element 樹檢查 Widget 是否與舊 Widget 是相同類型和 Key。 如果都相同的話,它會(huì)更新對(duì)新 widget 的引用。在我們這里,Widget 沒(méi)有設(shè)置 Key,所以Flutter只是檢查類型。它對(duì)第二個(gè)孩子做同樣的事情。所以 Element 樹將根據(jù) Widget 樹進(jìn)行對(duì)應(yīng)的更新。
現(xiàn)在,我們點(diǎn)擊按鈕,交換控件的次序,F(xiàn)lutter 將遍歷 Element 樹,檢查 Widget 樹中 Row 控件并且更新 Element 樹中的引用,然后第一個(gè) Tile 控件檢查它對(duì)應(yīng)的控件是否是相同類型,它發(fā)現(xiàn)對(duì)方是相同的類型; 然后第二個(gè) Tile 控件做相同的事情,最終就導(dǎo)致 Flutter 認(rèn)為這兩個(gè)控件都沒(méi)有發(fā)生改變。Flutter 使用 Element 樹和它對(duì)應(yīng)的控件的 State 去確定要在設(shè)備上顯示的內(nèi)容, 所以 Element 樹沒(méi)有改變,顯示的內(nèi)容也就不會(huì)改變。
不符合直觀邏輯尤其是無(wú)法解釋 示例3中 添加了外部defaultColor時(shí)為何色塊又能交換了,因?yàn)閞untimetype和key都是相等的。
為了尋求答案查了兩天 先貼下用到的知識(shí)儲(chǔ)備
·StatelessWidget中
第一次構(gòu)建StatelessElement createElement() => StatelessElement(this);
StatelessElement中
Widget build() => widget.build(this);
即StatelessElement調(diào)用build其實(shí)調(diào)用的是StatelessWidget的Widget build(BuildContext context)
對(duì)于StatefulWidget
StatefulElement createElement() => StatefulElement(this);
第一次創(chuàng)建StatefulElement時(shí)將_state和StatefulElement進(jìn)行了一對(duì)一綁定
這個(gè)綁定是始終不變的
The State instance associated with this location in the tree.
There is a one-to-one relationship between State objects and the StatefulElement objects that hold them. The State objects are created by StatefulElement in mount.
第一次創(chuàng)建時(shí)
StatefulElement的widget是StatefulWidget
StatefulElement的_state是widget.createState()
_state的_element是StatefulElement
_state的_widget是StatefulElement的widget
StatefulElement(StatefulWidget widget)
: _state = widget.createState(),
super(widget) {
_state._element = this;
_state._widget = widget;
}
//此處其實(shí)是調(diào)用的_state的build方法
Widget build() => _state.build(this);
stateful和stateless執(zhí)行邏輯是相同的
首先父Widget發(fā)起
updateChild=>child.update=>rebuild=>performRebuild=>build()
方法實(shí)現(xiàn)中唯一的不同在child.update
updateChild
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
if (newWidget == null) {
if (child != null)
deactivateChild(child);
return null;
}
Element newChild;
if (child != null) {
bool hasSameSuperclass = true;
final int oldElementClass = Element._debugConcreteSubtype(child);
final int newWidgetClass = Widget._debugConcreteSubtype(newWidget);
hasSameSuperclass = oldElementClass == newWidgetClass;
if (hasSameSuperclass && child.widget == newWidget) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
newChild = child;
} else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
child.update(newWidget);
newChild = child;
} else {
deactivateChild(child);
newChild = inflateWidget(newWidget, newSlot);
}
} else {
newChild = inflateWidget(newWidget, newSlot);
}
return newChild;
}
stateful的update
//stateful的update
void update(StatefulWidget newWidget) {
super.update(newWidget);
final StatefulWidget oldWidget = _state._widget;
// Notice that we mark ourselves as dirty before calling didUpdateWidget to
// let authors call setState from within didUpdateWidget without triggering
// asserts.
_dirty = true;
_state._widget = widget as StatefulWidget;
rebuild();
}
stateless的update
//stateless的update
void update(StatelessWidget newWidget) {
super.update(newWidget);
_dirty = true;
rebuild();
}
rebuild=> performRebuild
performRebuild()
盯住這兒
void performRebuild() {
Widget built;
#盯住這兒
built = build();
_dirty = false;
_child = updateChild(_child, built, slot);
assert(_child != null);
if (!kReleaseMode && debugProfileBuildsEnabled)
Timeline.finishSync();
}
1:當(dāng)交換StatelessWidget色塊時(shí) 函數(shù)調(diào)用流程如下
第一步:Element updateChild(Element child, Widget newWidget, dynamic newSlot) {}
第二步:child.update(newWidget);
class StatelessElement extends ComponentElement
//StatelessElement的update
void update(StatelessWidget newWidget) {
super.update(newWidget);
_dirty = true;
rebuild();
}
rebuild=>performRebuild()
第三步:調(diào)用了build(),build是在Widget中的Widget build(BuildContext context)函數(shù) 使用了newWidget的build()所以色塊能夠發(fā)生交換
void performRebuild() {
Widget built;
built = build();
_dirty = false;
_child = updateChild(_child, built, slot);
assert(_child != null);
if (!kReleaseMode && debugProfileBuildsEnabled)
Timeline.finishSync();
}
從StatelessColorfulTile對(duì)應(yīng)的Element的父Element開始調(diào)用 父Element開始更新子Element
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {}
此時(shí)this是child的父Element child是StatelessColorfulTile對(duì)應(yīng)的Element
因?yàn)閞untimetype和key相等調(diào)用到child.update(newWidget);
在child.update(newWidget)中 newWidget是交換后的色塊
child.update=>rebuild()=>performRebuild()=>build()
對(duì)于StatelessElement是newWidget的build()所以色塊發(fā)生了交換
總結(jié)如下:
1:element在elementtree中沒(méi)有交換位置
2:element對(duì)應(yīng)的widget發(fā)生了交換
3:調(diào)用了newWidget的build()導(dǎo)致顏色發(fā)生了改變
2:當(dāng)交換StatefulWidget時(shí)
update變成了如下方法
//stateful的update
void update(StatefulWidget newWidget) {
super.update(newWidget);
final StatefulWidget oldWidget = _state._widget;
// Notice that we mark ourselves as dirty before calling didUpdateWidget to
// let authors call setState from within didUpdateWidget without triggering
// asserts.
_dirty = true;
_state._widget = widget as StatefulWidget;
rebuild();
}
1:super.update(newWidget);將當(dāng)前element的widget更新為newWidget
2:_state._widget = widget 將(當(dāng)前element對(duì)應(yīng)的)_state的_widget更新為
newWidget
后續(xù)和StatelessElement一樣去調(diào)用
performRebuild()
built = build();
三者統(tǒng)一了 走的方法也要和StateLess一樣了 皆大歡喜啊
但是 和諧么?不和諧! 色塊沒(méi)有發(fā)生交換當(dāng)然不和諧了!
為啥沒(méi)換??!?。?/p>
解釋如下:
前文說(shuō)過(guò)
//此處其實(shí)是調(diào)用的_state的build方法
Widget build() => _state.build(this);
而_state和Statefulelement是一一對(duì)應(yīng)的
此處ElementTree中的Statefulelement位置沒(méi)有發(fā)生改變 那Statefulelement對(duì)應(yīng)的_state還是老的 老的_state顏色是state內(nèi)部持有的沒(méi)變 即描述當(dāng)前Statefulelement的state沒(méi)變 還是執(zhí)行老的state的build方法,
你外部交換Widget位置和我Statefulelement有啥關(guān)系。。我還執(zhí)行老的build
總結(jié)如下:
1:element在elementtree中沒(méi)有交換位置
2:element對(duì)應(yīng)的widget發(fā)生了交換
3:(element對(duì)應(yīng)的)state的_widget發(fā)生了交換
4:執(zhí)行build執(zhí)行的是state的build() 但是顏色是跟隨state的 所以色塊沒(méi)有發(fā)生變化
3:那么問(wèn)題來(lái)了當(dāng)用示例3給StatefulWidget外部傳入color時(shí) 為啥色塊又能交換了呢?
頭疼?。?!
明明說(shuō)StatefulWidget不會(huì)變的?!!
哈哈解釋如下:
順著2的解釋來(lái):
我還執(zhí)行老的state中的build(),
Widget build(BuildContext context) {
return SizedBox(
height: 100,
width: 100,
child: Container(
color: widget.defaultColor,
),
);
}
里面有個(gè)widget.defaultColor ,widget是state的_widget
//Stateful的update方法
void update(StatefulWidget newWidget) {
super.update(newWidget);
final StatefulWidget oldWidget = _state._widget;
_dirty = true;
_state._widget = widget as StatefulWidget;
rebuild();
}
此處_state的_widget變成了widget widget其實(shí)是newWidget;
widget.defaultColor其實(shí)是newWidget.defaultColor
噢噢!! 執(zhí)行build()時(shí)都是newWidget的配置了呀 !
所以色塊發(fā)生了交換
總結(jié)如下:
1:element在elementtree中沒(méi)有交換位置
2:element對(duì)應(yīng)的widget發(fā)生了交換
3:(element對(duì)應(yīng)的)state的_widget發(fā)生了交換
4:執(zhí)行build執(zhí)行的是state的build() 但是顏色是跟隨state的widget的 _widget發(fā)生了交換 所以顏色發(fā)生了交換
額外問(wèn)題:為啥說(shuō)element在elementtree中沒(méi)有交換位置?
官方解釋如下:
Update the given child with the given new configuration.
This method is the core of the widgets system. It is called each time we are to add, update, or remove a child based on an updated configuration.
The newSlot argument specifies the new value for this element's slot.
If the child is null, and the newWidget is not null, then we have a new child for which we need to create an Element, configured with newWidget.
If the newWidget is null, and the child is not null, then we need to remove it because it no longer has a configuration.
If neither are null, then we need to update the child's configuration to be the new configuration given by newWidget. If newWidget can be given to the existing child (as determined by Widget.canUpdate), then it is so given. Otherwise, the old child needs to be disposed and a new child created for the new configuration.
If both are null, then we don't have a child and won't have a child, so we do nothing.
The updateChild method returns the new child, if it had to create one, or the child that was passed in, if it just had to update the child, or null, if it removed the child and did not replace it.
The following table summarizes the above:
| | newWidget == null | newWidget != null | | :-----------------: | :--------------------- | :---------------------- | | child == null | Returns null. | Returns new Element. | | child != null | Old child is removed, returns null. | Old child updated if possible, returns child or new Element. |
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
if (newWidget == null) {
if (child != null)
deactivateChild(child);
return null;
}
Element newChild;
if (child != null) {
bool hasSameSuperclass = true;
final int oldElementClass = Element._debugConcreteSubtype(child);
final int newWidgetClass = Widget._debugConcreteSubtype(newWidget);
hasSameSuperclass = oldElementClass == newWidgetClass;
if (hasSameSuperclass && child.widget == newWidget) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
newChild = child;
} else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
child.update(newWidget);
newChild = child;
} else {
deactivateChild(child);
newChild = inflateWidget(newWidget, newSlot);
}
} else {
newChild = inflateWidget(newWidget, newSlot);
}
return newChild;
}
當(dāng)進(jìn)行交換時(shí)打斷點(diǎn)可以發(fā)現(xiàn)
1:沒(méi)有創(chuàng)建新的Element
2:邏輯判斷都是進(jìn)更新當(dāng)前child(Element)而非創(chuàng)建
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
child.update(newWidget);
newChild = child;
ElementTree的初始化創(chuàng)建和ElementTree某個(gè)Element的更新
flutter底層內(nèi)核邏輯依然是函數(shù)式的