和尚前段时间自定义 ACEStepper 步进器时,在 ACEStep 中尝试过 AnimatedCrossFade 用于在两个 Widget 切换过度,简单实用,今天和尚重点学习一下并尝试相关隐式动画 Widget;
const AnimatedCrossFade({
Key key,
@required this.firstChild, // 首个展示 Widget
@required this.secondChild, // 第二展示 Widget
this.firstCurve = Curves.linear, // 首个 Widget 展示动画
this.secondCurve = Curves.linear, // 第二 Widget 展示动画
this.sizeCurve = Curves.linear, // 切换时尺寸动画
this.alignment = Alignment.topCenter, // 对齐方式
@required this.crossFadeState, // 切换状态(是否切换)
@required this.duration, // 切换动画时长
this.reverseDuration, // 切换反向动画时长
this.layoutBuilder = defaultLayoutBuilder, // Widget 布局构造器
})
分析源码可知,AnimatedCrossFade 可以在指定时间内从一个 Widget 到另一个 Widget 的平滑过渡或反向过渡;其中切换状态和时长是必要属性;
return GestureDetector(
onTap: () { setState(() => isChanged = !isChanged); },
child: Container(
child: AnimatedCrossFade(
firstChild: Container(width: 100, height: 100, color: Colors.purpleAccent.withOpacity(0.4)),
secondChild: Container(width: 200, height: 200, color: Colors.blueGrey.withOpacity(0.4)),
duration: Duration(milliseconds: 1500),
crossFadeState: isChanged ? CrossFadeState.showSecond : CrossFadeState.showFirst)));
reverseDuration: Duration(milliseconds: 500),
firstCurve: Curves.fastOutSlowIn,
secondCurve: Curves.easeInExpo,
alignment: Alignment.bottomRight,
alignment: Alignment.center,
sizeCurve: Curves.easeInExpo,
sizeCurve: Curves.fastOutSlowIn,
// 默认
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)
]);
}
// 调整 Position 位置
layoutBuilder: (topChild, topChildKey, bottomChild, bottomChildKey) {
return Stack(children: <Widget>[
Positioned(key: bottomChildKey, left: 50.0, top: 50.0, right: 50.0, bottom: 50.0, child: bottomChild),
Positioned(key: topChildKey, child: topChild)
]);
}
[AnimatedCrossFade 源码]()
const AnimatedSwitcher({
Key key,
this.child,
@required this.duration, // 切换动画时长
this.reverseDuration, // 反向切换动画时长
this.switchInCurve = Curves.linear, // 切换显示时动画曲线
this.switchOutCurve = Curves.linear, // 切换隐藏时动画曲线
this.transitionBuilder = AnimatedSwitcher.defaultTransitionBuilder, // Widget 动画构造器
this.layoutBuilder = AnimatedSwitcher.defaultLayoutBuilder, // Widget 布局构造器
})
分析源码可知,AnimatedSwitcher 更加灵活,可自由设置切换动画之间显示隐藏动画效果;当 child Widget 内容或 Key 有变更时,old child 会执行隐藏动画,new child 会执行展现动画;
return GestureDetector(
onTap: () => setState(() => isChanged = !isChanged),
child: AnimatedSwitcher(
duration: Duration(milliseconds: 500),
reverseDuration: Duration(milliseconds: 1500),
child: isChanged
? Container(key: UniqueKey(), color: Colors.purpleAccent.withOpacity(0.4), width: 100, height: 100)
: Container(key: UniqueKey(), color: Colors.green.withOpacity(0.4), width: 150, height: 120)));
switchInCurve: Curves.easeInCubic,
switchOutCurve: Curves.fastLinearToSlowEaseIn,
switchInCurve: Curves.easeInExpo,
switchOutCurve: Curves.fastOutSlowIn,
// 缩放动画效果
return GestureDetector(
onTap: () => setState(() => isChanged = !isChanged),
child: AnimatedSwitcher(
duration: Duration(milliseconds: 500),
reverseDuration: Duration(milliseconds: 1500),
switchInCurve: Curves.easeInCubic,
switchOutCurve: Curves.fastLinearToSlowEaseIn,
child: isChanged
? Container(key: UniqueKey(), color: Colors.purpleAccent.withOpacity(0.4), width: 100, height: 100)
: Container(key: UniqueKey(), color: Colors.green.withOpacity(0.4), width: 150, height: 120),
transitionBuilder: (Widget child, Animation<double> animation) {
return ScaleTransition(scale: animation, child: child);
}));
// 平移动画效果
return GestureDetector(
onTap: () => setState(() => isChanged = !isChanged),
child: AnimatedSwitcher(
duration: Duration(milliseconds: 500),
reverseDuration: Duration(milliseconds: 1500),
switchInCurve: Curves.easeInExpo,
switchOutCurve: Curves.fastOutSlowIn,
child: isChanged
? Container(key: UniqueKey(), color: Colors.purpleAccent.withOpacity(0.4), width: 100, height: 100)
: Container(key: UniqueKey(), color: Colors.green.withOpacity(0.4), width: 150, height: 120),
transitionBuilder: (Widget child, Animation<double> animation) {
return SlideTransition(child: child, position: Tween<Offset>(begin: Offset(1, 0), end: Offset(0, 0)).animate(animation));
}));
// 调整层级对齐方式
layoutBuilder: (Widget currentChild, List<Widget> previousChildren) {
return Stack(children: <Widget>[
...previousChildren,
if (currentChild != null) currentChild
], alignment: Alignment.bottomRight);
}
// 只展示当前 Widget 动画效果
layoutBuilder: (Widget currentChild, List<Widget> previousChildren) {
return currentChild;
}
return GestureDetector(
onTap: () => setState(() {
++index;
index = index % 3;
}),
child: AnimatedSwitcher(
duration: Duration(milliseconds: 500),
child: _animatedItemWid(index),
transitionBuilder: (Widget child, Animation<double> animation) {
return SlideTransition(child: child, position: Tween<Offset>(begin: Offset(1, 0), end: Offset(0, 0)).animate(animation));
}));
Widget _animatedItemWid(index) {
switch (index) {
case 0:
return Container(key: UniqueKey(), color: Colors.purpleAccent.withOpacity(0.4), width: 100, height: 100);
break;
case 1:
return Container(key: UniqueKey(), color: Colors.green.withOpacity(0.4), width: 150, height: 120);
break;
case 2:
return Container(key: UniqueKey(), color: Colors.orange.withOpacity(0.4), width: 120, height: 140);
break;
}
}
[AnimatedSwitcher 源码]()
Flutter 还提供了很多灵活的隐式动画 Widget,和尚认为这两类最灵活,使用场景最多;和尚对隐式动画研究还不够深入,如有错误请多多指导!
来源:阿策小和尚