如何使用Flutter翻转动画

当我第一次看到AnimationSwitcher窗口小部件时,我想到可以翻转一个窗口小部件,从而露出它的背面。

%E6%9C%AA%E5%91%BD%E5%90%8D_%E5%89%AF%E6%9C%AC
我们想要实现的目标

我完全错了:AnimationSwitcher允许你…用定义的动画在多个小部件之间切换(默认动画是淡出过渡),这个组件太普通了。

1_gLmCtOi65vBl7z8ljFNoAw
我应该仔细阅读…

但它的用法非常简单易懂,我将向你展示如何完成该动画。

我发现了一个名为animated_card_switcher的扑包可以做翻转动画,但它似乎不能有效维护动画,并且它的代码也过于复杂了。

这是开发步骤:

  • 创建前后小部件
  • 使用AnimationSwitcher小部件进行动画处理
  • 编写你的自定义过渡生成器来旋转你的卡
  • 添加曲线

创建前后小部件

在此示例中,我将使用前后小部件的简化版本(因为在整个过程中不是很重要)。

唯一要记住的是,你必须为顶级窗口小部件设置一个键,帮助AnimationSwitcher检测到该窗口小部件是否得到更改(并因此执行动画)。

1_48-WQli2uaxyr7mwpdlSCw
以下是我将使用的小部件布局示例:

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(),
    ),
  );
}

当我们单击小部件时,我们可以看到前面的小部件正在褪色,露出了背面。再次单击就会使后窗小部件褪色以露出前侧。

%E6%9C%AA%E5%91%BD%E5%90%8D_%E5%89%AF%E6%9C%AC
至少有一些改变。

我们想从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,
      );
    },
  );
}

1_QSnBx2afBRXNYUomEq1cyA
翻转是正确的,但我们感觉不到深度。

这是一个好的开始,但并不是我们真正想要的。我们能看到在设置动画过渡时,后部件从头到尾都在顶部。

%E6%9C%AA%E5%91%BD%E5%90%8D_%E5%89%AF%E6%9C%AC
最后一面的显示速度过快。

我们需要将前面板逐步替换为后面板。

因此,我们需要修改两件事:

  • 显示顺序必须要颠倒过来:要替换的小部件必须在堆栈的顶部。
  • 在动画的中间,要替换的小部件必须清理。

为此,我们将更改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,
      );
    },
  );
}

1_ERigH9WHhpPWLU4-hCv2Lw

现在好多了,但是我们还没有完成呢!要想增加部件旋转的感觉,我们需要让部件有些“倾斜”

%E6%9C%AA%E5%91%BD%E5%90%8D_%E5%89%AF%E6%9C%AC
我们需要深度…一点点深度。

在动画开始和结束时,此“倾斜”值都必须是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的曲线输入参数。

1_YMVyYHq4sGxR4_E3Mva84g
带有曲线总是会更好!

这是我的第一次尝试:

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,
    ),
  );
}

我不得不向你展示慢动作的问题。
1_oEvLEJ4DElaIkItcg0Fy3Q
Ho nooooooooooo …
由于曲线并不完全相同,并且由于先前的窗口小部件动画将被反向播放,因此你会获得两个未能正确叠加的窗口小部件的块效应,两者之间会有一点变化。

1_WaLZiQOJbxOo79UVhb8prg
几帧动画…

为避免这种情况,我们必须使用曲线的翻转属性

switchOutCurve: Curves. ***easeInBack*** . **flipped** ,

1_8ZmJHWo8kitJVRZbzLKJsg
Sloooow Mooooo…(现在很完美)

结论

如你所见,这并不难:我仅用一个属性(小部件的显示面)以及大约30行代码(仅动画)就制作出了此动画。

我认为为此创建一个软件包并不明智。在代码中添加依赖项意味着,如果它不适用于版本更新(例如),那你的项目将被暂停一段时间。更糟糕的是,如果不再保持依赖关系,你将无法保证你的项目会在未来6个月、1或2年内被正确编译…

%E6%9C%AA%E5%91%BD%E5%90%8D_%E5%89%AF%E6%9C%AC
适当复制并粘贴此示例

因此,随时使用我的代码。我不喜欢复制粘贴,所以我会说“适当使用我的代码”,意思其实是复制粘贴、 理解 它并根据你的需要对其进行 修改

你可以在这里找到本文中使用的演示:

GONZALEZD / flutter_demos
一个新的Flutter项目。该项目是Flutter应用程序的起点。一些资源可以帮助您入门…

github.com

原文作者 David Gonzalez
原文链接 https://medium.com/flutter-community/flutter-flip-card-animation-eb25c403f371

推荐阅读
作者信息
AgoraTechnicalTeam
TA 暂未填写个人简介
文章
117
相关专栏
开源技术
64 文章
本专栏仅用于分享音视频相关的技术文章,与其他开发者和 Agora 研发团队交流、分享行业前沿技术、资讯。发帖前,请参考「社区发帖指南」,方便您更好的展示所发表的文章和内容。