专栏首页Flutter笔记Flutter AnimatedList 源码分析

Flutter AnimatedList 源码分析

现在的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

本文分享自微信公众号 - Flutter笔记(Flutter_Note),作者:Flutter笔记

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-06-02

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Flutter Wrap & Chip

    一般来讲是一个 RecyclerView + 自动换行的 layoutManager + 自定义的background。

    Flutter笔记
  • Flutter 史上最牛拖动控件 Draggable

    我们定义一个 GridView,里面每一个 item 都是一个 Draggable。

    Flutter笔记
  • Flutter实战 | 从 0 搭建「网易云音乐」APP(九、搜索页面、底部播放控制栏)

    本系列可能会伴随大家很长时间,这里我会从0开始搭建一个「网易云音乐」的APP出来。

    Flutter笔记
  • 架构设计基础知识整理

    From http://msdn.microsoft.com/en-us/library/ff647859.aspx

    哲洛不闹
  • Angular 2 + 折腾记 :(2)初步认识angular2,不一样的开发模式

    想来想去,概念这些东西不怎么想讲,更多的是想讲点实战性的内容。 所以有些东西跳过去了,小伙伴们请去看官方文档哈;跳跃性的前进,写的不好多包涵。。。

    CRPER
  • 我是一个CPU:这个世界慢!死!了!

    经常听到有人说磁盘很慢、网络很卡,这都是站在人类的感知维度去表述的,比如拷贝一个文件到硬盘需要几分钟到几十分钟,够我去吃个饭啦;而从网络下载一部电影,有时候需要...

    黄泽杰
  • 让 CPU 告诉你硬盘和网络到底有多慢

    经常听到有人说磁盘很慢、网络很卡,这都是站在人类的感知维度去表述的,比如拷贝一个文件到硬盘需要几分钟到几十分钟,够我去吃个饭啦;而从网络下载一部电影,有时候需要...

    芋道源码
  • CPU:不是我针对谁,在座的各位都是垃圾

    经常听到有人说磁盘很慢、网络很卡,这都是站在人类的感知维度去表述的,比如拷贝一个文件到硬盘需要几分钟到几十分钟,够我去吃个饭啦;而从网络下载一部电影,有时候可能...

    菜天哥哥
  • 公有云镜像模版的创建<二>

    明哥的运维笔记
  • 找出一个数组的最大值

    赵哥窟

扫码关注云+社区

领取腾讯云代金券