现在的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,不然我们用这个控件的意义何在?
共同学习,写下你的评论
评论加载中...
作者其他优质文章