文章转载自:juejin.cn/post/700146…
上一篇文章中,我们遇到了一个问题 , widget换位置的时候,出现了一些意外,并没有按照我们所想去调换位置, 这时候我们就很困惑, 那是在我们看来,写了一个widget
,那这个widget
就该按照我们设定的那样展示, 但实际上, widget
并不是最终出现在屏幕上的东西.
Widget Tree
可能有人听说过Widget Tree , 就是嵌套着widget
的东西. 比如现在这段代码:
Column(
children: [
Box(Colors.blue),
Box(Colors.red),
],
)
复制代码
对应的Widget Tree就是下面这个样子:
- Column
- Box
- Box
复制代码
但真正绘制在屏幕上的并不是这个Widget Tree
, 我们都知道Wdiget是描述UI元素Element的配置
,所以实际运行起来的时候,还会产生一个Element Tree
, Element Tree
就是Widget Tree
实例化之后产生的对象.
Widget Tree ---实例化---> Element Tree
- Column - ColumnElement
- Box -实例化-> - BoxElement
- Box - BoxElement
复制代码
Widget负责如何渲染,比如颜色,大小,形状等等.
Element负责管理里面的状态.
所以状态是随着Element
来改变的,外观是随着Widget
改变的.二者是分开的.
为啥要分开的呢?
因为Widget是不可变的,我们可能经常会看到这样的提示:
不可变,意思就是创建之后就不能在运行时改变它,但是State
是可以变的.通常我们会用setState
通知flutter去重建一个新的widget,而不是去改变这个widget,因为widget是不可变的.但是丢掉这个widget,并没有把状态也一起丢掉.
因为大部分UI的设计,是通过我们改变Widget来实现的,所以如果能把Widget和State分开,在hot reload
的时候,就能在改动widget的时候保留它的状态. 如果widget每次有变化,state就丢掉的话,程序就没法用了,没有办法支持Hot Reload
和动画效果了.
为什么改变Widget的顺序,UI上并没有改变顺序
所以当Wdiget改变的时候,我们尽量将它与原来的State关联起来.
如何关联的呢?
当Widget改变的时候,flutter会去看Wdiget和这个Element是不是同样的的,比如我们把Columen
换成Row
,那它们就不一样了,然后会去看Key是一样,这也就是Key可以帮助Flutter把容易混淆的Widget区分清楚.当然,如果不传key,它就没办法对比key了,如果widget不变,依然会使用之前的element.
有点类似判断两个对象是否相等,会先去比较它们的类型.
因此我们把Box
调换位置的时候,比如上面有两个Box
, 改变位置的时候,对应的Widget Tree是原来的类型,所以BoxElement刚好可以和之前的Box对应上,所以Flutter会以为没有改变.
所以当我们做如下改变的时候:
Column( Column(
children: [ children: [
Box(Colors.blue), -> Box(Colors.red),
Box(Colors.red), Box(Colors.blue),
] ]
复制代码
在Flutter看来,就是将上面的Widget变成了红色, 下面的变成了蓝色而已. 这么看似乎也没有问题.
接下来,我们看看有Key的情况下
- Column - ColumnElement
- Box (Key1) -实例化-> - BoxElement (Key1)
- Box (Key2) - BoxElement (Key2)
复制代码
将上下两个Box调换位置, 在比较类型相同之后比较Key的时候,第一个BoxElement (Key1)发现对应位置的Box的key不一致,此时Flutter就会在同一级别去搜索其他widget,看看有没有一样的key,然后找到了下面有一个对应的box,同理下面的也是一样的找法.
那么改变之后的样子就是这样的:
- Column - ColumnElement
- Box (Key2) -实例化-> - BoxElement (Key1)
- Box (Key1) - BoxElement (Key2)
复制代码
也就达到了状态也会互换的作用,Flutter能够借助Key来跟踪顺序的变化.
那如果我们像之前一样删除其中一个呢?
Column(
children: [
Box(Colors.blue),
Box(Colors.red),
],
)
复制代码
删掉第一个之后变成:
Column(
children: [
Box(Colors.red),
],
)
复制代码
那么红盒子就变成了第一个. 原来的
- ColumnElement
- BoxElement
- BoxElement
复制代码
第一个BoxElement就对应了这个红色的Box,第二个没找到,此时它就被丢弃了.
那有Key的情况下,第一个Element去找Widget的时候,第一个key对不上,同级又找不到对应的,那么它自己就丢弃了,第二个Element就对应到了第一个,变化也就和我们预期的一致了.
增加一个Wdiget也是同样的道理.因为Element是按顺序从上到下找的,没有key的情况下,新增的同类型widget,会替代之前位置上的widget与element对应,最后位置的widget因为没有对应的element,它就会调用createState来创建一个新的Stete.
作者:__white
链接:juejin.cn/post/700146…