AnimatedListState
管理`AnimatedList`组件动态列表项动画状态的核心类
AnimatedListState是Flutter中用于管理AnimatedList组件动态列表项动画状态的核心类。它不是一个独立的Widget,而是AnimatedList的状态管理对象,专门负责处理列表项的添加、删除和更新动画效果。
主要逻辑: 通过维护一个全局的GlobalKey<AnimatedListState>,开发者可以控制列表项的动画行为,实现流畅的插入、删除和更新动画,提升用户体验。
使用场景
- 动态列表管理: 需要动态添加或删除列表项的应用
- 聊天应用: 消息的发送和删除动画
- 待办事项: 任务的添加和完成删除动画
- 购物车: 商品项的增删动画效果
示例
基础动态列表
class BasicAnimatedList extends StatefulWidget {
_BasicAnimatedListState createState() => _BasicAnimatedListState();
}
class _BasicAnimatedListState extends State<BasicAnimatedList> {
final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
List<String> _items = ['Item 1', 'Item 2', 'Item 3'];
void _addItem() {
final int newIndex = _items.length;
_items.add('Item ${newIndex + 1}');
_listKey.currentState!.insertItem(newIndex);
}
void _removeItem(int index) {
final String removedItem = _items.removeAt(index);
_listKey.currentState!.removeItem(
index,
(context, animation) => _buildItem(removedItem, animation),
);
}
Widget _buildItem(String item, Animation<double> animation) {
return SizeTransition(
sizeFactor: animation,
child: Card(
child: ListTile(
title: Text(item),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () => _removeItem(_items.indexOf(item)),
),
),
),
);
}
Widget build(BuildContext context) {
return Column(
children: [
Expanded(
child: AnimatedList(
key: _listKey,
initialItemCount: _items.length,
itemBuilder: (context, index, animation) {
return _buildItem(_items[index], animation);
},
),
),
ElevatedButton(
onPressed: _addItem,
child: Text('添加项目'),
),
],
);
}
}
聊天消息列表
class ChatMessageList extends StatefulWidget {
_ChatMessageListState createState() => _ChatMessageListState();
}
class _ChatMessageListState extends State<ChatMessageList> {
final GlobalKey<AnimatedListState> _chatKey = GlobalKey<AnimatedListState>();
List<ChatMessage> _messages = [];
void _sendMessage(String text) {
final newMessage = ChatMessage(text: text, timestamp: DateTime.now());
final int newIndex = _messages.length;
_messages.insert(0, newMessage); // 最新消息在顶部
_chatKey.currentState!.insertItem(0);
}
void _deleteMessage(int index) {
final removedMessage = _messages.removeAt(index);
_chatKey.currentState!.removeItem(
index,
(context, animation) => _buildMessageItem(removedMessage, animation),
);
}
Widget _buildMessageItem(ChatMessage message, Animation<double> animation) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(-1, 0),
end: Offset.zero,
).animate(CurvedAnimation(
parent: animation,
curve: Curves.easeOut,
)),
child: Dismissible(
key: ValueKey(message.timestamp),
direction: DismissDirection.endToStart,
onDismissed: (direction) => _deleteMessage(_messages.indexOf(message)),
background: Container(color: Colors.red),
child: Card(
child: ListTile(
title: Text(message.text),
subtitle: Text('${message.timestamp.hour}:${message.timestamp.minute}'),
),
),
),
);
}
Widget build(BuildContext context) {
return Column(
children: [
Expanded(
child: AnimatedList(
key: _chatKey,
initialItemCount: _messages.length,
reverse: true, // 最新消息在顶部
itemBuilder: (context, index, animation) {
return _buildMessageItem(_messages[index], animation);
},
),
),
// 消息输入组件...
],
);
}
}
class ChatMessage {
final String text;
final DateTime timestamp;
ChatMessage({required this.text, required this.timestamp});
}
带分类的待办列表
class CategorizedTodoList extends StatefulWidget {
_CategorizedTodoListState createState() => _CategorizedTodoListState();
}
class _CategorizedTodoListState extends State<CategorizedTodoList> {
final GlobalKey<AnimatedListState> _todoKey = GlobalKey<AnimatedListState>();
Map<String, List<TodoItem>> _categories = {
'工作': [TodoItem('完成报告', false), TodoItem('会议准备', false)],
'个人': [TodoItem('购物', false), TodoItem('健身', false)],
};
void _addTodo(String category, String task) {
final newTodo = TodoItem(task, false);
_categories[category]!.add(newTodo);
final int newIndex = _getGlobalIndex(category, _categories[category]!.length - 1);
_todoKey.currentState!.insertItem(newIndex);
}
void _toggleTodo(String category, int index) {
final int globalIndex = _getGlobalIndex(category, index);
_categories[category]![index].isCompleted = !_categories[category]![index].isCompleted;
_todoKey.currentState!.removeItem(
globalIndex,
(context, animation) => _buildTodoItem(_categories[category]![index], animation),
);
_todoKey.currentState!.insertItem(globalIndex);
}
int _getGlobalIndex(String category, int localIndex) {
int globalIndex = 0;
for (final entry in _categories.entries) {
if (entry.key == category) {
return globalIndex + localIndex;
}
globalIndex += entry.value.length;
}
return globalIndex;
}
Widget _buildTodoItem(TodoItem todo, Animation<double> animation) {
return FadeTransition(
opacity: animation,
child: Card(
child: ListTile(
leading: Checkbox(
value: todo.isCompleted,
onChanged: (value) => _toggleTodo('工作', 0), // 简化示例
),
title: Text(
todo.task,
style: TextStyle(
decoration: todo.isCompleted ? TextDecoration.lineThrough : null,
),
),
),
),
);
}
Widget build(BuildContext context) {
final allItems = _categories.entries.expand((entry) => entry.value).toList();
return AnimatedList(
key: _todoKey,
initialItemCount: allItems.length,
itemBuilder: (context, index, animation) {
return _buildTodoItem(allItems[index], animation);
},
);
}
}
class TodoItem {
String task;
bool isCompleted;
TodoItem(this.task, this.isCompleted);
}
注意点
常见问题
Key管理: 必须使用GlobalKey<AnimatedListState>来获取状态实例- 索引同步: 确保数据列表与动画列表的索引保持一致
- 性能优化: 避免在动画过程中进行大量的重建操作
优化技巧
- 使用
SliverAnimatedList替代AnimatedList以获得更好的滚动性能 - 对于大量数据,考虑使用
ListView.builder的懒加载特性 - 合理设置动画时长,避免过长的动画影响用户体验
最佳实践
- 始终在状态类中维护数据列表的副本
- 使用合适的动画曲线(如
Curves.easeInOut) - 为删除操作提供适当的回退机制
构造函数
AnimatedListState()
参数说明:
- 无直接参数,通过
AnimatedList组件创建
初始化描述:
- 内部维护动画控制器和列表项状态
- 需要配合
GlobalKey使用来获取状态实例
属性
| 属性名 | 属性类型 | 说明 |
|---|---|---|
insertItem | void Function(int index, {Duration duration}) | 在指定索引处插入新项并触发动画 |
removeItem | void Function(int index, AnimatedListRemovedItemBuilder builder, {Duration duration}) | 移除指定索引的项并显示删除动画 |
animateItem | void Function(int index, {Duration duration}) | 对现有项触发动画效果 |
关键属性详解
-
insertItem方法- 作用: 在指定位置插入新列表项并播放插入动画
- 参数:
index: 插入位置的索引(0-based)duration: 可选,动画持续时间
- 使用场景: 添加新数据到列表时调用
-
removeItem方法- 作用: 移除指定项并播放删除动画
- 参数:
index: 要移除项的索引builder: 构建被移除项的Widget构建器duration: 可选,动画持续时间
- 重要提示: 必须在数据列表中实际删除该项后再调用此方法
-
animateItem方法- 作用: 对现有项重新触发动画,用于更新动画效果
- 使用场景: 当项内容发生变化需要重新动画时使用