前提
阅读之前你需要知道Flutter Widget刷新机制,可以见我的另一篇文章:
Flutter的Widget刷新时机以及优化
作用
在Flutter中,Key是一个用于标识widget的对象,它的作用有以下几个:
- 区分不同的widget
Key可以帮助Flutter区分不同的widget。当我们在widget树中添加、移除或更新一个widget时,Flutter需要确定哪些widget需要被修改或重新构建。如果两个widget具有相同的Key,则Flutter会将它们视为同一个widget,并且不会重复构建。 - 提高性能
通过使用Key,Flutter可以更加高效地处理widget树的构建和更新。例如,在ListView中,如果我们没有为每个item指定Key,Flutter将无法区分不同的item,并且每次构建和更新都会重新创建所有的item,这会降低性能。 - 保留状态
当我们使用StatefulWidget时,Key还可以帮助我们保留widget的状态。当我们在widget树中添加、移除或更新一个StatefulWidget时,Flutter需要确定哪些State对象需要被保留,以便维护widget的状态。如果我们没有为StatefulWidget指定Key,则Flutter将无法确定哪些State对象需要被保留,从而可能导致widget状态的丢失。
下面举用法例子
当我们使用StatefulWidget时,如果没有为widget指定Key,则当widget树中的某个节点发生变化时,Flutter会重新创建所有的State对象,这可能导致widget的状态丢失。
以下是一个简单的示例,演示了如何使用Key来保留StatefulWidget的状态。假设我们有一个数字计数器,每次点击按钮时,计数器的值都会加1。我们可以使用StatefulWidget来实现这个计数器,如下所示:
class CounterWidget extends StatefulWidget {
@override
_CounterWidgetState createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int _count = 0;
void _increment() {
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Count: $_count'),
ElevatedButton(
onPressed: _increment,
child: Text('Increment'),
),
],
);
}
}
在上述代码中,我们定义了一个CounterWidget类,它继承自StatefulWidget,用于显示一个数字计数器。CounterWidget的状态由一个_CountWidgetState类来管理,它包含一个整数_count,用于保存计数器的值。每次点击按钮时,_count都会加1,这个操作是在_increment函数中执行的。
如果我们在widget树中多次使用CounterWidget,那么每个CounterWidget都将拥有自己的_CountWidgetState对象,并且每次点击按钮时,所有的_CountWidgetState对象都会被重新创建。为了避免这个问题,我们可以为每个CounterWidget指定一个Key,如下所示:
class CounterWidget extends StatefulWidget {
CounterWidget({Key key}) : super(key: key);
@override
_CounterWidgetState createState() => _CounterWidgetState();
}
然后,在widget树中使用CounterWidget时,我们可以为每个CounterWidget指定一个不同的Key,例如:
CounterWidget(key: ValueKey(1)),
CounterWidget(key: ValueKey(2)),
CounterWidget(key: ValueKey(3)),
通过为每个CounterWidget指定一个不同的Key,Flutter将会正确地保留每个CounterWidget的状态,并且在点击按钮时只会重新构建对应的_CountWidgetState对象。这可以提高应用程序的性能,并确保计数器的状态正确显示。
如果我们没有为每个CounterWidget指定一个不同的Key,那么每次点击按钮时,所有的_CountWidgetState对象都会被重新创建,导致计数器的状态丢失。下面是一个没有使用Key的示例代码:
class CounterWidget extends StatefulWidget {
@override
_CounterWidgetState createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int _count = 0;
void _increment() {
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Count: $_count'),
ElevatedButton(
onPressed: _increment,
child: Text('Increment'),
),
],
);
}
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CounterWidget(),
CounterWidget(),
CounterWidget(),
],
),
),
),
);
}
}
在上述代码中,我们定义了一个CounterWidget类,用于显示一个数字计数器。在MyApp类中,我们使用CounterWidget三次,将三个计数器显示在屏幕中。
如果我们在应用程序中运行上述代码,并点击任意一个计数器的按钮,所有的计数器都会被重新构建,导致计数器的状态丢失。这是因为所有的CounterWidget都没有指定Key,因此Flutter无法区分它们之间的差异,从而会将它们视为同一个widget,并且每次点击按钮时都会重新构建所有的CounterWidget。
总之,通过为每个widget指定Key,我们可以帮助Flutter正确地区分不同的widget,从而保留widget的状态,并提高应用程序的性能和响应性能。在使用StatefulWidget时,特别需要注意使用Key来保留widget的状态,避免状态丢失的问题。
Key有哪几种
ValueKey
ValueKey是最常见的Key类型之一,它基于一个给定的值创建一个Key对象。当我们使用ValueKey时,如果两个widget具有相同的值,则它们将被视为同一个widget,从而可以避免widget的重复构建。
例如,我们可以使用ValueKey来为一个ListView的item指定Key,如下所示:
ListView.builder(
itemCount: 10,
itemBuilder: (BuildContext context, int index) {
return ListTile(
key: ValueKey(index),
title: Text('Item $index'),
);
},
)
在上述代码中,我们为ListView的每个item都指定了一个ValueKey,基于它们的索引值来创建Key对象。这样,当ListView中的item发生变化时,Flutter将能够正确地区分不同的item,并且避免不必要的重复构建。
ObjectKey
ObjectKey是另一种常见的Key类型,它基于一个给定的对象创建一个Key对象。当我们使用ObjectKey时,如果两个widget具有相同的对象,则它们将被视为同一个widget,从而可以避免widget的重复构建。
例如,我们可以使用ObjectKey来为一个含有一组数据的widget指定Key,如下所示:
MyData data = ...;
return MyWidget(
key: ObjectKey(data),
data: data,
);
在上述代码中,我们为MyWidget指定了一个ObjectKey,基于它所包含的数据对象来创建Key对象。这样,当MyWidget中的数据对象发生变化时,Flutter将能够正确地区分不同的MyWidget,并且避免不必要的重复构建。
UniqueKey
UniqueKey是一种特殊的Key类型,它基于一个随机数创建一个唯一的Key对象。当我们使用UniqueKey时,每个widget都将具有一个唯一的Key,从而可以避免重复构建和状态丢失等问题。
例如,我们可以使用UniqueKey来为一个StatefulWidget指定Key,如下所示:
return MyStatefulWidget(
key: UniqueKey(),
);
在上述代码中,我们为MyStatefulWidget指定了一个UniqueKey,以确保每次创建时都具有一个唯一的Key。这可以帮助我们避免状态丢失等问题,并提高应用程序的性能和响应性能。
GlobalKey
与其他Key类型不同,GlobalKey可以跨widget树访问widget,它可以用于在widget树之外引用widget的状态或方法。
具体来说,我们可以使用GlobalKey来获取widget的状态或方法,然后在widget树之外调用它们。这在某些情况下非常有用,例如当我们需要从widget树之外修改widget的状态或执行一些操作时。
以下是一个使用GlobalKey的示例代码,它演示了如何在widget树之外引用widget的状态并进行修改:
class MyWidget extends StatefulWidget {
const MyWidget({Key key}) : super(key: key);
@override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
int _count = 0;
void increment() {
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
return Text('Count: $_count');
}
}
class MyApp extends StatelessWidget {
final GlobalKey<_MyWidgetState> _widgetKey = GlobalKey<_MyWidgetState>();
void incrementCount() {
_widgetKey.currentState?.increment();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
MyWidget(key: _widgetKey),
ElevatedButton(
onPressed: incrementCount,
child: Text('Increment'),
),
],
),
),
),
);
}
}
在上述代码中,我们定义了一个MyWidget类,用于显示一个计数器。计数器的值保存在一个私有变量_count中,我们定义了一个increment方法来增加计数器的值,并使用setState来通知Flutter更新widget的状态。
在MyApp类中,我们创建了一个GlobalKey<_MyWidgetState>对象,用于引用MyWidget的状态。我们定义了一个incrementCount方法,在点击按钮时调用这个方法,从而调用MyWidget的increment方法,并更新计数器的值。
通过使用GlobalKey,我们可以从widget树之外修改MyWidget的状态,并确保计数器的值正确更新。这在一些复杂的应用程序中非常有用,例如当我们需要在不同的widget之间共享数据或状态时。