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
。