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使用来获取状态实例

属性

属性名属性类型说明
insertItemvoid Function(int index, {Duration duration})在指定索引处插入新项并触发动画
removeItemvoid Function(int index, AnimatedListRemovedItemBuilder builder, {Duration duration})移除指定索引的项并显示删除动画
animateItemvoid Function(int index, {Duration duration})对现有项触发动画效果

关键属性详解

  • insertItem方法

    • 作用: 在指定位置插入新列表项并播放插入动画
    • 参数:
      • index: 插入位置的索引(0-based)
      • duration: 可选,动画持续时间
    • 使用场景: 添加新数据到列表时调用
  • removeItem方法

    • 作用: 移除指定项并播放删除动画
    • 参数:
      • index: 要移除项的索引
      • builder: 构建被移除项的Widget构建器
      • duration: 可选,动画持续时间
    • 重要提示: 必须在数据列表中实际删除该项后再调用此方法
  • animateItem方法

    • 作用: 对现有项重新触发动画,用于更新动画效果
    • 使用场景: 当项内容发生变化需要重新动画时使用