当我第一次看到AnimationSwitcher窗口小部件时,我想到可以翻转一个窗口小部件,从而露出它的背面。
我们想要实现的目标
我完全错了:AnimationSwitcher允许你…用定义的动画在多个小部件之间切换(默认动画是淡出过渡),这个组件太普通了。
我应该仔细阅读…
但它的用法非常简单易懂,我将向你展示如何完成该动画。
我发现了一个名为animated_card_switcher的扑包可以做翻转动画,但它似乎不能有效维护动画,并且它的代码也过于复杂了。
这是开发步骤:
- 创建前后小部件
- 使用AnimationSwitcher小部件进行动画处理
- 编写你的自定义过渡生成器来旋转你的卡
- 添加曲线
创建前后小部件
在此示例中,我将使用前后小部件的简化版本(因为在整个过程中不是很重要)。
唯一要记住的是,你必须为顶级窗口小部件设置一个键,帮助AnimationSwitcher检测到该窗口小部件是否得到更改(并因此执行动画)。
以下是我将使用的小部件布局示例:
Widget __buildLayout({Key key, String faceName, Color backgroundColor}) {
return Container(
key: key,
decoration: BoxDecoration(
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(20.0),
color: backgroundColor,
),
child: Center(
child: Text(faceName.substring(0, 1), style: TextStyle(fontSize: 80.0)),
),
);
所以我的小部件的前视图和后视图将是:
Widget _buildFront() {
return __buildLayout(
key: ValueKey(true),
backgroundColor: Colors.blue,
faceName: "F",
);
}
Widget _buildRear() {
return __buildLayout(
key: ValueKey(false),
backgroundColor: Colors.blue.shade700,
faceName: "R",
);
}
使用AnimationSwitcher小部件进行动画处理
现在,我们可以使用AnimationSwitcher小部件为前后小部件之间的过渡设置动画。
在StatefulWidget中,我重写了build方法以创建一个页面,该页面将在其中心显示动画。
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool _displayFront;
bool _flipXAxis;
@override
void initState() {
super.initState();
_displayFront = true;
_flipXAxis = true;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.widget.title),
centerTitle: true,
),
body: Center(
child: Container(
constraints: BoxConstraints.tight(Size.square(200.0)),
child: _buildFlipAnimation(),
),
);
}
}
我将动画从页面分离到_buildFlipAnimation方法,使代码更清晰。
以下是该方法的第一个版本:
Widget _buildFlipAnimation() {
return GestureDetector(
onTap: () => setState(() =>_showFrontSide = !_showFrontSide),
child: AnimatedSwitcher(
duration: Duration(milliseconds: 600),
child: _showFrontSide ? _buildFront() : _buildRear(),
),
);
}
当我们单击小部件时,我们可以看到前面的小部件正在褪色,露出了背面。再次单击就会使后窗小部件褪色以露出前侧。
至少有一些改变。
我们想从Y轴旋转小部件。希望借助输入 TransitionBuilder,让 AnimationTransfer允许我们重新定义过渡。
编写你的自定义过渡生成器以旋转你的卡
所以计划是这样的:我们希望旋转180°(pi)。我们将小部件包装到AnimatedBuidler中,并使用Transform小部件进行旋转。
Widget __transitionBuilder(Widget widget, Animation<double> animation) {
final rotateAnim = Tween(begin: pi, end: 0.0).animate(animation);
return AnimatedBuilder(
animation: rotateAnim,
child: widget,
builder: (context, widget) {
return Transform(
transform: Matrix4.rotationY(value),
child: widget,
alignment: Alignment.center,
);
},
);
}
翻转是正确的,但我们感觉不到深度。
这是一个好的开始,但并不是我们真正想要的。我们能看到在设置动画过渡时,后部件从头到尾都在顶部。
最后一面的显示速度过快。
我们需要将前面板逐步替换为后面板。
因此,我们需要修改两件事:
- 显示顺序必须要颠倒过来:要替换的小部件必须在堆栈的顶部。
- 在动画的中间,要替换的小部件必须清理。
为此,我们将更改AnimationSwitcher实例的layoutBuilder输入。
layoutBuilder: (widget, list) => Stack(children: **[widget, ...list]** ),
然后,在动画的中间,pi / 2的旋转使小部件的宽度变为0.0。因此,我们要阻止该旋转,以使前一个小部件(仅)具有动画效果。
Widget __transitionBuilder(Widget widget, Animation<double> animation) {
final rotateAnim = Tween(begin: pi, end: 0.0).animate(animation);
return AnimatedBuilder(
animation: rotateAnim,
child: widget,
builder: (context, widget) {
final isUnder = (ValueKey(_showFrontSide) != widget.key);
final value = isUnder ? min(rotateAnim.value, pi / 2) : rotateAnim.value;
return Transform(
transform: Matrix4.rotationY(value),
child: widget,
alignment: Alignment.center,
);
},
);
}
现在好多了,但是我们还没有完成呢!要想增加部件旋转的感觉,我们需要让部件有些“倾斜”
我们需要深度…一点点深度。
在动画开始和结束时,此“倾斜”值都必须是0.0,因为我们要在部件的两端添加动画,所以他们的倾斜值必须是相对的。例如,前部件的倾斜值是0.2,那后部件的倾斜值必须是-0.2。
Widget __transitionBuilder(Widget widget, Animation<double> animation) {
final rotateAnim = Tween(begin: pi, end: 0.0).animate(animation);
return AnimatedBuilder(
animation: rotateAnim,
child: widget,
builder: (context, widget) {
final isUnder = (ValueKey(_showFrontSide) != widget.key);
var tilt = ((animation.value - 0.5).abs() - 0.5) * 0.003;
tilt *= isUnder ? -1.0 : 1.0;
final value = isUnder ? min(rotateAnim.value, pi / 2) : rotateAnim.value;
return Transform(
transform: Matrix4.rotationY(value)..setEntry(3, 0, tilt),
child: widget,
alignment: Alignment.center,
);
},
);
}
要把倾斜应用到部件中,我们要手动设置一个特定值到定义旋转的Matrix4中。
你可以在此处获取有关Matrix4的更多信息:https://medium.com/flutter-community/advanced-flutter-matrix4-and-perspective-transformations-a79404a0d828
添加曲线
最后,要给动画增加一点活力/强度,你可能需要修改AnimationSwitcher的曲线输入参数。
带有曲线总是会更好!
这是我的第一次尝试:
Widget _buildFlipAnimation() {
return GestureDetector(
onTap: _switchCard,
child: AnimatedSwitcher(
duration: Duration(milliseconds: 4600),
transitionBuilder: __transitionBuilder,
layoutBuilder: (widget, list) => Stack(children: [widget, ...list]),
child: _showFrontSide ? _buildFront() : _buildRear(),
switchInCurve: Curves.easeInBack,
switchOutCurve: Curves.easeOutBack,
),
);
}
我不得不向你展示慢动作的问题。
Ho nooooooooooo …
由于曲线并不完全相同,并且由于先前的窗口小部件动画将被反向播放,因此你会获得两个未能正确叠加的窗口小部件的块效应,两者之间会有一点变化。
几帧动画…
为避免这种情况,我们必须使用曲线的翻转属性
switchOutCurve: Curves. ***easeInBack*** . **flipped** ,
Sloooow Mooooo…(现在很完美)
结论
如你所见,这并不难:我仅用一个属性(小部件的显示面)以及大约30行代码(仅动画)就制作出了此动画。
我认为为此创建一个软件包并不明智。在代码中添加依赖项意味着,如果它不适用于版本更新(例如),那你的项目将被暂停一段时间。更糟糕的是,如果不再保持依赖关系,你将无法保证你的项目会在未来6个月、1或2年内被正确编译…
适当复制并粘贴此示例
因此,随时使用我的代码。我不喜欢复制粘贴,所以我会说“适当使用我的代码”,意思其实是复制粘贴、 理解 它并根据你的需要对其进行 修改 !
你可以在这里找到本文中使用的演示:
GONZALEZD / flutter_demos
一个新的Flutter项目。该项目是Flutter应用程序的起点。一些资源可以帮助您入门…
原文作者 David Gonzalez
原文链接 https://medium.com/flutter-community/flutter-flip-card-animation-eb25c403f371