Flutter入门:动画相关

动画

在flutter中,如果想让某个widget执行动画,需要用一个动画类的widget封装一下,比如一个图片

Center(
    child: Image.asset(xxxxxx),
),

想实现透明度动画,使用FadeTransition,如下

Center(
  child: FadeTransition(
    opacity: _speakAnimation,
    child: Image.asset(R.assetsLiveSpeak),
  ),
),

关键是opacity,是一个Animation,要想实现一个动画,还需要一个Tween和一个AnimationController。

Tween就是Animation。Tween还需要一个AnimationController,他们的创建及定义如下:

class _XXX extends State<XXXX>  with SingleTickerProviderStateMixin{
  AnimationController _speakController;
  Animation _speakAnimation;
  
  @override
  void initState() {
    super.initState();
    _speakController = AnimationController(
      vsync: this,
      duration: Duration(seconds: 2),
    );
    _speakAnimation = Tween(
      begin: 1.0,
      end: 0.0,
    ).animate(_speakController);
    _speakAnimation.addStatusListener((status) {   //监听动画
      if(status == AnimationStatus.completed){
        //todo
      }
    });
  }
  ...

可以看到Tween定义的是动画的起始和结束状态(这里就是透明度的值)。而AnimationController主要是定义时长。

另外Tween可以添加动画监听(addStatusListener),一共有四种状态

enum AnimationStatus {
  /// The animation is stopped at the beginning.
  dismissed,

  /// The animation is running from beginning to end.
  forward,

  /// The animation is running backwards, from end to beginning.
  reverse,

  /// The animation is stopped at the end.
  completed,
}

但是这样还差最后一步,启动动画,因为我们需求是页面一展示即播放,所以在build函数中forward一下即可(当然还可以在其他时机播放),代码如下:

class _XXX extends State<XXXX>  with SingleTickerProviderStateMixin{
  AnimationController _speakController;
  Animation _speakAnimation;
  
  @override
  void initState() {
    super.initState();
    _speakController = AnimationController(
      vsync: this,
      duration: Duration(seconds: 2),
    );
    _speakAnimation = Tween(
      begin: 1.0,
      end: 0.0,
    ).animate(_speakController);
    _speakAnimation.addStatusListener((status) {
      if(status == AnimationStatus.completed){
        //todo
      }
    });
  }
  ...

  Widget build(BuildContext context) {
   _speakController.forward(); //播放动画
   return Stack(
    children: <Widget>[
      Center(
        child: FadeTransition(
          opacity: _speakAnimation,
          child: Image.asset(R.assetsLiveSpeak),
        ),
      ),
      ...

动画卡顿

从今天的一个需求说起吧,实现一个按钮呼吸效果,很简单,就是使用一个缩放动画即可,如下:

class _xxx extends State<xxx> with SingleTickerProviderStateMixin{
  AnimationController _animationController;
  Animation _animation;

  @override
  void initState() {
    super.initState();
    _animationController = AnimationController(
      vsync: this,
      duration: Duration(milliseconds: 800),
    );
    _animation = Tween(
      begin: 0.8,
      end: 1.0,
    ).animate(_animationController);
  }


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

  @override
  Widget build(BuildContext context) {
    _animationController.repeat(reverse: true);
    return Stack(
      alignment: Alignment.center,
      children: [
        ...
        Column(
          children: [
            ...
            ScaleTransition(
              scale: _animation,
              child: TextButton(
                onPressed: widget.onOpen,
                child: Container(
                  width: 120,
                  height: 60,
                  padding: EdgeInsets.only(bottom: 8),
                  alignment: Alignment.center,
                  decoration: BoxDecoration(
                      image: DecorationImage(
                          image: AssetImage(R.assetsLiveRedPackageBtn)
                      )
                  ),
                  child: Text(
                    "领取",
                    style: TextStyle(
                        fontSize: 22,
                        fontWeight: FontWeight.bold,
                        color: Color(0xFFB34D35)
                    ),
                  ),
                ),
              ),
            ),
            ...
          ],
        )
      ],
    );
  }
}

动画效果是从0.8倍扩大到1.0,然后reverse,一直repeat即可。

但是运行后发现动画出现了异常,动画扩大到1.0后会快速小幅度缩放一次,然后才会还原到0.8
这明显不是我们想要的动画效果,后来我们尝试了其他动画,都有类似的效果。

但是在其他页面上的动画就不会出问题,所以最后排查发现是该页面的定时器影响到了动画。

这个页面里有一个倒计时,通过Timer来更新其中倒计时的文字,而更新使用了setState进行重绘,这样在动画执行到1秒的时候(扩大到1.0又缩回一点的时候)倒计时更新了,由于是setState,所以动画widget也重绘了(这样又变回1.0大小了),然后才缩回0.8

解决方法就是新增一个StatefulWidget类,将Timer和倒计时相关的组件放到这个类中实现,这样倒计时更新只会刷新这一部分,不会刷新动画组件,如下:

class TimerText extends StatefulWidget{
  int time;
  TimerText(this.time);


  @override
  State<StatefulWidget> createState() {
    return _TimerText();
  }
}

class _TimerText extends State<TimerText>{
  Timer timer;

  @override
  void initState() {
    super.initState();

    timer = Timer.periodic(Duration(seconds: 1), (timer) {
      setState(() {
        if(widget.time <= 0){
          timer.cancel();
        }
        else{
          widget.time--;
        }
      });
    });
  }


  @override
  void dispose() {
    timer.cancel();
    super.dispose();
  }
  @override
  Widget build(BuildContext context) {
    return Text(
      "${widget.time}s",
      style: TextStyle(
          fontSize: 14,
          color: Color(0xFFF9D873)
      ),
    );
  }

}

关注公众号:BennuCTech,获取更多干货!

猜你喜欢

转载自blog.csdn.net/chzphoenix/article/details/122670447
今日推荐