在Flutter中,当widget树中的一个widget需要更新时,Flutter会根据以下三个因素来决定是否更新widget:
Widget的Type是否相同
当widget树中的一个widget需要更新时,Flutter会首先检查widget的Type是否相同。如果新的widget和旧的widget的Type不同,那么Flutter将会创建一个新的widget,并将其插入到widget树中。
例如,假设我们有一个Text widget,它的文本内容随着时间而变化:
Text(
_getTime(),
style: TextStyle(fontSize: 16.0),
),
String _getTime() {
var now = DateTime.now();
return '${now.hour}:${now.minute}:${now.second}';
}
在上述代码中,我们使用一个函数_getTime来获取当前时间,并将其作为Text widget的文本内容。每秒钟,我们会调用setState来通知Flutter更新widget的状态,从而更新文本内容。
在每次更新时,Flutter会检查新的Text widget和旧的Text widget的Type是否相同。由于它们的Type相同,Flutter将只更新它们的属性,而不会创建新的widget。这可以提高应用程序的性能,并确保widget的正确更新。
Widget的Key是否相同
当widget树中的一个widget需要更新时,Flutter会其次检查widget的Key是否相同。如果新的widget和旧的widget的Key相同,那么Flutter将会重用旧的widget,并仅更新它的属性。否则,Flutter将会创建一个新的widget,并将其插入到widget树中。
Row(
children: [
ElevatedButton(
onPressed: () {},
child: Text('Button 1'),
),
ElevatedButton(
onPressed: () {},
child: Text('Button 2'),
),
],
)
如果我们在应用程序中多次使用这个Row,但没有为它们指定Key,则当我们更新其中一个按钮的属性时,Flutter会重新构建整个Row,这会降低性能。
相比之下,当我们为widget指定带UniqueKey的Key时,Flutter将能够区分不同的widget,并且只更新发生变化的部分。这可以提高应用程序的性能,并确保widget的正确更新。
假设我们有一个表单,包含多个文本输入框。我们希望能够检测表单中任何一个输入框的值是否发生了变化,以便在用户试图离开表单时提示用户保存更改。我们可以为每个输入框都指定一个唯一的Key,这样当用户修改任何一个输入框时,Flutter将只更新它发生了变化的输入框,而不会更新其他输入框。
class MyForm extends StatefulWidget {
@override
_MyFormState createState() => _MyFormState();
}
class _MyFormState extends State {
final _formKey = GlobalKey();
final _nameKey = UniqueKey();
final _emailKey = UniqueKey();
final _phoneKey = UniqueKey();
String _name;
String _email;
String _phone;
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
TextFormField(
key: _nameKey,
initialValue: _name,
decoration: InputDecoration(labelText: 'Name'),
onChanged: (value) => setState(() => _name = value),
),
TextFormField(
key: _emailKey,
initialValue: _email,
decoration: InputDecoration(labelText: 'Email'),
onChanged: (value) => setState(() => _email = value),
),
TextFormField(
key: _phoneKey,
initialValue: _phone,
decoration: InputDecoration(labelText: 'Phone'),
onChanged: (value) => setState(() => _phone = value),
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState.validate()) {
// 不会每次点击,输入框里面的值都消失了
}
},
child: Text('Save'),
),
],
),
);
}
}
在上述代码中,我们为每个输入框都指定了一个唯一的Key,以确保它们能够正确地重用并仅更新它们发生变化的部分。我们还将每个输入框的值保存在相应的私有变量中,并使用setState来通知Flutter更新它们的值。
Widget的属性是否相同
当widget的Type和Key都相同时,Flutter将只更新widget的属性。以下是一个示例,演示了如何使用Key和Type来优化一个包含多个列表项的列表的性能。
假设我们有一个列表,其中包含多个列表项。我们希望能够检测列表中任何一个列表项的状态是否发生了变化,以便在需要时更新列表项的显示。我们可以为每个列表项都指定一个唯一的Key,并确保它们的Type不变,这样当列表项的状态发生变化时,Flutter将只更新发生了变化的列表项,而不会更新其他列表项。
class MyList extends StatefulWidget {
@override
_MyListState createState() => _MyListState();
}
class _MyListState extends State {
final _items = List.generate(100, (index) => 'Item $index');
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: _items.length,
itemBuilder: (context, index) {
final item = _items[index];
return MyListItem(
key: ValueKey(item),
title: item,
);
},
);
}
}
class MyListItem extends StatefulWidget {
final String title;
MyListItem({Key key, this.title}) : super(key: key);
@override
_MyListItemState createState() => _MyListItemState();
}
class _MyListItemState extends State {
bool _isChecked = false;
@override
Widget build(BuildContext context) {
return ListTile(
title: Text(widget.title),
leading: Checkbox(
value: _isChecked,
// 点击之后不会刷新整个列表
onChanged: (value) => setState(() => _isChecked = value),
),
);
}
}
在上述代码中,我们使用ListView.builder来构建列表,并为每个列表项都指定一个唯一的Key。当构建列表项时,我们使用MyListItem widget来包装它们,并将其title作为属性传递。在MyListItem widget中,我们使用Checkbox来表示列表项的状态,并将其值保存在私有变量_isChecked中。通过使用Key和Type,我们可以确保Flutter只更新发生了变化的列表项,并提高应用程序的性能和响应性能。