背景
我們在進行Flutter開發程序中,幾乎每一個Widget都會有一個可選引數——Key,但是我們卻很少去傳這個值,既然我們可以不用傳,那么這個Key作用到底是什么呢?
問題
下面我們先來看看這樣一個場景:
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: BodyWidget(),
);
}
}
class BodyWidget extends StatefulWidget {
const BodyWidget({Key key}) : super(key: key);
@override
_BodyWidgetState createState() => _BodyWidgetState();
}
class _BodyWidgetState extends State<BodyWidget> {
List<Widget> list = [
//上面兩個顯示StateLessColorBoxContainer
StateLessColorBoxContainer(),
StateLessColorBoxContainer(),
//分割線
Divider(
height: 60.0,
color: Colors.black,
),
//下面兩個顯示StatefulColorBoxContainer
StatefulColorBoxContainer(),
StatefulColorBoxContainer(),
];
void switchWidget() {
setState(() {
list.insert(0, list.removeAt(1));
list.insert(3, list.removeAt(4));
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: new Text("KeyDemo"),
),
body: Container(
child: Center(
child: Column(
children: list,
),
),
),
floatingActionButton: ElevatedButton(
onPressed: () {
switchWidget();
},
child: Text("點擊交換")),
);
}
}
class StateLessColorBoxContainer extends StatelessWidget {
StateLessColorBoxContainer({Key key}) : super(key: key);
final int random = Random().nextInt(4);
final List<Color> colors = [
Colors.red,
Colors.yellow,
Colors.green,
Colors.grey
];
@override
Widget build(BuildContext context) {
return Container(
width: 100,
height: 100,
color: colors[random],
);
}
}
//定義StatefulColorBoxContainerContainer
class StatefulColorBoxContainer extends StatefulWidget {
StatefulColorBoxContainer({Key key}) : super(key: key);
@override
_StatefulColorBoxContainerState createState() =>
_StatefulColorBoxContainerState();
}
class _StatefulColorBoxContainerState extends State<StatefulColorBoxContainer> {
int random = Random().nextInt(3);
var colors = [Colors.red, Colors.yellow, Colors.green, Colors.grey];
@override
Widget build(BuildContext context) {
return Container(
width: 100,
height: 100,
color: colors[random],
);
}
}
原始碼比較好理解,上面兩個顯示兩個StateLessColorBoxContainer,下面兩個顯示StatefulColorBoxContainer,兩ColorBuildContainer類的build(context)方法完全一樣,但是我們卻發現點擊下面的交換按鈕,只有上面的每次進行了交換,而下面的不論點擊多少次,都沒有進行交換,
這是為什么呢,為什么使用StatefulWidget就不能成功交換更新呢?這就需要從Widget的更新機制說起來了,
Widget更新機制
在 Flutter 框架中,視圖維持在樹的結構中,我們撰寫的 Widget 一個嵌套一個,最終組合為一個 Tree,
StatelessWidget更新機制
在第一種使用 StatelessWidget 的實作中,當 Flutter 渲染這些 Widgets 時,Row Widget 為它的子 Widget 提供了一組有序的插槽,對于每一個 Widget,Flutter 都會構建一個對應的 Element,構建的這個 Element Tree 相當簡單,僅保存有關每個 Widget 型別的資訊以及對子Widget 的參考,你可以將這個 Element Tree 當做就像你的 Flutter App 的骨架,它展示了 App 的結構,但其他資訊需要通過參考原始Widget來查找,
當我們交換行中的兩個色塊時,Flutter 遍歷 Widget 樹,看看骨架結構是否相同,它從 Row Widget 開始,然后移動到它的子 Widget,Element 樹檢查 Widget 是否與舊 Widget 是相同型別和 Key, 如果都相同的話,它會更新對新 widget 的參考,在我們這里,Widget 沒有設定 Key,所以Flutter只是檢查型別,它對第二個孩子做同樣的事情,所以 Element 樹將根據 Widget 樹進行對應的更新,
當 Element Tree 更新完成后,Flutter 將根據 Element Tree 構建一個 Render Object Tree,最終開始渲染流程,
StatefulWidget更新機制
當使用 StatefulWidget 實作時,控制元件樹的結構也是類似的,只是現在 color 資訊沒有存盤控制元件自身了,而是在外部的 State 物件中,
現在,我們點擊按鈕,交換控制元件的次序,Flutter 將遍歷 Element 樹,檢查 Widget 樹中 Row 控制元件并且更新 Element 樹中的參考,然后第一個 StatefulColorBoxContainer 控制元件檢查它對應的控制元件是否是相同型別,它發現對方是相同的型別; 然后第二個 StatefulColorBoxContainer 控制元件做相同的事情,最終就導致 Flutter 認為這兩個控制元件都沒有發生改變,Flutter 使用 Element 樹和它對應的控制元件的 State 去確定要在設備上顯示的內容, 所以 Element 樹沒有改變,顯示的內容也就不會改變,
那么如何解決這個StatefulWidget不更新的問題呢?這就需要用到Key了
StatefullWidget 結合 Key
StatefullWidget中有個方法canUpdate,我們先看下原始碼:
@immutable
abstract class Widget extends DiagnosticableTree {
const Widget({ this.key });
final Key key;
···
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
}
通過原始碼不難判斷出上面的例子中,所有的canUpdate回傳的都是true,也就是意味著他不回去重新創建Element,而是通過Widget配置資訊去更新Element,但是呢上文的StatefulColorBoxContainer卻沒有保存color資訊,所以導致Element也不會去更新,如果我們想要在runtimeType相同的情況下去正確的執行更新操作就只能使用到key了,每個widget如果key不同,也就是canUpdate回傳為false,那么它對于的Element也就會重建,也就能夠正確的執行交換了,
上面的例子中我們通過在StatefulColorBoxContainer加入key引數,就能正確的交換了,
List<Widget> list = [
StateLessColorBoxContainer(),
StateLessColorBoxContainer(),
//分割線
Divider(
height: 60.0,
color: Colors.black,
),
// 加入不同的key
StatefulColorBoxContainer(key: UniqueKey()),
StatefulColorBoxContainer(key: UniqueKey()),
];
Key的種類
Key 的目的在于為每個 Widget 指明一個唯一的身份,使用何種 Key 就要依具體的使用場景決定,
ValueKey
例如在一個 ToDo 串列應用中,每個 Todo Item 的文本是恒定且唯一的,這種情況,適合使用 ValueKey,value 是文本,
ObjectKey
假設,每個子 Widget 都存盤了一個更復雜的資料組合,比如一個用戶資訊的地址簿應用,任何單個欄位(如名字或生日)可能與另一個條目相同,但每個資料組合是唯一的,在這種情況下, ObjectKey 最合適,
UniqueKey
如果集合中有多個具有相同值的 Widget,或者如果您想確保每個 Widget 與其他 Widget 不同,則可以使用 UniqueKey, 在我們的例子中就使用了 UniqueKey,因為我們沒有將任何其他常量資料存盤在我們的色塊上,并且在構建 Widget 之前我們不知道顏色是什么,
不要在 Key 中使用亂數,如果你那樣設定,那么當每次構建 Widget 時,都會生成一個新的亂數,Element 樹將不會和 Widget 樹做一致的更新,
GlobalKeys
Global Keys有兩種用途,
它們允許 Widget 在應用中的任何位置更改父級而不會丟失 State ,或者可以使用它們在 Widget 樹 的完全不同的部分中訪問有關另一個 Widget 的資訊,
比如: 要在兩個不同的螢屏上顯示相同的 Widget,同時保持相同的 State,則需要使用 GlobalKeys,
在第二種情況下,您可能希望驗證密碼,但不希望與樹中的其他 Widget 共享該狀態資訊,可以使用 GlobalKey 持有一個表單 Form 的 State,
總結
如何合理適當的使用 Key:
- When: 當您想要保留 Widget 樹的狀態時,請使用 Key,例如: 當修改相同型別的 Widget 集合(如串列中)時
- Where: 將 Key 設定在要指明唯一身份的 Widget 樹的頂部
- Which: 根據在該 Widget 中存盤的資料型別選擇使用的不同型別的Key
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/355363.html
標籤:其他
