主要区分
| 维度 | ValueKey | UniqueKey | PageStorageKey | GlobalKey |
|---|---|---|---|---|
| 类型 | LocalKey | LocalKey | LocalKey(继承自 ValueKey) | GlobalKey |
| 相等性判定 | 值相等 (==) |
永远不相等(每次新建都是全新随机身份) | 值相等 (==) |
全局唯一性(同一个 GlobalKey 实例在整个 App 中只能出现一次) |
| 作用域 | 同一父组件的兄弟节点之间 | 同一父组件的兄弟节点之间 | 同一父组件的兄弟节点之间 | 整个 Widget 树全局 |
| 主要目的 | 按数据身份保持 Widget 状态,防止增删排序时状态错位 | 强制丢弃旧状态,完全重建 Widget 及子树 | 自动保存和恢复滚动组件的滚动位置(如列表、PageView) | ① 跨任意层级保持状态移动 ② 直接访问子组件的 State / RenderObject |
| 是否保持 Widget 复用 | ✅ 是 | ❌ 否(强制创建新 Element) | ✅ 是(继承自 ValueKey,也具有复用能力) | ✅ 是(全局匹配 Element) |
| 是否自动保存滚动位置 | ❌ 否 | ❌ 否 | ✅ 是(内建与 PageStorage 协作,保存滚动偏移) | ❌ 否(需手动实现) |
| 能否访问子 State | ❌ 否 | ❌ 否 | ❌ 否 | ✅ 是(通过 currentState) |
| 性能影响 | 极小,局部比对 | 较大,破坏复用,造成额外重建 | 极小,等同于 ValueKey(额外的滚动位置存取开销可忽略) | 较大,全局维护映射表,滥用会导致性能下降和状态混乱 |
| 典型用法场景 | 动态列表的增删重排:key: ValueKey(item.id) |
强制重置表单、动画或重新创建组件:key: UniqueKey() |
保持 Tab 切换后的列表滚动位置:key: PageStorageKey('myList') |
表单验证、动画控制、获取子组件尺寸、跨树移动大组件 |
GlobalKey(访问子组件 State(表单验证为例))
通常作为全局作用域下widget对应的key,可以获取对应的currentState,如key.currentState 直接调用其方法(如 validate())、获取属性或触发刷新。
less
import 'package:flutter/material.dart';
class GlobalKeyDemo extends StatelessWidget {
final _formKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('GlobalKey 示例')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
children: [
TextFormField(
decoration: const InputDecoration(labelText: '用户名'),
validator: (value) =>
value?.isEmpty ?? true ? '请输入用户名' : null,
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('验证通过')),
);
}
},
child: const Text('提交'),
),
],
),
),
),
);
}
}
ValueKey(保持列表状态并复用diff)
给每个列表项绑定一个与数据唯一标识相关的值(如 id),当列表顺序或数量变化时,框架通过 Key 找到对应的旧 Element 并复用,从而保留滚动位置、输入框内容、动画状态等。
scala
import 'package:flutter/material.dart';
class Item {
final String id;
final String title;
Item(this.id, this.title);
}
class ValueKeyDemo extends StatefulWidget {
@override
State<ValueKeyDemo> createState() => _ValueKeyDemoState();
}
class _ValueKeyDemoState extends State<ValueKeyDemo> {
List<Item> items = [
Item('1', 'Apple'),
Item('2', 'Banana'),
Item('3', 'Cherry'),
];
void _removeItem(int index) {
setState(() {
items.removeAt(index);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('ValueKey 示例')),
body: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
return ListTile(
key: ValueKey(item.id), // 关键:用 id 作为 ValueKey
title: Text(item.title),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () => _removeItem(index),
),
);
},
),
);
}
}
UniqueKey( 强制完全重建组件)
当需要主动丢弃一个 Widget 的所有内部状态(如动画、表单输入、滚动位置)并全新创建时,可以给该 Widget 一个 UniqueKey()。每次 build 时生成的新 UniqueKey 与旧的不等,框架将销毁旧 Element 并创建新的。
less
import 'package:flutter/material.dart';
class UniqueKeyDemo extends StatefulWidget {
@override
State<UniqueKeyDemo> createState() => _UniqueKeyDemoState();
}
class _UniqueKeyDemoState extends State<UniqueKeyDemo> {
// 通过改变这个 Key 来触发完全重建
Key _resetKey = UniqueKey();
void _forceRebuild() {
setState(() {
_resetKey = UniqueKey(); // 创建一个全新的 Key
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('UniqueKey 示例')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
// 目标组件:使用当前的 _resetKey
Form(
key: _resetKey,
child: TextFormField(
decoration: const InputDecoration(labelText: '输入内容'),
),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: _forceRebuild,
child: const Text('重置表单状态'),
),
],
),
),
);
}
}
PageStorageKey(保留滚动位置)
less
TabBarView(
children: [
ListView.builder(
key: const PageStorageKey('fruits'), // 切换 Tab 后自动恢复滚动位置
itemBuilder: (_, i) => ListTile(title: Text('水果 $i')),
),
ListView.builder(
key: const PageStorageKey('veggies'),
itemBuilder: (_, i) => ListTile(title: Text('蔬菜 $i')),
),
],
)