最近倒腾Flutter,须要作列表的插入删除动画,用到了AnimatedList这个组件,也遇到一些问题,在这里分析下源码以做备忘,不足之处但愿大神指点bash
先看下组件的构造函数app
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,
})
复制代码
简单使用以下:ide
...
final GlobalKey<AnimatedListState> _listKey = new GlobalKey<AnimatedListState>();
final List<String> _list = [];
Scaffold(
backgroundColor: Colors.grey,
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: AnimatedList(
key: _listKey,
initialItemCount: _list.length,
itemBuilder: buildItem,
)),
)
/// 构建item
Widget buildItem(
BuildContext context, int index, Animation<double> animation) {...}
/// 执行删除动画时须要替换原来位置item的组件
Widget _buildRemovedItem(
String item, BuildContext context, Animation<double> animation) {...}
/// 增长一条数据
void _insert(String item, [int index = 0]) {
_list.insert(index, item);
listKey.currentState.insertItem(index);
}
/// 删除一条数据
void _remove(int index) {
var removedItem = _list.removeAt(index);
listKey.currentState.removeItem(index,
(BuildContext context, Animation<double> animation) {
return _buildRemovedItem(removedItem, context, animation);
})
}
复制代码
GlobalKey的做用:函数
事实上AnimatedListState在初始化后,内部维护了_itemCount,因此当外部对数据集进行操做时,须要同步AnimatedListState源码分析
看下组件对应的State布局
class AnimatedListState extends State<AnimatedList> with TickerProviderStateMixin<AnimatedList> {
final List<_ActiveItem> _incomingItems = <_ActiveItem>[];
final List<_ActiveItem> _outgoingItems = <_ActiveItem>[];
int _itemsCount = 0;
@override
void initState() {
super.initState();
_itemsCount = widget.initialItemCount;
}
@override
void dispose() {
for (_ActiveItem item in _incomingItems)
item.controller.dispose();
for (_ActiveItem item in _outgoingItems)
item.controller.dispose();
super.dispose();
}
...
@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,
);
}
}
复制代码
重点来了~扩展功能的关键代码就在上面代码片断里的...里,咱接着看post
...
void insertItem(int index, { Duration duration = _kDuration }) {
...
final int itemIndex = _indexToItemIndex(index);
...
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();
});
}
...
复制代码
当调用insertItem增长一个元素时动画
调用_indexToItemIndex(index),将外部数据源集合传入的index转换成AnimatedListState内部实际的itemIndex(由于删除动画未播放完成 _itemCount的值是不会变的,因此会出现外部数据源集合长度不一致的状况,须要作Index修正 )ui
int _indexToItemIndex(int index) {
int itemIndex = index;
for (_ActiveItem item in _outgoingItems) {
if (item.itemIndex <= itemIndex)
itemIndex += 1;
else
break;
}
return itemIndex;
}
复制代码
看的出来,这里主要是对若是有正在删除元素的动做状况下,对index修正(当当前传入index>=删除动画的index时,即代表该传入index映射在AnimatedListState里认为的集合里的位置应该要+1)this
遍历正在插入动画的item集合,由于插入了个新元素,因此本来播放着动画的item的itemIndex若是>=当前插入的index,则须要+1到正确的位置
将新增的index项封装成_ActiveItem,添加到 _incomingItems里,表示这个位置的item正在播放插入动画,而后 _itemsCount+1
开启动画,动画结束后将 item从_incomingItems里清掉
细心的观察会发现,第一步里只对_outgoingItems集合进行了修正,没有对 _incomingItem进行处理,这是由于新增的时候 _itemsCount += 1是在动画开始前就设置了,而remove是在动画结束后才会去减1的,之因此动画结束后才减 _itemCount,主要是由于。。。减了widget就消失了!!!哪还有动画
void removeItem(int index, AnimatedListRemovedItemBuilder builder, { Duration duration = _kDuration }) {
...
final int itemIndex = _indexToItemIndex(index);
...
final _ActiveItem incomingItem = _removeActiveItemAt(_incomingItems, itemIndex);
final AnimationController controller = incomingItem?.controller
?? AnimationController(duration: duration, value: 1.0, vsync: this);
final _ActiveItem outgoingItem = _ActiveItem.outgoing(controller, itemIndex, builder);
setState(() {
_outgoingItems
..add(outgoingItem)
..sort();
});
controller.reverse().then<void>((void value) {
_removeActiveItemAt(_outgoingItems, outgoingItem.itemIndex).controller.dispose();
// Decrement the incoming and outgoing item indices to account
// for the removal.
for (_ActiveItem item in _incomingItems) {
if (item.itemIndex > outgoingItem.itemIndex)
item.itemIndex -= 1;
}
for (_ActiveItem item in _outgoingItems) {
if (item.itemIndex > outgoingItem.itemIndex)
item.itemIndex -= 1;
}
setState(() {
_itemsCount -= 1;
});
});
}
复制代码
删除动画,主要步骤以下:
上述动做都是在对插入、删除动做进行index、动画的处理,当开启动画后,便会开始不断地触发build,而build方法里则构造ListView,最终调用_itemBuilder方法
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);
}
复制代码
上述_itemBuilder方法,是直接赋值给ListView的itemBuilder属性,用来构建视图列表的每一个item的widget的回调
首先判断回调itemIndex对应的Item此时是否正在进行删除动画, _activeItemAt(_outgoingItems, itemIndex)返回封装的 _ActiveItem,不为null则表示找到,此时应该调用removeItem方法传入的AnimatedListRemovedItemBuilder回调进行构建删除显示的Widget,以后构建下一个item
若是判断此ItemIndex没在删除动画集合里,则再判断是不是正在执行插入动画的item,是则取出Animation,不然使用默认的Animation,最后回调父widget传入的函数,进行构建Widget
_itemIndexToIndex(itemIndex):该方法和 _indexToItemIndex方法相反,它是将内部的itemIndex转成外部数据源集合相应的index(由于若是有正在执行删除动画的item则内外count会存在不一致)
int _itemIndexToIndex(int itemIndex) {
int index = itemIndex;
for (_ActiveItem item in _outgoingItems) {
assert(item.itemIndex != itemIndex);
if (item.itemIndex < itemIndex)
index -= 1;
else
break;
}
return index;
}
复制代码
能够看出来,遍历正在删除动画的item,若是此时的itemIndex大于它们,则须要-1才能修正回去
AnimatedList主要是利用装饰器模式对ListView进行了功能上的扩展,其在初始化后,内部对数据集的数量进行了维护以方便动画的播放,须要注意的是:进行删除动画时,实际上数据源集合的长度和内部_itemCount是会在一段时间内存在不一致的
亮点:
坑:
本文由Owen Lee原创,转载请注明来源: juejin.im/post/5dd525…