AnimatedList 是Flutter提供的一个可以在插入或移除Item时为Item设置动画的列表Widget。
淡入淡出效果使用 FadeTransition
实现,若不熟悉该控件的用法,建议先看博客
Flutter 平移动画 — 4种实现方式
AnimatedList 的主要的属性
以下代码为实现 AnimatedList的列表展示,并无动画效果
class _AnimatedListPageState extends State<AnimatedListPage> {
/// AnimatedList 的 key 可通过AnimatedListState来添加或删除Item
final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
final List<String> _items = ["Item 0", "Item 1"];
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(title: const Text('AnimatedList'), centerTitle: true),
body: AnimatedList(
key: _listKey,
initialItemCount: _items.length,
itemBuilder: (context, index, animation) {
return _buildItem(_items[index]);
},
),
);
}
/// 无动画效果的Item
Widget _buildItem(String item) {
return Container(
height: 44.0,
margin: const EdgeInsets.only(bottom: 1),
color: Colors.black,
alignment: Alignment.center,
child: Text(item, style: const TextStyle(color: Colors.white)),
);
}
}
在无动画的Item基础上套一层Widgt FadeTransition
,实现淡入淡出效果。
/// 淡入淡出的Widget
Widget _buildFadeWidget(
Widget child,
Animation<double> animation,
) {
return FadeTransition(
opacity: Tween<double>(
begin: 0,
end: 1,
).animate(animation),
child: child,
);
}
更改AnimatedList - itemBuilder 中返回的ItemWidget
AnimatedList(
key: _listKey,
initialItemCount: _items.length,
itemBuilder: (context, index, animation) {
var itemChild = _buildItem(_items[index]);
return _buildFadeWidget(itemChild, animation);
},
到现在为止AnimatedList仍无动画效果,还需要使用AnimatedListState来添加或删除Item来触发动画效果。
/// 在列表的尾部添加Item
void _addItem() {
var nextItemIndex = _items.length;
_items.add('Item $nextItemIndex');
_listKey.currentState?.insertItem(nextItemIndex);
}
/// 从列表中间位置移除Item
void _removeItem() {
if (_items.isEmpty) {
return;
}
int index = _items.length ~/ 2;
/// 列表删除时,索引就不可用了,所以需要得到删除的值以及对应的Widget 来展示还在刷新的列表
String item = _items.removeAt(index);
_listKey.currentState?.removeItem(
index,
(context, animation) => _buildFadeWidget(_buildItem(item), animation),
);
}
查看此时动画效果
从效果图看感觉动画太快了,需要修改一下动画效果。
AnimatedListState
添加或删除item的时候还可指定动画的持续时间。
/// 设置动画的持续时间为2秒
_listKey.currentState?.insertItem(
nextItemIndex,
duration: const Duration(seconds: 2),
);
_listKey.currentState?.removeItem(
index,
(context, animation) => _buildFadeWidget(_buildItem(item), animation),
duration: const Duration(seconds: 2),
);
左进左出动画效果使用 SlideTransition
平移动画实现,若不熟悉该控件的用法,建议先看博客 Flutter 淡入淡出与逐渐出现动画
实现方式与实现Item淡入淡出动画差不多,只需要在ItemWidget上套一层SlideTransition即可。其它的就不详细描述了,具体的看后面列出的代码。
/// 左进左出动画 Widget
Widget _buildSlideWidget(
Widget child,
Animation<double> animation,
) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(-1, 0),
end: const Offset(0, 0),
).animate(
/// 使用非线性动画展示
CurvedAnimation(parent: animation, curve: Curves.elasticInOut),
),
child: child,
);
}
效果图如下
void _loadData() async {
for (int i = 0; i < 6; i++) {
// 等待500ms
await Future.delayed(const Duration(milliseconds: 500));
_items.add('Item $i');
_listKey.currentState?.insertItem(
i,
duration: const Duration(milliseconds: 500),
);
}
}
class AnimatedListPage extends StatefulWidget {
const AnimatedListPage({Key? key}) : super(key: key);
@override
State<StatefulWidget> createState() => _AnimatedListPageState();
}
class _AnimatedListPageState extends State<AnimatedListPage> {
/// AnimatedList 的 key 可通过AnimatedListState来添加或删除Item
final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
final List<String> _items = [];
@override
void initState() {
super.initState();
_loadData();
}
void _loadData() async {
for (int i = 0; i < 6; i++) {
// 等待500ms
await Future.delayed(const Duration(milliseconds: 500));
_items.add('Item $i');
_listKey.currentState?.insertItem(
i,
duration: const Duration(milliseconds: 500),
);
}
}
/// 在列表的尾部添加Item
void _addItem() {
var nextItemIndex = _items.length;
_items.add('Item $nextItemIndex');
_listKey.currentState?.insertItem(
nextItemIndex,
duration: const Duration(seconds: 2),
);
}
/// 从列表中间位置移除Item
void _removeItem() {
if (_items.isEmpty) {
return;
}
int index = _items.length ~/ 2;
/// 列表删除时,索引就不可用了,所以需要得到删除的值以及对应的Widget 来展示还在刷新的列表
String item = _items.removeAt(index);
_listKey.currentState?.removeItem(
index,
(context, animation) => _buildSlideWidget(_buildItem(item), animation),
duration: const Duration(seconds: 2),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: const Text('AnimatedList'),
centerTitle: true,
actions: [
IconButton(
onPressed: _removeItem,
icon: const Icon(Icons.remove),
),
IconButton(
onPressed: _addItem,
icon: const Icon(Icons.add),
),
const SizedBox(width: 8),
],
),
body: AnimatedList(
key: _listKey,
initialItemCount: _items.length,
itemBuilder: (context, index, animation) {
var itemChild = _buildItem(_items[index]);
return _buildSlideWidget(itemChild, animation);
},
),
);
}
/// 无动画效果的Item
Widget _buildItem(String item) {
return Container(
height: 44.0,
margin: const EdgeInsets.only(bottom: 1),
color: Colors.black,
alignment: Alignment.center,
child: Text(item, style: const TextStyle(color: Colors.white)),
);
}
/// 淡入淡出的Widget
Widget _buildFadeWidget(
Widget child,
Animation<double> animation,
) {
return FadeTransition(
opacity: Tween<double>(begin: 0, end: 1).animate(animation),
child: child,
);
}
/// 左进左出动画 Widget
Widget _buildSlideWidget(
Widget child,
Animation<double> animation,
) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(-1, 0),
end: const Offset(0, 0),
).animate(
/// 使用非线性动画展示
CurvedAnimation(parent: animation, curve: Curves.elasticInOut),
),
child: child,
);
}
}