概述
在Widget的構(gòu)造方法中隅忿,有Key這么一個(gè)可選參數(shù)迫靖,Key是一個(gè)抽象類懊蒸,有LocalKey和GlobalKey兩種拇泛,本文將對(duì)這兩種key的作用進(jìn)行探究滨巴。
LocalKey
在探究LocalKey的作用之前,先通過一段代碼來看看一個(gè)場(chǎng)景
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"),
];
@override
Widget 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);
@override
Widget build(BuildContext context) {
return Container(
color: _randomColor,
width: 100,
height: 100,
child: Center(
child: Text(widget.title),
),
);
}
}
這段代碼中創(chuàng)建了3個(gè)顏色不同俺叭,文本不同的子widget恭取,按照從左往右排列,要注意的是熄守,顏色對(duì)象保存在Sate中蜈垮,而文本對(duì)象保存在Widget中耗跛,然后點(diǎn)擊按鈕,每次刪除最左邊的widget攒发。
執(zhí)行結(jié)果出乎意料:
雖然每次都能刪除文本正確的widget调塌,但是顏色值確實(shí)下一個(gè)widget的顏色,這種奇怪現(xiàn)象的原因其實(shí)和Flutter的增量渲染機(jī)制有關(guān)惠猿,我們知道widget樹是對(duì)應(yīng)著element樹的羔砾,而widget樹是不穩(wěn)定的,widget在每次的刷新中都有可能在創(chuàng)建或者銷毀偶妖,而element不會(huì)姜凄,他會(huì)去對(duì)比樹中新的widget和舊widget是否一致,是否可以更新widget趾访,如果可以更新檀葛,那么element將會(huì)指向新的widget。如下圖:
一開始腹缩,widget樹的節(jié)點(diǎn)與element樹的節(jié)點(diǎn)是一一對(duì)應(yīng)的屿聋,element的_widget屬性指向了對(duì)應(yīng)的widget,當(dāng)widget樹發(fā)生改變
element樹并不會(huì)全部重新創(chuàng)建藏鹊,而是與從左到右比較润讥,看是否新的節(jié)點(diǎn)的widget與原先的節(jié)點(diǎn)的widget是否一致,而判斷的依據(jù)我們可以在源碼中看到
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
他比較的新舊widget的runtimeType和key值盘寡,明顯widgetA和widgetB的runtimeType和key均一致楚殿,所以element會(huì)變成這樣
element會(huì)復(fù)用,而widget則換成新的widget竿痰,當(dāng)state并沒有改變脆粥,而在案例中顏色是存放在sate中的,所以影涉,首個(gè)element的文本變成了widgetB的文本变隔,但是顏色還是sateA的的顏色,而widgetC的顏色變成了stateB的顏色蟹倾,而由于原先3個(gè)節(jié)點(diǎn)變成兩個(gè)匣缘,所以最后一個(gè)element被移除,最終變成我們看到的結(jié)果鲜棠。
要解決這個(gè)問題肌厨,我們只需要在Flutter判斷是否更新widget的時(shí)候給一個(gè)false的結(jié)果,就能解決這個(gè)問題豁陆,兩個(gè)對(duì)比條件中柑爸,runtimeType肯定是一致的,所以盒音,可以給widget添加一個(gè)唯一標(biāo)示key
我們只需要對(duì)代碼做下面的修改
...
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();
}
...
這樣表鳍,結(jié)果就是正常的了何址。
從上面的案例可以看出,LocalKey可以給Widget作為唯一表示进胯,在element樹更新能準(zhǔn)確的更新對(duì)應(yīng)正確的widget。
LocalKey除了ValueKey原押,還有ObjectKey和UniqueKey兩種類型胁镐,其實(shí)效果都一樣,只是有一些差別
- ValueKey: 以一個(gè)數(shù)據(jù)作為Key诸衔,如:數(shù)字盯漂、字符
- ObjectKey: 以O(shè)bject對(duì)象作為Key
- UniqueKey: 可以保證Key的唯一性,一旦使用UniqueKey那么就不存在Element復(fù)用了
GlobalKey
GlobalKey可以獲取到對(duì)應(yīng)的Sate,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;
}
}
而利用這個(gè)特性就缆,我們可以實(shí)現(xiàn)局部刷新從而進(jìn)行優(yōu)化,比如如果只是根widget的按鈕被點(diǎn)擊谒亦,而需要改變的僅僅是子widget竭宰,我們并不需要刷新整個(gè)widget樹,可以通過GlobalKey拿到對(duì)應(yīng)的sate份招,僅僅刷新子widget的狀態(tài)切揭,從而優(yōu)化性能。
class GlobalKeyDemo extends StatelessWidget {
final GlobalKey<_ChildPageState> _globalKey = GlobalKey();
@override
Widget 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;
@override
Widget build(BuildContext context) {
return Container(
child: Center(
child: Text(count.toString(), style: TextStyle(fontSize: 30, fontWeight: FontWeight.w800, color: Colors.blue),),
),
);
}
}
總結(jié)
以上锁摔,便是我對(duì)key的探究廓旬,Key是一個(gè)抽象類,有LocalKey和GlobalKey兩個(gè)子類谐腰,LocalKey可以作為Widget的唯一標(biāo)示孕豹,避免Element的重用,而GlobalKey可以拿到指定的Widget十气、Element励背、State。