Flutter动画 1 - 实现一个最简单的动画

1,217 阅读4分钟


简述


动画效果会对提升用户体验有着显著效果,一个漂亮的动画效果会让人眼前一亮,反正就是用这舒服,如果没有动画效果的加持,整个App会显的非常生涩难用.对于Flutter的动画,作为小白的我也是最近才刚刚接触,所以这里就说一下Futter中如何使用动画.

这一篇主要是来说如何在Flutter中实现一个最简单的动画,这里假设你已经对Flutter有一些基本的了解或者有一定的开发经验了.


构建基础


这里我们就以控制 Container组件中 margin 为示例.

首先我们先创建一个StatefulWidget有状态组件,同时需要混合 SingleTickerProviderStateMixin,至于 SingleTickerProviderStateMixin 是什么,后面我们会讲到,具体代码如下所示.

class FlutterAnimationWidget extends StatefulWidget {
  @override
  _FlutterAnimationWidgetState createState() => _FlutterAnimationWidgetState();
}

class _FlutterAnimationWidgetState extends State<FlutterAnimationWidget> with SingleTickerProviderStateMixin {
    ****
}

我们接着创建一个受控Container,放在一个纵向布局中,具体如下所示.关于触发按钮的布局这里就不给过叙述了,不是本文的重点.

Container(
  width: 200,
  height: 50,
  color: Colors.orangeAccent,
  margin: EdgeInsets.only(top: 0),
),

构建动画


我们声明动画控制器 _animationController 和margin的top状态属性 _marginTop ,改造Container让margin受控于 _marginTop 的值.具体代码如下所示.

  double _marginTop;
  AnimationController _animationController;
Container(

  ...

  margin: EdgeInsets.only(top: _marginTop),

  ...
),

紧接着我们就需要实现 _animationController 了, 假设我们设定动画运行时间为 300 毫秒,top的移动范围为[0, 50],那么具体代码就如下所示.

_marginTop = 0;
_animationController = AnimationController(duration: Duration(milliseconds: 300), vsync: this)..addListener(() {
  setState(() {
    _marginTop = _animationController.value * 50.0;
  });
});

这里就要对上述代码进行说明一下, 首先是 AnimationController 这个动画控制类,动画控制类中的只能在给定的时间内线性生成 0 → 1 的数值(默认区间),所以我们需要映射成我们想要的数值就需要使用到 value 这个属性映射成我们想要的值,这里就是简单映射成 0 → 50.

那么我们可不可以改动默认区间呢?当然是可以的,这就需要使用到 lowerBoundupperBound 了.那么上面的代码就可以如下示例,两者的效果是一样的.

_animationController = AnimationController(duration: Duration(milliseconds: 300), lowerBound: 0, upperBound: 50, vsync: this)..addListener(() {
  setState(() {
    _marginTop = _animationController.value;
  });
});

那么 代码中 vsync 指向 this,又是什么意思呢?这个简单的来说就是使用Ticker(而不是Timer)来驱动动画会防止屏幕外动画(动画的UI不在当前屏幕时,如锁屏时)消耗不必要的资源.还记得文章一开始状态组件混合 SingleTickerProviderStateMixin 吗?就是这个作用.如果一个组件内存在多个动画控制器,则可以使用 TickerProviderStateMixin .

如下是摘抄 <<Flutter 实战>> 中的对Ticker一段说明.

Flutter应用在启动时都会绑定一个SchedulerBinding,通过SchedulerBinding可以给每一次屏幕刷新添加回调,而Ticker就是通过SchedulerBinding来添加屏幕刷新回调,这样一来,每次屏幕刷新都会调用TickerCallback。使用Ticker(而不是Timer)来驱动动画会防止屏幕外动画(动画的UI不在当前屏幕时,如锁屏时)消耗不必要的资源,因为Flutter中屏幕刷新时会通知到绑定的SchedulerBinding,而Ticker是受SchedulerBinding驱动的,由于锁屏后屏幕会停止刷新,所以Ticker就不会再触发。

对于 addListener() 方法,是给 AnimationController 添加监听回调,并且每一帧都会回调一次.这个我们在里面调用 setState() 方法就能实现对状态组件UI的重建了.

当然了,除了 addListener() 帧监听回调方法之外,还有 addStatusListener() 动画状态的监听,监听的值如下所示.

状态说明
AnimationStatus.dismissed动画从 controller.reverse() 反向执行 结束时会回调此方法
AnimationStatus.forward执行 controller.forward() 会回调此状态
AnimationStatus.reverse执行 controller.reverse() 会回调此状态
AnimationStatus.completed动画从 controller.forward() 正向执行 结束时会回调此方法

使用的话,示例如下所示.

_animationController.addStatusListener((status) { 
  if (status == AnimationStatus.completed) print("动画完成");
});

构建完成动画控制器,但是不要忘了最后需要进行释放操作.代码如下所示.

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

执行动画


指定动画比较简单.主要有如下几个方法.

方法说明
forward()正向开始执行动画
reverse()反向开始执行动画
reset()重置动画到初始状态
dispose()取消/停止动画

这里我们就只需要在触发时间中调用 forward() 就可以了.具体示例如下所示.

void startEasyAnimation() {
  _animationController.forward();
}

示例代码


由于这一篇比较简单,所以我们就把示例代码贴到文章中了,需要的拿走.

class FlutterAnimationWidget extends StatefulWidget {
  @override
  _FlutterAnimationWidgetState createState() => _FlutterAnimationWidgetState();
}

class _FlutterAnimationWidgetState extends State<FlutterAnimationWidget> with TickerProviderStateMixin {
  AnimationController _animationController;
  double _marginTop;

  @override
  void initState() {
    super.initState();
    _marginTop = 0;
    _animationController = AnimationController(duration: Duration(milliseconds: 300), lowerBound: 0, upperBound: 50, vsync: this)..addListener(() {
      setState(() {
        _marginTop = _animationController.value;
      });
    });
    _animationController.addStatusListener((status) {
      if (status == AnimationStatus.completed) print("动画完成");
    });
  }

  void startEasyAnimation() {
    _animationController.forward();
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Container(
              width: 200,
              height: 50,
              color: Colors.orangeAccent,
              margin: EdgeInsets.only(top: _marginTop),
            ),
            FlatButton(
              onPressed: startEasyAnimation,
              child: Text(
                "点击执行最简单动画",
                style: TextStyle(color: Colors.black38),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

结语


OK,使用Flutter实现一个最简单的动画就说道这里了,接下来,骚栋会发布更多Flutter动画相关文章,欢迎关注评论,感谢大家了.