继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

Flutter AnimatedList 源码分析

Flutter笔记
关注TA
已关注
手记 49
粉丝 30
获赞 45

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

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP