現在的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 的程式碼:
- 首先判斷 index 和 duration 都不能為 null
- 判斷 index 不能小於0 或者大於整個列表的 length
- 把所有在當前 index 以後的 item 下標全部 +1
- 給當前 item 設定上動畫的 controller
- 啟動動畫並在動畫完結後把當前動畫的 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/…