Flutter動(dòng)畫(huà) 3 - Animation動(dòng)畫(huà)組


簡(jiǎn)述


在前面的兩篇博客中,我們了解了在Flutter中動(dòng)畫(huà)如何簡(jiǎn)單的使用動(dòng)畫(huà),了解Tween相關(guān)使用方法.但是在很多場(chǎng)景下,我們使用的并不是單單一種動(dòng)畫(huà),而是多種動(dòng)畫(huà)一起執(zhí)行或者順序執(zhí)行,那么在應(yīng)對(duì)這樣的場(chǎng)景我們?cè)撛趺崔k呢? 今天,我們就聊一聊如何在Flutter中實(shí)現(xiàn)這種并行動(dòng)畫(huà)或者串行動(dòng)畫(huà)呢?接下來(lái)我們就一起看看這兩種形式的動(dòng)畫(huà)是如何實(shí)現(xiàn)的.


并行動(dòng)畫(huà)


對(duì)于并行動(dòng)畫(huà)這種多個(gè)動(dòng)畫(huà)同時(shí)執(zhí)行,我們需要讓各個(gè)動(dòng)畫(huà)Animation的動(dòng)畫(huà)控制器AnimationController保持一致就可以了. 這個(gè)在上一篇Tween已經(jīng)進(jìn)行了對(duì)應(yīng)說(shuō)明演示.這里就直接把代碼拿來(lái)了.

首先創(chuàng)建動(dòng)畫(huà)并保持動(dòng)畫(huà)控制器的統(tǒng)一性.示例如下代碼所示.

    _animationController = AnimationController(duration: Duration(milliseconds: 300), vsync: this);
    _animation = Tween<double>(begin: 0, end: 50).animate(_animationController)
      ..addListener(() {
        setState(() {});
      });
    _colorAnimation = ColorTween(begin: Colors.orangeAccent, end: Colors.redAccent).animate(_animationController)
      ..addListener(() {
        setState(() {});
      });

然后再構(gòu)建build方法中直接使用_animation和_colorAnimation的動(dòng)畫(huà)值就好,具體代碼如下所示.

  Container(
    width: 200,
    height: 50,
    color: _colorAnimation.value,
    margin: EdgeInsets.only(top: _animation.value),
  ),


串行動(dòng)畫(huà)


相對(duì)于并行動(dòng)畫(huà)而言,串行動(dòng)畫(huà)寫(xiě)起來(lái)就比較復(fù)雜的多,串行動(dòng)畫(huà)的實(shí)現(xiàn)方案總共有三種,分別是 監(jiān)聽(tīng)狀態(tài)法, Interval時(shí)間間隔法, TweenSequence動(dòng)畫(huà)序列法.下面我們就分別來(lái)看看三種方法的實(shí)現(xiàn)以及區(qū)別.


監(jiān)聽(tīng)狀態(tài)法

狀態(tài)監(jiān)聽(tīng)法主要通過(guò)AnimationController監(jiān)聽(tīng)動(dòng)畫(huà)的completed狀態(tài),然后再去執(zhí)行下一個(gè)動(dòng)畫(huà),如此往復(fù),直到所有動(dòng)畫(huà)完成.

例如現(xiàn)在我們需要實(shí)現(xiàn)先執(zhí)行組件在0.3秒鐘往下偏移50個(gè)單位,然后再執(zhí)行在0.6s中組件的顏色由 橘色 變?yōu)?紅色.

首先,我們先聲明位移動(dòng)畫(huà)控制器和顏色動(dòng)畫(huà)控制器以及位移動(dòng)畫(huà)和顏色動(dòng)畫(huà),代碼如下所示.

  AnimationController _animationController;
  Animation<double> _animation;
  AnimationController _colorAnimationController;
  Animation<Color> _colorAnimation;

然后,我們創(chuàng)建位移、顏色的動(dòng)畫(huà)控制器和動(dòng)畫(huà),具體代碼如下所示.

    _animationController = AnimationController(duration: Duration(milliseconds: 300), vsync: this);
    _animation = Tween<double>(begin: 0, end: 50).animate(_animationController)
      ..addListener(() {
        setState(() {});
      });
    _colorAnimationController = AnimationController(duration: Duration(milliseconds: 600), vsync: this);
    _colorAnimation = ColorTween(begin: Colors.orangeAccent, end: Colors.redAccent).animate(_colorAnimationController)
      ..addListener(() {
        setState(() {});
      });

最后,我們只需要監(jiān)聽(tīng)位移動(dòng)畫(huà)完成狀態(tài)之后執(zhí)行顏色動(dòng)畫(huà)即可,具體代碼如下所示.

    _animationController.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        _colorAnimationController.forward();
      };
    });

整體Demo代碼如下所示.

class _FlutterAnimationWidgetState extends State<FlutterAnimationWidget> with TickerProviderStateMixin {
  AnimationController _animationController;
  Animation<double> _animation;
  AnimationController _colorAnimationController;
  Animation<Color> _colorAnimation;

  @override
  void initState() {
    super.initState();
    _animationController = AnimationController(duration: Duration(milliseconds: 300), vsync: this);
    _animation = Tween<double>(begin: 0, end: 50).animate(_animationController)
      ..addListener(() {
        setState(() {});
      });
    _colorAnimationController = AnimationController(duration: Duration(milliseconds: 600), vsync: this);
    _colorAnimation = ColorTween(begin: Colors.orangeAccent, end: Colors.redAccent).animate(_colorAnimationController)
      ..addListener(() {
        setState(() {});
      });
    _animationController.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        _colorAnimationController.forward();
      };
    });
  }

  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: _colorAnimation.value,
              margin: EdgeInsets.only(top: _animation.value),
            ),
            FlatButton(
              onPressed: startEasyAnimation,
              child: Text(
                "點(diǎn)擊執(zhí)行最簡(jiǎn)單動(dòng)畫(huà)",
                style: TextStyle(color: Colors.black38),
              ),
            ),
          ],
        ),
      ),
    );
  }
}


Interval時(shí)間間隔法

上面的狀態(tài)監(jiān)聽(tīng)需要一個(gè)動(dòng)畫(huà)過(guò)程就寫(xiě)一個(gè)Controller,而且基本上還要每一個(gè)Controller都監(jiān)聽(tīng)執(zhí)行完成然后再去啟動(dòng)下一個(gè)Controller.如果一個(gè)動(dòng)畫(huà)過(guò)程有十幾個(gè),自己想想都是腦瓜子嗡嗡的.所以接下來(lái)我們就來(lái)介紹第二種方案 - Interval時(shí)間間隔法 .

Interval時(shí)間間隔法 的整體思路是一個(gè)動(dòng)畫(huà)Controller控制所有動(dòng)畫(huà)的執(zhí)行.然后每一個(gè)動(dòng)畫(huà)只需要確認(rèn)自己在整個(gè)動(dòng)畫(huà)的時(shí)間比重即可.

首先,聲明一個(gè)動(dòng)畫(huà)Controller和多個(gè)動(dòng)畫(huà).

  AnimationController _animationController;
  Animation<double> _animation;
  Animation<Color> _colorAnimation;

然后初始化AnimationController,AnimationController的動(dòng)畫(huà)時(shí)間(duration)要設(shè)置成所有動(dòng)畫(huà)的總時(shí)長(zhǎng),例如這里我設(shè)定為600毫秒(_animation時(shí)長(zhǎng):300毫秒,_colorAnimation時(shí)長(zhǎng):300毫秒).

    _animationController = AnimationController(duration: Duration(milliseconds: 600), vsync: this);

接下來(lái)就是初始化兩個(gè)Animation,Tween對(duì)象調(diào)用animate()函數(shù)不再是直接傳入上面的AnimationController,而是傳入一個(gè) CurvedAnimation 對(duì)象. CurvedAnimation構(gòu)建過(guò)程中需要傳入兩個(gè)參數(shù)一個(gè)是 parent ,用于指定AnimationController. 另外一個(gè)是 curve,用于指定動(dòng)畫(huà)曲線函數(shù).我們可以使用常用的動(dòng)畫(huà)曲線函數(shù),也可以自己生成,這里我們就自己生成.指定動(dòng)畫(huà)執(zhí)行的時(shí)間區(qū)間.

  // CurvedAnimation的構(gòu)建方法
  CurvedAnimation({
    required this.parent,
    required this.curve,
    this.reverseCurve,
  }) : assert(parent != null),
       assert(curve != null) {
    _updateCurveDirection(parent.status);
    parent.addStatusListener(_updateCurveDirection);
  }

由于兩個(gè)動(dòng)畫(huà)時(shí)間長(zhǎng)度是對(duì)分的,每一個(gè)都是300毫秒,所以 curve 參數(shù)中的值就分別是 Interval(0.0, 0.5) 、Interval(0.5, 1.0),兩個(gè)Animation的初始化過(guò)程如下所示.

  _animation = Tween<double>(begin: 0, end: 50).animate(
    CurvedAnimation(
      parent: _animationController,
      curve: Interval(0.0, 0.5),
    ),
  )..addListener(() {
      setState(() {});
  });

  _colorAnimation = ColorTween(begin: Colors.orangeAccent, end: Colors.redAccent).animate(
    CurvedAnimation(
      parent: _animationController,
      curve: Interval(0.5, 1.0),
    ),
  )..addListener(() {
      setState(() {});
  });

整體Demo代碼如下所示.

class _FlutterAnimationWidgetState extends State<FlutterAnimationWidget> with TickerProviderStateMixin {
  AnimationController _animationController;
  Animation<double> _animation;
  Animation<Color> _colorAnimation;

  @override
  void initState() {
    super.initState();
    _animationController = AnimationController(duration: Duration(milliseconds: 600), vsync: this);
    _animation = Tween<double>(begin: 0, end: 50).animate(
      CurvedAnimation(
        parent: _animationController,
        curve: Interval(0.0, 0.5),
      ),
    )..addListener(() {
        setState(() {});
      });
    _colorAnimation = ColorTween(begin: Colors.orangeAccent, end: Colors.redAccent).animate(
      CurvedAnimation(
        parent: _animationController,
        curve: Interval(0.5, 1.0),
      ),
    )..addListener(() {
        setState(() {});
      });
  }

  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: _colorAnimation.value,
              margin: EdgeInsets.only(top: _animation.value),
            ),
            FlatButton(
              onPressed: startEasyAnimation,
              child: Text(
                "點(diǎn)擊執(zhí)行最簡(jiǎn)單動(dòng)畫(huà)",
                style: TextStyle(color: Colors.black38),
              ),
            ),
          ],
        ),
      ),
    );
  }
}


TweenSequence動(dòng)畫(huà)序列法

上面的兩種方案雖然能解決動(dòng)畫(huà)組的問(wèn)題,但是都太過(guò)于繁瑣,那么有沒(méi)有一種比較優(yōu)雅的方案呢?這就需要使用到 TweenSequenceTweenSequenceItem 這兩個(gè)類(lèi)了. 其中 TweenSequence 是動(dòng)畫(huà)組類(lèi),TweenSequenceItem 則是用來(lái)定義每一個(gè)動(dòng)畫(huà)的具體實(shí)現(xiàn)的類(lèi).但是TweenSequenceTweenSequenceItem也不是盡善盡美的,它最大的問(wèn)題就是前后變化的屬性值類(lèi)型必須是一致的.

下面,我們?nèi)匀灰愿淖僊argin為例, 先讓視圖組件往上移動(dòng)50,再讓視圖 來(lái)看看如何使用 TweenSequenceTweenSequenceItem 來(lái)實(shí)現(xiàn)這個(gè)動(dòng)畫(huà).

首先,我們聲明一個(gè) 動(dòng)畫(huà)控制器AnimationController 和 動(dòng)畫(huà)Animation.

  AnimationController _animationController;
  Animation<double> _animation;

仍然以兩者的動(dòng)畫(huà)總時(shí)長(zhǎng)為600毫秒,所以我們需要實(shí)現(xiàn)AnimationController,如下所示.

_animationController = AnimationController(duration: Duration(milliseconds: 600), vsync: this);

然后,我們通過(guò) TweenSequenceTweenSequenceItem 這兩個(gè)類(lèi)對(duì) 動(dòng)畫(huà)Animation 進(jìn)行實(shí)現(xiàn).

實(shí)現(xiàn)兩個(gè) TweenSequenceItem, TweenSequenceItem中的 <font color=red>weight</font> 屬性是來(lái)設(shè)定動(dòng)畫(huà)執(zhí)行的時(shí)間權(quán)重.也就是在整個(gè)動(dòng)畫(huà)過(guò)程,當(dāng)前動(dòng)畫(huà)執(zhí)行時(shí)長(zhǎng)占總時(shí)長(zhǎng)的比例.例如下面 第一個(gè)動(dòng)畫(huà)插值占的時(shí)間比例為 50/(50 + 100). 第二個(gè)動(dòng)畫(huà)插值占的時(shí)間比例為 100/(50 + 100) .


    TweenSequenceItem downMarginItem = TweenSequenceItem<double>(
      tween: Tween(begin: 1.0, end: 50.0),
      weight: 50,
    );
    TweenSequenceItem upMarginItem = TweenSequenceItem<double>(
      tween: Tween(begin: 50.0, end: 100.0),
      weight: 100,
    );

然后創(chuàng)建一個(gè)動(dòng)畫(huà)插值組,把上面兩個(gè)動(dòng)畫(huà)插值放入組中.

    TweenSequence tweenSequence = TweenSequence<double>([
      downMarginItem,
      upMarginItem,
    ]);

最后,生成動(dòng)畫(huà)就OK了.

    _animation = tweenSequence.animate(_animationController);
    _animation.addListener(() {
      setState(() {});
    });

當(dāng)然了,上面的三步可以縮寫(xiě)成一步代碼來(lái)實(shí)現(xiàn),我只是進(jìn)行了分解代碼來(lái)說(shuō)明每一個(gè)代碼.

      // 縮寫(xiě)代碼
    _animation = TweenSequence<double>([
      TweenSequenceItem<double>(
        tween: Tween(begin: 0.0, end: 50.0),
        weight: 50,
      ),
      TweenSequenceItem<double>(
        tween: Tween(begin: 50.0, end: 100.0),
        weight: 100,
      ),
    ]).animate(_animationController)
      ..addListener(() {
        setState(() {});
      });

整體代碼如下所示.

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

  @override
  void initState() {
    super.initState();
    _animationController = AnimationController(duration: Duration(milliseconds: 600), vsync: this);
    TweenSequenceItem downMarginItem = TweenSequenceItem<double>(
      tween: Tween(begin: 1.0, end: 50.0),
      weight: 50,
    );
    TweenSequenceItem upMarginItem = TweenSequenceItem<double>(
      tween: Tween(begin: 50.0, end: 100.0),
      weight: 100,
    );
    TweenSequence tweenSequence = TweenSequence<double>([
      downMarginItem,
      upMarginItem,
    ]);
    _animation = tweenSequence.animate(_animationController);
    _animation.addListener(() {
      setState(() {});
    });
  }

  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: _animation.value),
            ),
            FlatButton(
              onPressed: startEasyAnimation,
              child: Text(
                "點(diǎn)擊執(zhí)行最簡(jiǎn)單動(dòng)畫(huà)",
                style: TextStyle(color: Colors.black38),
              ),
            ),
          ],
        ),
      ),
    );
  }
}


動(dòng)畫(huà)組實(shí)現(xiàn)總結(jié)

上面三種實(shí)現(xiàn)動(dòng)畫(huà)組基本上已經(jīng)說(shuō)完了,接下來(lái)我們就來(lái)對(duì)比其不同點(diǎn).

特性 監(jiān)聽(tīng)狀態(tài)法 Interval時(shí)間間隔法 TweenSequence動(dòng)畫(huà)序列法
代碼簡(jiǎn)潔度 ???? ?????? ??????????
動(dòng)畫(huà)是否可交織 ? ? ?
動(dòng)畫(huà)屬性是否可以多變 ? ? ?

動(dòng)畫(huà)是否可交織 : 動(dòng)畫(huà)可否交織主要是說(shuō)兩個(gè)動(dòng)畫(huà)之間是否需要上一個(gè)動(dòng)畫(huà)完全執(zhí)行完成之后,下一個(gè)動(dòng)畫(huà)才能執(zhí)行.

動(dòng)畫(huà)屬性是否可以多變 : 動(dòng)畫(huà)屬性多變是指當(dāng)前動(dòng)畫(huà)過(guò)程中可變化的屬性是否可以有多個(gè),例如同時(shí)變化尺寸和顏色等等.


結(jié)語(yǔ)


OK,如何使用Flutter實(shí)現(xiàn)串行動(dòng)畫(huà)和并行動(dòng)畫(huà)就說(shuō)道這里,下一篇就說(shuō)一下在轉(zhuǎn)場(chǎng)動(dòng)畫(huà)比較常見(jiàn)的飛入飛出動(dòng)畫(huà) - Hero動(dòng)畫(huà),歡迎持續(xù)關(guān)注騷棟,有任何問(wèn)題歡迎聯(lián)系騷棟.


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容