
                            
                            因收到Google相关通知，网站将会择期关闭。相关通知内容
                            
                            
                            10 动画使用与状态周期
                            1. 什么是动画

接下来，我们将进入猜数字项目的最后一个模块：动画的使用。上一节末尾说了，此处使用动画的目的是 增加交互反馈效果 。动画本质上就是不断更新界面展示的内容，玩过翻页动画的感触会深一些:


在每页纸上绘制连续的动作，手快速翻动时，内容快速变化，形成连续运动的动画效果。




这里根据翻页动画，先统一给定几个概念描述，方便后续的表述:


动画帧 : 一页纸上的内容。
动画时长 : 从开始翻看，到结束的时间差。
帧率 : 动画过程中，每秒钟包含动画帧的个数。
动画控制动器: 动画进行的动力来源，比如翻页动画中的手。




其实对于界面来说也是类似的，屏幕上展示的内容不断变化，给人视觉上的动画效果。对于界面编程来说，动画一般都是改变某些属性值；比如这里是对中间文字的大小进行动画表现：




标题













我们之前将提示信息的界面封装成 ResultNotice 组件进行展示，现在想要修改面板的展示效果，只要对该组件进行优化即可。可以很快定位到 result_notice.dart, 这也是各司其职的一个好处。下面就来看一下，让文字进行动画变化的流程。





2. 动画控制器的创建

要进行动画，首先要找到 驱动力， 也就是翻页的那只手怎么得到。Flutter 框架层对这只手 (Ticker) 进行了封装，给出一个更易用的 AnimationController 类型。想创该类型对象需要两步：


1. 一般在 State 派生类中创建 AnimationController 对象，使用这里将 ResultNotice 改为继承自 StatefulWidget ：


class ResultNotice extends StatefulWidget {
  final Color color;
  final String info;

  const ResultNotice({
    Key? key,
    required this.color,
    required this.info,
  }) : super(key: key);

  @override
  State<ResultNotice> createState() => _ResultNoticeState();
}

class _ResultNoticeState extends State<ResultNotice>{
   //...
}



1. 将状态类通过 with 关键字混入 SingleTickerProviderStateMixin, 让状态类拥有创建 Ticker 的能力。这样在 AnimationController 构造方法中 vsync 入参就可以传入当前状态类。
 对于新手来说，这可能比较难理解，可以先记成固定的流程。不用太纠结，以后有能力时，可以在动画小册中探索更深层的原理。


class _ResultNoticeState extends State<ResultNotice> with SingleTickerProviderStateMixin{

  late AnimationController controller;

  @override
  void initState() {
    super.initState();
    controller = AnimationController(
      vsync: this, 
      duration: const Duration(milliseconds: 200),
    );
  }


这样动画控制器对象就在创建完毕了，在创建对象时 duration 入参用于控制动画时长。默认情况下，动画控制器的值会在指定时长内，从 0 匀速变化到 1 。 下面来看一下如何通过动画控制器，来驱动字号大小的变化。



3. 动画构造器 AnimatedBuilder 的使用

动画本身就决定它需要频繁地变化，但很多时候我们只需要局部一小部分进行动画，比如这里只针对于文字。对于这种频繁变化的场景，最好尽可能小地进行重新构建。这里推荐新手使用 AnimatedBuilder 组件，可以非常方便地处理局部组件的动画变化。 由于需要动画的只是文字，所步骤如下：


将 AnimatedBuilder 套在 Text 组件之上。
将动画控制器作为 animation 入参。
将需要动画变化的属性值，根据 animation.value 进行计算即可。




刚才说过，默认情况下，动画控制器启动之后，它的值会在指定时长内，从 0 匀速变化到 1。所以，这里 fontSize 会从 0 匀速变到 54 。


小思考: 通过简单的数学知识，思考一下如何让 fontSize 从 12 ~ 54 匀速变化。




4. 状态类的生命周期回调方法

这里介绍一下 State 派生类常用的几个生命周期回调方法；生命周期 顾名思义就是对象从生到死的过程，回调就是生命中特定时机的触发点；回调是 Flutter 框架中触发的，派生类可以通过 覆写 的方式，来感知某个特定时机。

比如，我们一般在 initState 回调中处理状态类中成员对象的初始化；如下这里对 controller 对象的初始化。在创建后可以通过 controller.forward 方法，启动动画器，让数值运动：

@override
void initState() {
  super.initState();
  controller = AnimationController(
    vsync: this,
    duration: const Duration(milliseconds: 200),
  );
  controller.forward();
}




既然有生，就必然有死。当状态类不再需要时，其中持有的一些资源需要被释放，必然动画控制器。这时可以通过 dispose 回调监听到状态销毁的时机：

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




人除了生和死，就是工作，状态类也是一样。而状态类最重要的工作就是 build 方法构建 Widget 组件，它也是生命周期回调中的一环：

@override
Widget build(BuildContext context) {
  // 略同...
}


生和死都是只会触发一次，而工作是每天都要进行。所以，框架中对 State 对象的 initState 和 dispose 只会触发一次， build 方法可能触发多次。



现在一个问题，由于 controller 只会在 initState 触发一次，所以两次校验结果相同，状态类还活着，也只能进行一次动画。状态类如何监听到更新信息呢? 答案也是生命周期回调：


当上级状态触发 setState 时，会触发子级的 didUpdateWidget 生命回调


代码中在点击按钮时，会触发 setState , 所以 _ResultNoticeState 里可以覆写 didUpdateWidget 获得点击的时机，在此触发 controller 的 forward 进行动画。

如果 ResultNotice 提供了动画时长的参数，如果外界需要修改动画时长，而外界无法直接访问状态类。就可以通过 didUpdateWidget 来 间接 修改动画控制器的时长。其中 oldWidget 是之前的组件配置信息，另外最新的组件信息是 widget 成员，可以比较两者时长是否不同，对动画控制器进行修改。

---->[result_notice.dart]----
@override
void didUpdateWidget(covariant ResultNotice oldWidget) {
  controller.forward(from: 0);
  super.didUpdateWidget(oldWidget);
}


didUpdateWidget 可能对新手来说比较难理解，它提供了外界更新时机的回调，并根据新旧组件配置，来维护 状态类内部数据 。 在 State 的生命之中也可以被调用多次。


当前代码提交位置: result_notice.dart


initState 、build 、didUpdateWidget、dispose 三者是最基础的 State 生命周期回调。除此之外还有几个回调，不过对于新手来说并不重要，以后有能力时可以通过渲染机制小册，从源码的角度去了解它们。



5. Statless Or Statful

对于新手而言，面临的一个非常难的问题就是，我该选择 StatelessWidget 还是 StatefulWidget 。 这其实要取决于你对需求的理解，以及对组件的封装思路：比如这里 ResultNotice，由于想让它进行动画，而动画控制器的控制和维护需要在状态类中处理，所以就选择了 StatefulWidget 。

但这并不是绝对的，因为上面选择 StatefulWidget 本质上就是由于 动画控制器 对象。那 ResultNotice 直接摆烂，由构造函数传入动画控制器。这就相当于动画控制器由外界维护，此时 ResultNotice 就可以是 StatelessWidget。


大家可以细品一下 StatefulWidget 变为 StatelessWidget 的过程。先自己思考一下两者的差异，后面我会进行分析。


class ResultNotice extends StatelessWidget {
  final Color color;
  final String info;
  final AnimationController controller;

  const ResultNotice({
    Key? key,
    required this.color,
    required this.info,
    required this.controller,
  }) : super(key: key);
  
  @override
  Widget build(BuildContext context) {
    return Expanded(
      child: Container(
        alignment: Alignment.center,
        color: color,
        child: AnimatedBuilder(
          animation: controller,
          builder: (_, child) => Text(
            info,
            style: TextStyle(
              fontSize: 54 * (controller.value),
              color: Colors.white,
              fontWeight: FontWeight.bold,
            ),
          ),
        ),
      ),
    );
  }
}



当前代码提交位置: result_notice.dart




俗话说，冤有头，债有主 。只要还想进行动画，控制器就需要有一个状态类做接盘侠(维护者)。由于 ResultNotice 已经甩锅了，那这里就需要上层状态 _GuessPageState 来维护：



这时，在构建 ResultNotice 时，传入控制器即可：

Column(
  children: [
    if(_isBig!)
    ResultNotice(color:Colors.redAccent,info:'大了',controller: controller,),
    Spacer(),
    if(!_isBig!)
    ResultNotice(color:Colors.blueAccent,info:'小了',controller: controller,),
  ],
),


最后在点击时触发动画器：





现在就 ResultNotice 来分析一下 StatlessWidget 和 StatefulWidget 在使用上的差异性。


ResultNotice 为 StatefulWidget 时，外界使用者无需和 AnimationController 就能进行动画。也就是说将动画控制器的逻辑封装到了内部，拿来即用，用起来简洁方便。
ResultNotice 为 StatlessWidget 时，外界使用者需要主动维护 AnimationController 对象，使用门槛较高。另外，由于使用者主动掌握控制器，可以更灵活地操作。
之前是静态的界面，现在想要进行动画，对于功能拓展来说，使用 StatefulWidget 来独立维护状态的变化。可以在不修改之前其他代码的前提下，完成需求。


好用 和 灵活 是一组矛盾，封装度越高，使用者操心的事就越少，用起来就越好用。但同时想要修改封装体内部的细节就越麻烦，灵活性就越差。所以，没有什么真正的好与坏，只有场景的适合于不适合。在面临选择时，要想一下：


你是只想看电视的用户，还是以后电视开膛破肚的维修也要自己做。


Flutter 中内置的很多 StatefulWidget 组件，我们就是使用者。比如点击按钮有水波纹的变化、点击 Switch 有滑动效果等，这些内部的状态变化逻辑是不用我们操心的。作为使用者可以非常轻松地完成复杂的交互效果，这就是封装的优势。但同时，如果需求的表现有一点不符合，改框架源码将会非常复杂，门槛也很高，这就是封装的劣势。对于选取 StatfulWidget 我的标准是:


当前组件展示区域，在交互中需要改变内容；且外界无需在意内部状态数据。


对于 ResultNotice 来说， StatlessWidget 或 StatefulWidget 差别并不是非常大，只是 AnimationController 交给谁维护的问题。每种方式都有好处，也有坏处，但都可以实现需求。所以结合场景，选取你觉得更好的即可。对于新手而言，能完成需求是第一要务，至于如何更优雅，你可以在以后的路中慢慢揣摩。



6.本章小结

本章主要介绍了 Flutter 中使用动画的方式和步骤，并简单了解了一下状态类的生命周期回调方法。最后分析了一个对于新手而言比较重要的话题 StatlessWidget 和 StatefulWidget 的差异性。

到这里，我们的第一个猜数字小案例的全部功能就实现完毕了。从中可以了解很多 Flutter 的基础知识，在下一篇中将对猜数字的小案例进行一个总结，看看现在我们已经用到了哪些知识，以及当前代码还有哪些优化的空间。

                        
                        
                            