🔥 本文由 程序喵正在路上 原创,CSDN首发!
💖 系列专栏:Flutter学习
🌠 首发时间:2024年5月28日
🦋 欢迎关注🖱点赞👍收藏🌟留言🐾
目录
- Key详解
- LocalKey和GlobalKey
- GlobalKey获取子组件
- Widget Tree、Element Tree和RenderObject Tree
- AnimatedList组件
- AnimatedList介绍
- AnimatedList实现动态列表
Key详解
我们平时一定接触过很多的 Widget
,比如 Container
、Row
、Column
等,它们在我们绘制界面的过程中发挥着重要的作用。但是不知道你有没有注意到,在几乎每个 Widget
的构造函数中,都有一个共同的参数,它们通常在参数列表的第一个,那就是 Key
。
在 Flutter 中,Key
是不能重复使用的,所以 Key
一般用来做唯一标识。组件在更新的时候,其状态的保存主要是通过判断组件的类型或者 key
值是否一致。因此,当各组件的类型不同的时候,类型已经足够用来区分不同的组件了,此时我们可以不必使用 key
。但是如果同时存在多个同一类型的控件的时候,此时类型已经无法作为区分的条件了,我们就需要使用到 key
。
LocalKey和GlobalKey
Flutter key子类包含 LocalKey 和 GlobalKey
- 局部键(LocalKey):ValueKey、ObjectKey、UniqueKey
- ValueKey:把一个值作为key
- UniqueKey:程序会生成唯一的Key,当我们不知道如何指定 ValueKey 的时候就可以使用 UniqueKey
- ObjectKey:把一个对象实例作为key
- 全局键(GlobalKey): GlobalKey、GlobalObjectKey
- GlobalObjectKey:全局 Objec key,和 ObjectKey 有点类似
LocalKey的使用
下面程序的功能是,界面中有 3 个颜色不同的按钮,点击按钮,其上面的数字会增加,点击右下角的浮动按钮,按钮的顺序会被打乱。如果没有给 3 个按钮赋予不同的 key 值,当被打乱后,改变的只有颜色,其上的数字并不会改变。
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
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});
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
List<Widget> list = [
const Box(
key: ValueKey('666'),
color: Colors.red,
),
Box(
key: UniqueKey(),
color: Colors.blue,
),
const Box(
key: ObjectKey(Box(color: Colors.blue)),
color: Colors.yellow,
),
];
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.refresh),
onPressed: () {
setState(() {
list.shuffle(); //shuffle:打乱list元素的顺序
});
},
),
appBar: AppBar(
title: const Text('LocalKey'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: list,
),
),
);
}
}
class Box extends StatefulWidget {
final Color color;
const Box({super.key, required this.color});
State<Box> createState() => _BoxState();
}
class _BoxState extends State<Box> {
int _count = 0;
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.all(10),
height: 100,
width: 100,
child: ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(widget.color)),
onPressed: () {
setState(() {
_count++;
});
},
child: Text(
"$_count",
style: Theme.of(context).textTheme.displayLarge,
),
),
);
}
}
初始:
随意点击:
打乱:
GlobalKey的使用
运行下面这个程序会发现,当我们将手机改为横屏后,按钮的状态恢复到初始状态,这是因为外层组件从 Column
变成了 Row
,组件的类型已经改变,状态自然无法再保存。
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
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});
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
List<Widget> list = [
const Box(
key: ValueKey('666'),
color: Colors.red,
),
Box(
key: UniqueKey(),
color: Colors.blue,
),
const Box(
key: ObjectKey(Box(color: Colors.blue)),
color: Colors.yellow,
),
];
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.refresh),
onPressed: () {
setState(() {
list.shuffle(); //shuffle:打乱list元素的顺序
});
},
),
appBar: AppBar(
title: const Text('GlobalKey'),
),
body: Center(
// //判断屏幕为竖屏还是横屏
child: MediaQuery.of(context).orientation == Orientation.portrait
? Column(
mainAxisAlignment: MainAxisAlignment.center,
children: list,
)
: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: list,
),
),
);
}
}
class Box extends StatefulWidget {
final Color color;
const Box({super.key, required this.color});
State<Box> createState() => _BoxState();
}
class _BoxState extends State<Box> {
int _count = 0;
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.all(10),
height: 100,
width: 100,
child: ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(widget.color)),
onPressed: () {
setState(() {
_count++;
});
},
child: Text(
"$_count",
style: Theme.of(context).textTheme.displayLarge,
),
),
);
}
}
下面使用 GlobalKey
来对其改进,我们定义了 3 个 GlobalKey,然后在 initState
里面对 Box
进行初始化。
竖屏:
横屏:
GlobalKey获取子组件
globalKey.currentState
可以获取子组件的状态,执行子组件的方法,globalKey.currentWidget
可以获i取子组件的属性,_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.
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const HomePage(),
);
}
}
//父Widget
class HomePage extends StatefulWidget {
const HomePage({super.key});
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final GlobalKey _globalKey = GlobalKey();
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
//1、获取currentState Widget的属性(记住)
var boxState = _globalKey.currentState as _BoxState;
print(boxState._count);
setState(() {
boxState._count++; //可以设置获取的属性
});
//调用currentState Widget的方法
boxState.run();
//2、获取子Widget (了解)
var boxWidget = _globalKey.currentWidget as Box;
print(boxWidget
.color); //值:MaterialColor(primary value: Color(0xfff44336))
// 3、获取子组件渲染的属性(了解)
var renderBox =
_globalKey.currentContext!.findRenderObject() as RenderBox;
print(renderBox.size); //值:Size(100.0, 100.0)
},
),
appBar: AppBar(
title: const Text('Global获取子组件'),
),
body: Center(
child: Box(key: _globalKey, color: Colors.red),
),
);
}
}
//子Widget
class Box extends StatefulWidget {
final Color color;
const Box({Key? key, required this.color}) : super(key: key);
State<Box> createState() => _BoxState();
}
class _BoxState extends State<Box> {
int _count = 0;
void run() {
print("我是box的run方法");
}
Widget build(BuildContext context) {
return SizedBox(
height: 100,
width: 100,
child: ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(widget.color)),
onPressed: () {
setState(() {
_count++;
});
},
child: Text(
"$_count",
style: Theme.of(context).textTheme.headlineLarge,
),
),
);
}
}
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
。
AnimatedList组件
AnimatedList介绍
AnimatedList
和 ListView
的功能大体相似,不同的是, AnimatedList
可以在列表中插入或删除节点时执行一个动画,在需要添加或删除列表项的场景中会提高用户体验。
AnimatedList
是一个 StatefulWidget
,它对应的 State
类型为 AnimatedListState
,添加和删除元素的方法位于 AnimatedListState
中:
void insertItem(int index, { Duration duration = _kDuration });
void removeItem(int index, AnimatedListRemovedItemBuilder builder, { Duration duration = _kDuration }) ;
AnimatedList
常见属性:
属性 | 描述 |
---|---|
key | globalKey final globalKey = GlobalKey(); |
initialItemCount | 子元素数量 |
itemBuilder | 方法 (BuildContext context, int index, Animation animation) {} |
关于GlobalKey
- 每个
Widget
都对应一个Element
,我们可以直接对Widget
进行操作,但是无法直接操作Widget
对应的Element
。而GlobalKey
就是那把直接访问Element
的钥匙。通过GlobalKey
可以获取到Widget
对应的Element
。
AnimatedList实现动态列表
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
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});
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final _globalKey = GlobalKey<AnimatedListState>();
bool flag = true;
List<String> list = ["第一条", "第二条"];
Widget _buildItem(index) {
return ListTile(
key: ValueKey(index),
title: Text(list[index]),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () {
//执行删除
_deleteItem(index);
},
),
);
}
_deleteItem(index) {
if (flag == true) {
flag = false;
//执行删除
_globalKey.currentState!.removeItem(index, (context, animation) {
//animation的值是从1到0
var removeItem = _buildItem(index);
list.removeAt(index); //数组中删除数据
return ScaleTransition(
// opacity: animation,
scale: animation,
child: removeItem, //删除的时候执行动画的元素
);
});
//解决快速删除的bug
Timer.periodic(const Duration(milliseconds: 500), (timer) {
flag = true;
timer.cancel();
});
}
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('AnimatedList动态列表'),
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
list.add("我是新增的数据");
_globalKey.currentState!.insertItem(list.length - 1);
},
),
body: AnimatedList(
key: _globalKey,
initialItemCount: list.length,
itemBuilder: ((context, index, animation) {
//animation的值是从0到1
return FadeTransition(
opacity: animation,
child: _buildItem(index),
);
})),
);
}
}