在Widget的构造方法中,有Key这么一个可选参数,用作Element和Widget进行比较
Key是一个抽象类,有LocalKey和GlobalKey两种
LocalKey
ValueKey,ObjectKey和UniqueKey三种类型:
- ValueKey: 以一个数据作为Key,如:数字、字符
- ObjectKey: 以Object对象作为Key
- UniqueKey: 可以保证Key的唯一性,一旦使用UniqueKey那么就不存在Element复用了,只要刷新Widget就好重建Element
当不传入Key时,按照Element的增量渲染,可能会存在问题
class LocalKeyDemo extends StatefulWidget {@override_LocalKeyDemoState createState() => _LocalKeyDemoState();}class _LocalKeyDemoState extends State<LocalKeyDemo> {List<TitleItem> _items = [TitleItem(title: "aaaaa"),TitleItem(title: "bbbbb"),TitleItem(title: "ccccc"),];@overrideWidget build(BuildContext context) {return Container(child: Scaffold(appBar: AppBar(title: Text("Key"),),body: Row(mainAxisAlignment: MainAxisAlignment.center,children: _items,),floatingActionButton: FloatingActionButton(onPressed: (){setState(() {_items.removeAt(0);});},child: Icon(Icons.delete),),),);}}class TitleItem extends StatefulWidget {final String title;TitleItem({this.title});@override_TitleItemState createState() => _TitleItemState();}class _TitleItemState extends State<TitleItem> {final Color _randomColor = Color.fromRGBO(Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1);@overrideWidget build(BuildContext context) {return Container(color: _randomColor,width: 100,height: 100,child: Center(child: Text(widget.title),),);}}
上述代码 创建了3个颜色不同且不同文本的子widget,按照从左往右排列,要注意的是,颜色对象保存在State中,而文本对象保存在Widget中,然后点击按钮,每次删除最左边的widget。
执行结果:
虽然每次都能删除文本正确的widget,但是颜色值下一个widget的颜色
这种奇怪现象的原因其实和Flutter的增量渲染机制有关:
我们知道widget树是对应着element树的,而widget树是不稳定的,widget在每次的刷新中都有可能在创建或者销毁,而element不会,他会去对比树中新的widget和旧widget是否一致,是否可以更新widget,如果可以更新,那么element将会指向新的widget。如下图:


一开始,widget树的节点与element树的节点是一一对应的,element的_widget属性指向了对应的widget,当widget树发生改变

element树并不会全部重新创建,而是与从左到右比较,看是否新的节点的widget与原先的节点的widget是否一致,而判断的依据我们可以在源码中看到
static bool canUpdate(Widget oldWidget, Widget newWidget) {return oldWidget.runtimeType == newWidget.runtimeType&& oldWidget.key == newWidget.key;}
他比较的新旧widget的runtimeType和key值,明显widgetA和widgetB的runtimeType和key均一致,所以element会变成这样

element会复用,而widget则换成新的widget,当state并没有改变,而在案例中颜色是存放在sate中的,所以,首个element的文本变成了widgetB的文本,但是颜色还是sateA的的颜色,而widgetC的颜色变成了stateB的颜色,而由于原先3个节点变成两个,所以最后一个element被移除,最终变成我们看到的结果。
要解决这个问题,我们只需要在Flutter判断是否更新widget的时候给一个false的结果,就能解决这个问题,两个对比条件中,runtimeType肯定是一致的,所以,可以给widget添加一个唯一标示key
我们只需要对代码做下面的修改
class _LocalKeyDemoState extends State<LocalKeyDemo> {List<TitleItem> _items = [TitleItem(title: "aaaaa", key: ValueKey(1),),TitleItem(title: "bbbbb", key: ValueKey(2),),TitleItem(title: "ccccc", key: ValueKey(3),),];}class TitleItem extends StatefulWidget {final String title;TitleItem({this.title, Key key}) : super(key: key);@override_TitleItemState createState() => _TitleItemState();}
这样,结果就是正常的了。
final Color _randomColor变量定义在StatefulWidget中结果也是正常的,由于Widget持有_randomColor,所有复用Element在改变Widget时,颜色也会随Widget一起编码
LocalKey可以给Widget作为唯一表示,在element树更新能准确的更新。
GlobalKey
GlobalKey可以获取到对应的State,Element以及Widget,源码如下:
BuildContext get currentContext => _currentElement;Widget get currentWidget => _currentElement?.widget;T get currentState {final Element element = _currentElement;if (element is StatefulElement) {final StatefulElement statefulElement = element;final State state = statefulElement.state;if (state is T)return state;}return null;}}
利用这个特性,可以实现局部刷新从而进行优化:
如果只是根widget的按钮被点击,而需要改变的仅仅是子widget,我们并不需要刷新整个widget树,
根Widget通过GlobalKey拿到对应子Widget的State,仅刷新子widget的状态,从而优化性能。
class GlobalKeyDemo extends StatelessWidget {final GlobalKey<_ChildPageState> _globalKey = GlobalKey();@overrideWidget build(BuildContext context) {return Container(child: Scaffold(appBar: AppBar(title: Text("GlobalKey"),),body: ChildPage(key: _globalKey),floatingActionButton: FloatingActionButton(onPressed: () {_globalKey.currentState.setState(() {_globalKey.currentState.count ++;});},child: Icon(Icons.add),),),);}}class ChildPage extends StatefulWidget {ChildPage({Key key}):super(key: key);@override_ChildPageState createState() => _ChildPageState();}class _ChildPageState extends State<ChildPage> {int count = 0;@overrideWidget build(BuildContext context) {return Container(child: Center(child: Text(count.toString(), style: TextStyle(fontSize: 30, fontWeight: FontWeight.w800, color: Colors.blue),),),);}}
总结
Key是一个抽象类,有LocalKey和GlobalKey两个子类:
LocalKey可以作为Widget的唯一标示,避免Element的重用,在删除和排序中创建Widget需加入Key
而GlobalKey可以拿到指定的Widget、Element、State。
