Flutter AnimatedList 源码分析

本文深入分析了Flutter的AnimatedList组件,解释了如何在插入或删除元素时实现动画效果。通过源码解析,展示了 AnimatedList 的构造函数、Insert/Remove方法以及Build方法的工作原理,强调了itemBuilder在动画过程中的作用,并提醒在定义AnimatedList时必须传入带有动画的Widget。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

现在的UI页面已经离不开动画了,如果没有动画,页面看起来就会很突兀。

对于我们使用最多的Listview,Flutter 当然也给我们封装好了。

AnimatedListView

由于近期某些不可抗拒的原因,Flutter官网我们是打不开了。

所以我们直接点开源码看吧,在 AnimatedList 类中的第一句话是:

Creates a scrolling container that animates items when they are inserted or removed.

创建一个滚动容器,在插入或删除项目时为其设置动画。

再来看一下构造函数:

const AnimatedList({	
  Key key,	
  @required this.itemBuilder,	
  this.initialItemCount = 0,	
  this.scrollDirection = Axis.vertical,	
  this.reverse = false,	
  this.controller,	
  this.primary,	
  this.physics,	
  this.shrinkWrap = false,	
  this.padding,	
}) : assert(itemBuilder != null),	
assert(initialItemCount != null && initialItemCount >= 0),	
super(key: key);

可以看到和普通的没什么区别,那我们再来找一下怎么添加/删除item以及添加/删除时是如何设置动画的。

Insert/Remove 方法

animated_list.dart 这个文件一共才380 行代码,所以我们很快就能找到:

/// Insert an item at [index] and start an animation that will be passed	
/// to [AnimatedList.itemBuilder] when the item is visible.	
///	
/// This method's semantics are the same as Dart's [List.insert] method:	
/// it increases the length of the list by one and shifts all items at or	
/// after [index] towards the end of the list.	
void insertItem(int index, { Duration duration = _kDuration }) {	
  assert(index != null && index >= 0);	
  assert(duration != null);	
  final int itemIndex = _indexToItemIndex(index);	
  assert(itemIndex >= 0 && itemIndex <= _itemsCount);	
  // Increment the incoming and outgoing item indices to account	
  // for the insertion.	
  for (_ActiveItem item in _incomingItems) {	
    if (item.itemIndex >= itemIndex)	
      item.itemIndex += 1;	
  }	
  for (_ActiveItem item in _outgoingItems) {	
    if (item.itemIndex >= itemIndex)	
      item.itemIndex += 1;	
  }	
  final AnimationController controller = AnimationController(duration: duration, vsync: this);	
  final _ActiveItem incomingItem = _ActiveItem.incoming(controller, itemIndex);	
  setState(() {	
    _incomingItems	
      ..add(incomingItem)	
      ..sort();	
    _itemsCount += 1;	
  });	
  controller.forward().then<void>((_) {	
    _removeActiveItemAt(_incomingItems, incomingItem.itemIndex).controller.dispose();	
  });	
}

首先我们看到这里用了一个 _ActiveItem 这个类,我们去看一下是什么:

// Incoming and outgoing AnimatedList items.	
class _ActiveItem implements Comparable<_ActiveItem> {	
  _ActiveItem.incoming(this.controller, this.itemIndex) : removedItemBuilder = null;	
  _ActiveItem.outgoing(this.controller, this.itemIndex, this.removedItemBuilder);	
  _ActiveItem.index(this.itemIndex)	
    : controller = null,	
      removedItemBuilder = null;	
  final AnimationController controller;	
  final AnimatedListRemovedItemBuilder removedItemBuilder;	
  int itemIndex;	
  @override	
  int compareTo(_ActiveItem other) => itemIndex - other.itemIndex;	
}

可以看得出来,这其实就是一个包装类,封装了 AnimatedList 中常用的参数。

接下来分析一下上面添加 item 的代码:

  1. 首先判断 index 和 duration 都不能为 null

  2. 判断 index 不能小于0 或者大于整个列表的 length

  3. 把所有在当前 index 以后的 item 下标全部 +1

  4. 给当前 item 设置上动画的 controller

  5. 启动动画并在动画完结后把当前动画的 controller dispose 掉

Build 方法

删除item的同理,就不讲了,下面再来看一下 build 方法:

Widget _itemBuilder(BuildContext context, int itemIndex) {	
  final _ActiveItem outgoingItem = _activeItemAt(_outgoingItems, itemIndex);	
  if (outgoingItem != null)	
    return outgoingItem.removedItemBuilder(context, outgoingItem.controller.view);	
  final _ActiveItem incomingItem = _activeItemAt(_incomingItems, itemIndex);	
  final Animation<double> animation = incomingItem?.controller?.view ?? kAlwaysCompleteAnimation;	
  return widget.itemBuilder(context, _itemIndexToIndex(itemIndex), animation);	
}	
@override	
Widget build(BuildContext context) {	
  return ListView.builder(	
    itemBuilder: _itemBuilder,	
    itemCount: _itemsCount,	
    scrollDirection: widget.scrollDirection,	
    reverse: widget.reverse,	
    controller: widget.controller,	
    primary: widget.primary,	
    physics: widget.physics,	
    shrinkWrap: widget.shrinkWrap,	
    padding: widget.padding,	
  );	
}

可以看到其他的参数都是用 widget 里的,唯独itemBuilder 是自己写的,那我们就可以主要来看一下他。

还是一步一步来。

首先看到他是去找 outgoingItem 也就是删除的 item,我们查看一下 _activeItemAt 方法:

_ActiveItem _activeItemAt(List<_ActiveItem> items, int itemIndex) {	
  final int i = binarySearch(items, _ActiveItem.index(itemIndex));	
  return i == -1 ? null : items[i];	
}

可以看到是用了二分查找来找需要删除的items列表里是否存在该 index。

如果存在,那么直接返回 outgoingItem.removedItemBuilder,这个 itemBuilder 是需要我们自己写的。

目的是在做动画的时候显示,而 insertItem 就不需要。

因为我们插入的 widget 肯定也是原有的widget,所以在写AnimatedList 时就已经写好了。

接下来就是判断添加的动画是否存在。

如果不存在,就默认一个永远都是完成的动画,也就是没有动画的动画 -> kAlwaysCompleteAnimation

点开看一下:

class _AlwaysCompleteAnimation extends Animation<double> {	
  const _AlwaysCompleteAnimation();	
  @override	
  void addListener(VoidCallback listener) { }	
  @override	
  void removeListener(VoidCallback listener) { }	
  @override	
  void addStatusListener(AnimationStatusListener listener) { }	
  @override	
  void removeStatusListener(AnimationStatusListener listener) { }	
  @override	
  AnimationStatus get status => AnimationStatus.completed;	
  @override	
  double get value => 1.0;	
  @override	
  String toString() => 'kAlwaysCompleteAnimation';	
}

可以看到 value 和 status 永远都是完成的状态。

所以这就是我们初始的列表没有动画的原因,而在调用 insertItem 的时候默认传入了一个 controller。

所以我们了解到,如果我们在定义 itemWidget 的时候,如果不给动画的插值器,那么动画就会是一个 kAlwaysCompleteAnimation

最后把这个widget 返回就完成了这一个 itemBuilder。

总结

所以,综上所述,我们在定义一个 AnimatedList 时必须传入一个带动画的 Widget,不然我们用这个控件的意义何在?

关注我,每天更新 Flutter & Dart 知识。

完整代码已经传至GitHub:https://github.com/wanglu1209/WFlutterDemo

640?wx_fmt=jpeg


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值