Key详解
我们平时一定接触过很多的 Widget ,比如 Container 、Row 、Column 等,它们在我们绘制界面的过程
中发挥着重要的作用。但是不知道你有没有注意到,在几乎每个 Widget 的构造函数中,都有一个共同
的参数,它们通常在参数列表的第一个,那就是 Key 。
在 Flutter 中,Key 是不能重复使用的,所以 Key 一般用来做唯一标识。组件在更新的时候,其状态的保
存主要是通过判断组件的类型或者 key 值是否一致。因此,当各组件的类型不同的时候,类型已经足够
用来区分不同的组件了,此时我们可以不必使用 key 。但是如果同时存在多个同一类型的控件的时候,
此时类型已经无法作为区分的条件了,我们就需要使用到 key 。
没有 Key 会发生什么奇怪现象
如下面例: 定义了一个 StatefulWidget 的 Box ,点击 Box 的时候可以改变 Box 里面的数字,当我们重新对 Box 排序的时候 Flutter 就无法识别到 Box 的变化了, 这是什么原因呢?
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
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,
),
),
);
}
}
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)
), // ButtonStyle
onPressed: () {
setState(() {
_count++;
});
},
child: Center(
child: Text("$_count"),
), // Center
), // ElevatedButton
); // SizedBox
}
}
运行后我们发现改变 list Widget 顺序后,Widget 颜色会变化,但是每个 Widget 里面的文本内容并没有
变化,为什么会这样呢?当我们 List 重新排序后 Flutter 检测到了 Widget 的顺序变化,所以重新绘制 List
Widget ,但是 Flutter 发现 List Widget 里面的元素没有变化,所以就没有改变 Widget 里面的内容。
把 List 里面的 Box 的颜色改成一样,这个时候您重新对 list 进行排序,就很容易理解了。重新排序后虽然
执行了 setState ,但是代码和以前是一样的,所以 Flutter 不会重构 List Widget 里面的内容, 也就是
Flutter 没法通过 Box 里面传入的参数来识别 Box 是否改变。如果要让 FLutter 能识别到 List Widget 子元素
的改变,就需要给每个 Box 指定一个 key 。
List<Widget> list = [
Box(
color: Colors.blue,
),
Box(
color: Colors.blue,
),
Box(
color: Colors.blue,
)
];
LocalKey、GlobalKey
在 Flutter 中,Key 是不能重复使用的,所以 Key 一般用来做唯一标识。组件在更新的时候,其状态的保
存主要是通过判断组件的类型或者 key 值是否一致。因此,当各组件的类型不同的时候,类型已经足够
用来区分不同的组件了,此时我们可以不必使用 key 。但是如果同时存在多个同一类型的控件的时候,
此时类型已经无法作为区分的条件了,我们就需要使用到 key 。
- Flutter
key子类包含LocalKey和GlobalKey。- 局部键(LocalKey):
ValueKey、ObjectKey、UniqueKey - 全局键(GlobalKey):
GlobalKey、GlobalObjectKey
- 局部键(LocalKey):
ValueKey (值key)把一个值作为 key ,UniqueKey(唯一 key )程序生成唯一的 Key ,当我们不知道
如何指定 ValueKey 的时候就可以使用 UniqueKey ,ObjectKey(对象key)把一个对象实例作为key 。
GlobalKey(全局key),GlobalObjectKey(全局 GlobalObjectKey ,和 ObjectKey 有点类似)。
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<Widget> list = [
Box(
key: const ValueKey(1),
color: Colors.blue,
),
Box(
key: ObjectKey(Box(color: Colors.red)),
color: Colors.red,
),
Box(
key:UniqueKey(), //程序自动生成一个key
color: Colors.orange,
)
];
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
list.shuffle(); //打乱list的顺序
});
},
child: const Icon(Icons.refresh),
), // FloatingActionButton
appBar: AppBar(
title: const Text('Title'),
), // AppBar
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: list,
), // Column
), // Center
); // Scaffold
}
}
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)
), // ButtonStyle
onPressed: () {
setState(() {
_count++;
});
},
child: Center(
child: Text("$_count"),
), // Center
), // ElevatedButton
);
}
}
GlobalKey 的使用
如果把 LocalKey 比作局部变量, GlobalKey 就类似于全局变量下面使用了 LocalKey ,当屏幕状态改变的时候把 Colum 换成了 Row ,Box 的状态就会丢失。
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<Widget> list = [
Box(
key: const ValueKey(1),
color: Colors.blue,
),
Box(
key: ObjectKey(Box(color: Colors.red)),
color: Colors.red,
),
Box(
key:UniqueKey(), //程序自动生成一个key
color: Colors.orange,
)
];
@override
Widget build(BuildContext context) {
print(MediaQuery.of(context).orientation);
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
list.shuffle(); //打乱list的顺序
});
},
child: const Icon(Icons.refresh),
), // FloatingActionButton
appBar: AppBar(
title: const Text('Title'),
), // AppBar
body: Center(
child: MediaQuery.of(context).orientation==Orientation.portrait ?
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: list,
):Row(
mainAxisAlignment: MainAxisAlignment.center,
children: list,
),
), // Center
); // Scaffold
}
}
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)
), // ButtonStyle
onPressed: () {
setState(() {
_count++;
});
},
child: Center(
child: Text("$_count"),
), // Center
), // ElevatedButton
); // SizedBox
}
}
在前面我们介绍过一个 Widget 状态的保存主要是通过判断组件的类型或者 key 值是否一致。LocalKey 只在当前的组件树有效,所以把 Colum 换成了 Row 的时候 Widget 的状态就丢失了。为了解决这个问题我们就可以使用 GlobalKey 。
GlobalKey 优化,把 LocalKey 换成 GlobalKey ,如下:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
); // MaterialApp
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<Widget> list = [];
final GlobalKey _key1 = GlobalKey();
final GlobalKey _key2 = GlobalKey();
final GlobalKey _key3 = GlobalKey();
@override
void initState() {
// TODO: implement initState
super.initState();
list = [
Box(
key: _key1,
color: Colors.blue,
),
Box(
key: _key2,
color: Colors.red,
),
Box(
key: _key3, //程序自动生成一个key
color: Colors.orange,
)
];
}
@override
Widget build(BuildContext context) {
print(MediaQuery.of(context).orientation);
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
list.shuffle(); //打乱list的顺序
});
},
child: const Icon(Icons.refresh),
), // FloatingActionButton
appBar: AppBar(
title: const Text('Title'),
), // AppBar
body: Center(
child: MediaQuery.of(context).orientation == Orientation.portrait
? Column(
mainAxisAlignment: MainAxisAlignment.center,
children: list,
) // Column
: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: list,
), // Row
), // Center
); // Scaffold
}
}
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)
), // ButtonStyle
onPressed: () {
setState(() {
_count++;
});
},
child: Center(
child: Text("$_count"),
), // Center
), // ElevatedButton
);
}
}
GlobalKey 获取子组件
globalKey.currentState 可以获取子组件的状态,执行子组件的方法,globalKey.currentWidget 可以获取子组件的属性,_globalKey.currentContext!.findRenderObject() 可以获取渲染的属性。
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
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、获取子组件的状态 调用子组件的属性
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,
),
),
);
}
}
class Box extends StatefulWidget {
final Color color;
const Box({Key? key, required this.color}):super(key: key);
@override
State<Box> createState() => _BoxState();
}
class _BoxState extends State<Box> {
int _count = 0;
run(){
print("run");
}
@override
Widget build(BuildContext context) {
return SizedBox(
height: 100,
width: 100,
child: ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(widget.color)
), // ButtonStyle
onPressed: () {
setState(() {
_count++;
});
},
child: Center(
child: Text("$_count"),
), // Center
), // ElevatedButton
);
}
}
Widget Tree、Element Tree 和 RenderObject Tree
Flutter 应用是由是 Widget Tree 、Element Tree 和 RenderObject Tree 组成 Widget 可以理解成一个类,Element 可以理解成 Widget 的实例,Widget 与 Element 的关系可以是一对多,一份配置可以创造多个 Element 实例。
| 属性 | 类型 | 含义 |
|---|---|---|
Widget | Widget 就是一个类, 是 Element 的配置信息。与 Element 的关系可以是一对多 ,一份配置可以创造多个 Element 实例 | |
Element | Widget 的实例化,内部持有Widget和RenderObject。 | |
RenderObject | 负责渲染绘制 |
默认情况下面,当 Flutter 同一个 Widget 的大小,顺序变化的时候,FLutter 不会改变 Widget 的 state 。