Animation深度挖掘

158 阅读5分钟

在本系列的其他几集中,我的同事们讨论了在Flutter中构建动画的所有实用方法。在我的节目里不是这样。在这里,您将学习如何以最不实用的方式实现动画。(但是,你也会学到一些东西。) 让我们从简单轻松的事情开始:

什么是运动,真的? 

 你看,运动是一种错觉。看看这个: 图像用于postImage for post 菲利普挥手的视频。 这是个谎言。你实际看到的是很多连续不断的静止图像。电影就是这样工作的。在电影院里,单个的图片被称为frame,因为数字屏幕的工作原理类似,所以在这里也被称为相框。电影院通常每秒放映24帧。现代数字设备显示每秒60到120帧。 那么,如果运动是个谎言,那么这些AnimationFoo和foottransition小部件到底在做什么呢?当然,因为帧每秒需要构造120次,所以UI不能每次都重建。

或者,可以吗? 

实际上,Flutter中的动画只是一种在每一帧上重建widget树的一部分的方法。没有特殊情况。颤振足够快来做到这一点。 让我们看看颤振动画的一个组成部分:AnimatedBuilder。此小部件是一个AnimatedWidget,由 AnimatedState支持。在State的initState()方法中,我们监听动画(或Listenable,这里称之为Listenable),当动画更改其值时,我们…调用setState()。

给你。Flutter中的动画只是一个快速的连续改变一些小部件的状态,每秒60到120次。 我可以证明。这是一个从零到光速的动画。虽然它改变了每一帧的文本,但从Flutter的角度来看,它只是另一个动画。

让我们使用Flutter的动画框架,从第一原理构建动画。 通常,我们将使用TweenAnimationBuilder小部件或类似的部件,但在本文中,我们将忽略所有这些,并使用ticker、controller和setState。

Ticker

我们先谈谈Ticker。99%的情况下,你不会直接使用Ticker代码。但是,我认为谈论它还是有帮助的,即使只是为了解开它的神秘感。 ticker是为每一帧调用一个函数的对象。

var ticker = Ticker((elapsed) => print('hello'));
ticker.start();

在本例中,我们将在每一帧中打印“hello”。诚然,这不是很有用。 还有,我们忘了调用ticker.dispose(),所以现在我们的ticker 将永远持续下去,直到我们杀死这个应用程序。 这就是为什么Flutter为您提供了SingleTickerProviderStateMixin,这是您在前面的一些视频中看到的名称恰当的mixin。 这个mixin解决了管理Ticker代码的麻烦。只要把它放在你的widget的状态上,现在你的状态就是一个TickerProvider。

class _MyWidgetState extends State<MyWidget>
     with SingleTickerProviderStateMixin<MyWidget> {
  @override  Widget build(BuildContext context) {
    return Container();
  }
}

这意味着Flutter框架可以向您的州请求一个Ticker代码。最重要的是,AnimationController可以向状态请求一个ticker。

class _MyWidgetState extends State<MyWidget>
    with SingleTickerProviderStateMixin<MyWidget> {
  AnimationController _controller;
  @override
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this);
  }
  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

AnimationController需要一个ticker才能正常工作。如果使用SingleTickerProviderStateMixin或它的同级TickerProviderStateMixin,只需将它交给AnimationController,就可以完成了。

AnimationController

AnimationController通常用于播放、暂停、反转和停止动画。而不是纯粹的“嘀嗒”事件,AnimationController告诉我们在动画的哪个点,在任何时候。例如,我们到一半了吗?我们99%都在吗?我们完成动画了吗?

通常情况下,您可以使用AnimationController,也许可以用曲线对其进行变换,将其穿过Tween,然后在FadeTransition或TweenAnimationBuilder之类的小部件中使用它。但是,出于教育目的,我们不要这样做。相反,我们将直接调用setState。

setState

初始化AnimationController之后,我们可以向其添加侦听器。在那个侦听器中,我们称之为setState。

class _MyWidgetState extends State<MyWidget>
    with SingleTickerProviderStateMixin<MyWidget> {
  AnimationController _controller;
  @override
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this);
    _controller.addListener(_update);
  }
  void _update() {
    setState(() {
      // TODO
    });
  }
  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

现在,我们应该有一个状态来设置。让我们用一个整数保持简单。我们不要忘记在构建方法中实际使用状态,并根据控制器的当前值更改侦听器中的状态。

class _MyWidgetState extends State<MyWidget>
    with SingleTickerProviderStateMixin<MyWidget> {
  AnimationController _controller;
  int i = 0;
  @override
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this);
    _controller.addListener(_update);
  }  void _update() {
    setState(() {
      i = (_controller.value * 299792458).round();
    });
  }
  @override
  Widget build(BuildContext context) {
    return Text('$i m/s');
  }
}

此代码根据动画的进度为光速指定一个从零到光速的值。 

 运行动画 

 现在,我们只需要告诉动画需要多长时间才能完成,然后开始动画。

class _MyWidgetState extends State<MyWidget>
    with SingleTickerProviderStateMixin<MyWidget> {
  AnimationController _controller;
  int i = 0;
  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
       duration: const Duration(seconds: 1),
    );
    _controller.addListener(_update);
    _controller.forward();
  }
  void _update() {
    setState(() {
      i = (_controller.value * 299792458).round();
    });
  }
  @override
  Widget build(BuildContext context) {
    return Text('$i m/s');
  }
}

这个小部件一旦被添加到屏幕上,就会显示动画。它能在一秒钟内从零到光速“动画化”。

处置控制器

别忘了处理动画控制器。否则你的应用程序内存泄漏。

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

也许只是使用一个内置的小部件?

正如你所见,一个人做这一切并不好。使用TweenAnimationBuilder可以用更少的代码行来实现相同的功能,而且不必处理AnimationController和调用setState。

class MyPragmaticWidget extends StatelessWidget {
  @override  Widget build(BuildContext context) {
    return TweenAnimationBuilder(
      tween: IntTween(begin: 0, end: 299792458),
      duration: const Duration(seconds: 1),
      builder: (BuildContext context, int i, Widget child) {
        return Text('$i m/s');
      },
    );
  }
}

总结  

我们看到了什么是Ticker行为。我们看到了如何手动监听AnimationController。而且,我们看到,在基本层次上,动画只是快速、连续地重建小部件。你可以在任何帧上做任何你想做的事。