看到这个标题的时候,有没有同学想到了一篇文章:Flutter | 求求你们了,切换 Widget 的时候加上动画吧![1]
是否想起了那个组件:AnimatedSwitcher
?
这两个控件有相似的地方,也有不同的地方。
但是这篇并不打算说出来他俩有什么不同,主要来讲解 AnimatedCrossFade
该组件使用的方式和大致可以使用的场景。
A widget that cross-fades between two given children and animates itself between their sizes. 在两个子 Widget 之间交叉淡入并在其大小之间设置动画的小部件。
其中「交叉淡入」其实是电影中的术语,意思就是由一个要素进入另一个要素。
也就是「由一个 Widget 进入另一个 Widget」。
const AnimatedCrossFade({
Key key,
@required this.firstChild,
@required this.secondChild,
this.firstCurve = Curves.linear,
this.secondCurve = Curves.linear,
this.sizeCurve = Curves.linear,
this.alignment = Alignment.topCenter,
@required this.crossFadeState,
@required this.duration,
this.reverseDuration,
this.layoutBuilder = defaultLayoutBuilder,
}) : assert(firstChild != null),
assert(secondChild != null),
assert(firstCurve != null),
assert(secondCurve != null),
assert(sizeCurve != null),
assert(alignment != null),
assert(crossFadeState != null),
assert(duration != null),
assert(layoutBuilder != null),
super(key: key);
可以看到必要的参数有四个:
1.
firstChild:第一个 childWidget,也就是直接展示的那个
2.
secondChild:第二个 childWidget,切换时显示的
3.
crossFadeState:final CrossFadeState crossFadeState
,是一个枚举:
enum CrossFadeState {
/// Show the first child ([AnimatedCrossFade.firstChild]) and hide the second
/// ([AnimatedCrossFade.secondChild]]).
showFirst,
/// Show the second child ([AnimatedCrossFade.secondChild]) and hide the first
/// ([AnimatedCrossFade.firstChild]).
showSecond,
4.
duration:不多说,动画切换的时间
有了这四个参数,我们就能用它了。
看一下代码:
AnimatedCrossFade(
duration: const Duration(seconds: 3),
firstChild: const FlutterLogo(style: FlutterLogoStyle.horizontal, size: 100.0),
secondChild: const FlutterLogo(style: FlutterLogoStyle.stacked, size: 100.0),
crossFadeState: _first ? CrossFadeState.showFirst : CrossFadeState.showSecond,
)
先看效果:
这不就跟 AnimatedSwitcher
一样吗?
这么看起来确实其实没什么屌的,官方的demo只是给你一个简单的使用方法而已。
我们可以从刚才的官方介绍里找到一点不一样的地方:「并在其大小之间设置动画」。
那我们给他们套上颜色,并且改一下大小来看看:
有内味了是不,可是这切换回来的时候怎么有点鬼畜的感觉?不要着急。
为什么会有鬼畜的效果?遇事不决看源码,去年在写文章的时候说过,Flutter 的源码里有特别多的注释和 demo。那就先来看一下他的注释:
/// The animation is controlled through the [crossFadeState] parameter.
/// [firstCurve] and [secondCurve] represent the opacity curves of the two
/// children. The [firstCurve] is inverted, i.e. it fades out when providing a
/// growing curve like [Curves.linear]. The [sizeCurve] is the curve used to
/// animate between the size of the fading-out child and the size of the
/// fading-in child.
///
/// This widget is intended to be used to fade a pair of widgets with the same
/// width. In the case where the two children have different heights, the
/// animation crops overflowing children during the animation by aligning their
/// top edge, which means that the bottom will be clipped.
///
/// The animation is automatically triggered when an existing
/// [AnimatedCrossFade] is rebuilt with a different value for the
/// [crossFadeState] property.
动画是通过[crossFadeState]参数控制的。[firstCurve]和[secondCurve]表示两个孩子的不透明度曲线。[firstCurve]是倒置的,即当提供诸如[Curves.linear]之类的增长曲线时,它会淡出。[sizeCurve]是用于在淡出子项的大小和淡入子项的大小之间进行动画处理的曲线。
此小部件用于淡化一对具有相同宽度的小部件。如果两个孩子的高度不同,则动画会在动画过程中通过对齐它们的顶部边缘来裁剪溢出的child,这意味着将裁剪底部。
当以不同的值重建现有的[AnimatedCrossFade]时,动画会自动触发。
画重点:如果两个孩子的高度不同,则动画会在动画过程中通过对齐它们的顶部边缘来裁剪溢出的child,这意味着将裁剪底部。
对齐顶部边缘和裁剪底部,那我们还是先来看一下 AnimatedCrossFade
是如何做到 在大小之间做动画的。
具体重点代码如下:
@override
Widget build(BuildContext context) {
// ...
// 去掉于此无关代码
return ClipRect(
child: AnimatedSize(
alignment: widget.alignment,
duration: widget.duration,
reverseDuration: widget.reverseDuration,
curve: widget.sizeCurve,
vsync: this,
child: widget.layoutBuilder(topChild, topKey, bottomChild, bottomKey),
),
);
}
// -----------------------------------------
// defaultLayoutBuilder
// -----------------------------------------
static Widget defaultLayoutBuilder(Widget topChild, Key topChildKey, Widget bottomChild, Key bottomChildKey) {
return Stack(
overflow: Overflow.visible,
children: <Widget>[
Positioned(
key: bottomChildKey,
left: 0.0,
top: 0.0,
right: 0.0,
child: bottomChild,
),
Positioned(
key: topChildKey,
child: topChild,
),
],
);
}
如果我们没有给 layoutBuilder
这个参数的时候,默认是有一个 defaultLayoutBuilder
的,这个里面就有前面所看到的那句话 对齐它们的顶部边缘来裁剪,所以当我们从第二个 child 回到第一个 child 的时候,就会发生宽度突然变化,解决办法也很简单:
layoutBuilder: (topChild, topChildKey, bottomChild, bottomChildKey) {
return Stack(
overflow: Overflow.visible,
alignment: Alignment.center,
children: <Widget>[
Positioned(
key: topChildKey,
child: topChild,
),
Positioned(
key: bottomChildKey,
top:0,
child: bottomChild,
),
],
);
}
把第二个的 left
、right
去掉,并且加上 alignment: Alignment.center
,这样就完成了,再来看一下效果:
鉴于 AnimatedCrossFade
的这个特性,我做了一个小 Demo,效果如下:
这玩意能玩出什么效果?
代码已传到 GitHub:AnimatedCrossFadePage[2]
[1]
Flutter | 求求你们了,切换 Widget 的时候加上动画吧!: https://juejin.im/post/5d3ac928e51d454f6f16ece0
[2]
GitHub:AnimatedCrossFadePage: https://github.com/wanglu1209/WFlutterDemo/blob/master/wx_demo_project/lib/animated_cross_fade_page.dart