Flutter AnimatedList 源碼分析

如今的UI頁面已經離不開動畫了,若是沒有動畫,頁面看起來就會很突兀。git

對於咱們使用最多的Listview,Flutter 固然也給咱們封裝好了。github

AnimatedListView

因爲近期某些不可抗拒的緣由,Flutter官網咱們是打不開了。markdown

因此咱們直接點開源碼看吧,在 AnimatedList 類中的第一句話是:ide

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

建立一個滾動容器,在插入或刪除項目時爲其設置動畫。動畫

再來看一下構造函數:ui

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以及添加/刪除時是如何設置動畫的。this

Insert/Remove 方法

animated_list.dart 這個文件一共才380 行代碼,因此咱們很快就能找到:spa

/// 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 這個類,咱們去看一下是什麼:code

// 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:github.com/wanglu1209/…

相關文章
相關標籤/搜索