Flutter 列表Item动画 — AnimatedList实现Item左进左出、淡入淡出

系列文章

  1. Flutter 旋转动画 — RotationTransition
  2. Flutter 平移动画 — 4种实现方式
  3. Flutter 淡入淡出与逐渐出现动画
  4. Flutter 尺寸缩放、形状、颜色、阴影变换动画
  5. Flutter 列表Item动画 — AnimatedList实现Item左进左出、淡入淡出
  6. Flutter Hero 实现共享元素转场动画
  7. Flutter Hero 实现径向变换动画 — 圆形变成矩形的转场动画
  8. Flutter 自定义动画 — 数字递增动画和文字逐行逐字出现或消失动画


1 列表Item动画效果图

在这里插入图片描述


2 列表动画 AnimatedList

AnimatedList 是Flutter提供的一个可以在插入或移除Item时为Item设置动画的列表Widget。

2.1 实现Item淡入淡出动画

淡入淡出效果使用 FadeTransition实现,若不熟悉该控件的用法,建议先看博客
Flutter 平移动画 — 4种实现方式

2.1.1 AnimatedList 列表展示

AnimatedList 的主要的属性

  • Widget Function(BuildContext context, int index, Animation animation) itemBuilder
    用于构建Item,可在回调中animation的基础上定义Item动画
  • int initialItemCount
    初始的item数量
  • GlobalKey<AnimatedListState> key
    可通过 AnimatedListState 来添加或删除Item,以触发动画效果

以下代码为实现 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)),
    );
  }
}

2.1.2 编写淡入淡出动画效果的Item Widget

在无动画的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);
  },

2.1.3 添加或删除时触发动画效果

到现在为止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),
    );
  }

查看此时动画效果

在这里插入图片描述


2.1.4 修改动画持续时间

从效果图看感觉动画太快了,需要修改一下动画效果。

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),
    );

2.1.5 淡入淡出动画图

在这里插入图片描述


2.2 实现Item左进左出非线性动画

左进左出动画效果使用 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,
    );
  }

效果图如下

在这里插入图片描述


2.3 设置初次加载时也展示动画效果

  1. 首先抓取数据
  2. for循环添加数据
  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),
      );
    }
  }

3 完整代码

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,
    );
  }
}

猜你喜欢

转载自blog.csdn.net/ww897532167/article/details/125377920