1、Key 作用
在 Flutter 中,Key 是不能重復(fù)使用的,所以 Key 一般用來做唯一標(biāo)識(shí)。組件在更新的時(shí)候,其狀態(tài)的保存主要是通過判斷組件的類型或者 key 值是否一致。因此,當(dāng)各組件的類型不同的時(shí)候,類型已經(jīng)足夠用來區(qū)分不同的組件了,此時(shí)我們可以不必使用 key。但是如果同時(shí)存在多個(gè)同一類型的控件的時(shí)候,此時(shí)類型已經(jīng)無法作為區(qū)分的條件了,我們就需要使用到 key。
2、示例
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
List<Widget> list = [
Box(
color: Colors.blue,
),
Box(
color: Colors.red,
),
Box(
color: Colors.orange,
)
];
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
list.shuffle(); //打亂list的順序
});
},
child: const Icon(Icons.refresh),
),
appBar: AppBar(
title: const Text('Title'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: list,
),
),
);
}
}
// ignore: must_be_immutable
class Box extends StatefulWidget {
Color color;
Box({super.key, required this.color});
@override
State<Box> createState() => _BoxState();
}
class _BoxState extends State<Box> {
int _count = 0;
@override
Widget build(BuildContext context) {
return SizedBox(
height: 100,
width: 100,
child: ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(widget.color)),
onPressed: () {
setState(() {
_count++;
});
},
child: Center(
child: Text("$_count"),
),
),
);
}
}
運(yùn)行后發(fā)現(xiàn)改變 list Widget 順序后,Widget 顏色會(huì)變化,但是每個(gè) Widget 里面的文本內(nèi)容并沒有變化?
當(dāng) List 重新排序后 Flutter 檢測(cè)到了 Widget 的順序變化,所以重新繪制 ListWidget,但是 Flutter 發(fā)現(xiàn) List Widget 里面的元素沒有變化,所以就沒有改變 Widget 里面的內(nèi)容。
把 List 里面的 Box 的顏色改成一樣,這個(gè)時(shí)候重新對(duì) list 進(jìn)行排序,就很容易理解了。重新排序后雖然執(zhí)行了setState,但是代碼和以前是一樣的,所以 Flutter 不會(huì)重構(gòu) List Widget 里面的內(nèi)容, 也就是 Flutter 沒法通過 Box 里面?zhèn)魅氲膮?shù)來識(shí)別 Box 是否改變。如果要讓 FLutter 能識(shí)別到 List Widget 子元素的改變,就需要給每個(gè) Box 指定一個(gè) key。
3、LocalKey、GlobalKey
1、 Flutter key 子類包含 LocalKey 和 GlobalKey 。
局部鍵(LocalKey):ValueKey、ObjectKey、UniqueKey。
全局鍵(GlobalKey): GlobalKey、GlobalObjectKey。
ValueKey (值key):把一個(gè)值作為 key 。
UniqueKey(唯一key):程序生成唯一的 Key,當(dāng)我們不知道如何指定 ValueKey 的時(shí)候就可以使用 UniqueKey。
ObjectKey(對(duì)象 key):把一個(gè)對(duì)象實(shí)例作為 key。
GlobalKey:每個(gè) Widget 都對(duì)應(yīng)一個(gè) Element ,我們可以直接對(duì) Widget 進(jìn)行操作,但是無法直接操作 Widget 對(duì)應(yīng)的 Element 。而 GlobalKey 就是那把直接訪問 Element 的鑰匙。通過 GlobalKey可以獲取到 Widget 對(duì)應(yīng)的 Element 。
2、key 的使用
LocalKey 使用
//替換上述示例中 list 數(shù)據(jù)
// List<Widget> list = [
// Box(
// color: Colors.blue,
// ),
// Box(
// color: Colors.red,
// ),
// Box(
// color: Colors.orange,
// )
// ];
List<Widget> list = [
Box(
key: const ValueKey(1),
color: Colors.blue,
),
Box(
key: ObjectKey(Box(color: Colors.red)),
color: Colors.red,
),
Box(
key: UniqueKey(), //程序自動(dòng)生成一個(gè)key
color: Colors.orange,
)
];
GlobalKey 的使用
如果把 LocalKey 比作局部變量, GlobalKey 就類似于全局變量。在使用了 LocalKey 之后,在上述示例中,當(dāng)屏幕狀態(tài)改變的時(shí)候把 Colum 換成了 Row,Box 的狀態(tài)就會(huì)丟失。
//示例中要使用添加過 LocalKey 之后的 list
//更改 Scaffold 中 body 里面的內(nèi)容
// body: Center(
// child: Column(
// mainAxisAlignment: MainAxisAlignment.center,
// children: list,
// ),
// ),
body: Center(
child: MediaQuery.of(context).orientation == Orientation.portrait
? Column(
mainAxisAlignment: MainAxisAlignment.center,
children: list,
)
: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: list,
),
),
分析:由于一個(gè) Widget 狀態(tài)的保存主要是通過判斷組件的類型或者 key 值是否一致。LocalKey 只在當(dāng)前的組件樹有效,所以把 Colum 換成了 Row 的時(shí)候 Widget 的狀態(tài)就丟失了。為了解決這個(gè)問題我們就可以使用 GlobalKey。
//在以上代碼的基礎(chǔ)上更改 list 數(shù)據(jù)
///1.不適用 key
// List<Widget> list = [
// Box(
// color: Colors.blue,
// ),
// Box(
// color: Colors.red,
// ),
// Box(
// color: Colors.orange,
// )
// ];
///2.使用 LocalKey
// List<Widget> list = [
// Box(
// key: const ValueKey(1),
// color: Colors.blue,
// ),
// Box(
// key: ObjectKey(Box(color: Colors.red)),
// color: Colors.red,
// ),
// Box(
// key: UniqueKey(), //程序自動(dòng)生成一個(gè)key
// color: Colors.orange,
// )
// ];
///3.GlobalKey
List<Widget> list = [];
final GlobalKey _key1 = GlobalKey();
final GlobalKey _key2 = GlobalKey();
final GlobalKey _key3 = GlobalKey();
@override
void initState() {
super.initState();
list = [
Box(
key: _key1,
color: Colors.blue,
),
Box(
key: _key2,
color: Colors.red,
),
Box(
key: _key3,
color: Colors.orange,
)
];
}
3、 GlobalKey 獲取子組件
globalKey.currentState 可以獲取子組件的狀態(tài),執(zhí)行子組件的方法。
globalKey.currentWidget 可以獲取子組件的屬性。
_globalKey.currentContext!.findRenderObject() 可以獲取渲染的屬性。
class _HomePageState extends State<HomePage> {
final GlobalKey _globalKey = GlobalKey();
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
//1、獲取子組件的狀態(tài) 調(diào)用子組件的屬性
var state = (_globalKey.currentState as _BoxState);
setState(() {
state._count++;
});
//2、獲取子組件的屬性
var box = (_globalKey.currentWidget as Box);
print(box.color);
//3、獲取子組件渲染的屬性
var renderBox =
(_globalKey.currentContext!.findRenderObject() as RenderBox);
print(renderBox.size);
},
),
appBar: AppBar(
title: const Text('Title'),
),
body: Center(
child: Box(
key: _globalKey,
color: Colors.red,
),
),
);
}
}
4、 Widget Tree、Element Tree 、 RenderObject Tree
Flutter 應(yīng)用是由是 Widget Tree、Element Tree 和 RenderObject Tree 組成。
Widget:Widget 就是一個(gè)類, 是 Element 的配置信息。與 Element 的關(guān)系可以是一對(duì)多,一份配置可以創(chuàng)造多個(gè) Element 實(shí)例。
Element:Widget 的實(shí)例化,內(nèi)部持有 Widget 和 RenderObject。
RenderObject:負(fù)責(zé)渲染繪制。
默認(rèn)情況下面,當(dāng) Flutter 同一個(gè) Widget 的大小,順序變化的時(shí)候,F(xiàn)Lutter 不會(huì)改變 Widget 的 state。