为了账号安全,请及时绑定邮箱和手机立即绑定

Flutter AnimatedList 源码分析

标签:
Html5

现在的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,不然我们用这个控件的意义何在?

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
移动开发工程师
手记
粉丝
30
获赞与收藏
45

关注作者,订阅最新文章

阅读免费教程

  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消