来自Flutter组件库

在构建复杂的用户界面时,动画经常被用来在视觉上提供反馈,改善用户体验。Flutter提供了一种称为AnimatedList的组件,它是一个可扩展的列表,具有在插入或删除条目时应用动画的功能,让我们在本文中更详细地探讨一下。

简介

AnimatedList 是动画版的List,可以轻松地为列表项的插入和移除应用动画效果。AnimatedList 使用动态状态作为其源,它是在其状态发生更改(例如:item添加或删除)时负责管理动画的集合。

属性和方法

重要属性

  • key来访问 AnimatedList 的状态(AnimatedListState),并调用其方法来插入或删除条目

  • itemBuilder:一个函数,用于在给定的上下文和索引处构建列表中的项目,并启动动画。它返回一个带有动画的widget。

  • initialItemCount:初始时列表中的项目数量。

重要方法(AnimatedListState的方法

  • insertItem:在 AnimatedList 的特定索引处插入一个项目。需要指定该索引及动画的时长。

  • removeItem:从 AnimatedList 的特定索引处删除一个项目。

示例

以下给出了使用AnimatedList组件的一个基础示例:

import 'package:flutter/material.dart';
void main() {
  runApp(const AnimatedListSample());
}
class AnimatedListSample extends StatefulWidget {
  const AnimatedListSample({super.key});

  @override
  State<AnimatedListSample> createState() => _AnimatedListSampleState();
}

class _AnimatedListSampleState extends State<AnimatedListSample> {
  final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
  late ListModel<int> _list;
  int? _selectedItem;
  late int
      _nextItem; // 当用户按下` + `按钮时插入的下一个元素。

  @override
  void initState() {
    super.initState();
    _list = ListModel<int>(
      listKey: _listKey,
      initialItems: <int>[0, 1, 2],
      removedItemBuilder: _buildRemovedItem,
    );
    _nextItem = 3;
  }

  Widget _buildItem(
      BuildContext context, int index, Animation<double> animation) {
    return CardItem(
      animation: animation,
      item: _list[index],
      selected: _selectedItem == _list[index],
      onTap: () {
        setState(() {
          _selectedItem = _selectedItem == _list[index] ? null : _list[index];
        });
      },
    );
  }

  Widget _buildRemovedItem(
      int item, BuildContext context, Animation<double> animation) {
    return CardItem(
      animation: animation,
      item: item,
      //这里没有手势检测器:我们不希望被删除的元素具有交互性。
    );
  }

  // 将“next item”插入到list模型中
  void _insert() {
    final int index =
        _selectedItem == null ? _list.length : _list.indexOf(_selectedItem!);
    _list.insert(index, _nextItem++);
  }

  //从列表模型中删除选定的项目。
  void _remove() {
    if (_selectedItem != null) {
      _list.removeAt(_list.indexOf(_selectedItem!));
      setState(() {
        _selectedItem = null;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('AnimatedList'),
          actions: <Widget>[
            IconButton(
              icon: const Icon(Icons.add_circle),
              onPressed: _insert,
              tooltip: 'insert a new item',
            ),
            IconButton(
              icon: const Icon(Icons.remove_circle),
              onPressed: _remove,
              tooltip: 'remove the selected item',
            ),
          ],
        ),
        body: Padding(
          padding: const EdgeInsets.all(16.0),
          child: AnimatedList(
            key: _listKey,
            initialItemCount: _list.length,
            itemBuilder: _buildItem,
          ),
        ),
      ),
    );
  }
}

typedef RemovedItemBuilder<T> = Widget Function(
    T item, BuildContext context, Animation<double> animation);

class ListModel<E> {
  ListModel({
    required this.listKey,
    required this.removedItemBuilder,
    Iterable<E>? initialItems,
  }) : _items = List<E>.from(initialItems ?? <E>[]);

  final GlobalKey<AnimatedListState> listKey;
  final RemovedItemBuilder<E> removedItemBuilder;
  final List<E> _items;

  AnimatedListState? get _animatedList => listKey.currentState;

  void insert(int index, E item) {
    _items.insert(index, item);
    _animatedList!.insertItem(index);
  }

  E removeAt(int index) {
    final E removedItem = _items.removeAt(index);
    if (removedItem != null) {
      _animatedList!.removeItem(
        index,
        (BuildContext context, Animation<double> animation) {
          return removedItemBuilder(removedItem, context, animation);
        },
      );
    }
    return removedItem;
  }

  int get length => _items.length;

  E operator [](int index) => _items[index];

  int indexOf(E item) => _items.indexOf(item);
}

class CardItem extends StatelessWidget {
  const CardItem({
    super.key,
    this.onTap,
    this.selected = false,
    required this.animation,
    required this.item,
  }) : assert(item >= 0);

  final Animation<double> animation;
  final VoidCallback? onTap;
  final int item;
  final bool selected;

  @override
  Widget build(BuildContext context) {
    TextStyle textStyle = Theme.of(context).textTheme.headlineMedium!;
    if (selected) {
      textStyle = textStyle.copyWith(color: Colors.lightGreenAccent[400]);
    }
    return Padding(
      padding: const EdgeInsets.all(2.0),
      child: SizeTransition(
        sizeFactor: animation,
        child: GestureDetector(
          behavior: HitTestBehavior.opaque,
          onTap: onTap,
          child: SizedBox(
            height: 80.0,
            child: Card(
              color: Colors.primaries[item % Colors.primaries.length],
              child: Center(
                child: Text('Item $item', style: textStyle),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

1720369541196.gif

这个例子创建了一个AnimatedList和添加、删除按钮。当用户点击添加按钮时,会向列表的底部添加一个新的元素,这个元素会有一个展开的动画效果。点击一个item然后点击删除按钮,改元素会有一个收起的动画效果。

注意事项

  1. 我们需要保证在使用AnimatedListState的insertItemremoveItem方法时,不只是改变_AnimatedListState_,还要改变用于构建列表项目的数据源。

  2. 不要忘记在删除项目时也使用删除动画。如果列表项在动画还没有完成的情况下就被删除,AnimatedList可能会出错。

动态列表AnimatedList是Flutter丰富动画库中的一个强大工具。只要正确使用,它就能轻松为你的应用创建出流畅、吸引人的动画效果,提升整体的用户体验。