Flutter Animation動畫

一,概述   

  Flutter動畫庫的核心類是Animation對象,它生成指導動畫的值,Animation對象指導動畫的當前狀態(例如,是開始、停止還是向前或者向後移動),但它不知道屏幕上顯示的內容。動畫類型分爲兩類:

  1. 補簡動畫(Tween),定義了開始點和結束點、時間線以及定義轉換時間和速度的曲線。然後由框架計算如何從開始點過渡到結束點。Tween是一個無狀態(stateless)對象,需要beginend值。Tween的唯一職責就是定義從輸入範圍到輸出範圍的映射。輸入範圍通常爲0.0到1.0,但這不是必須的。
  2. 基於物理動畫,運動被模擬與真實世界行爲相似,例如,當你擲球時,它何處落地,取決於拋球速度有多快、球有多重、距離地面有多遠。類似地,將連接在彈簧上的球落下(並彈起)與連接到繩子的球放下的方式也是不同。

  在Flutter中的動畫系統基於Animation對象的。widget可以在build函數中讀取Animation對象的當前值,並且可以監聽動畫的狀態改變。  

二,Flutter動畫介紹

  • Animation

  Animation 是 Flutter 動畫庫中的核心類,它會插入指導動畫生成的值。 Animation 對象知道一個動畫當前的狀態(例如開始、 停止、 播放、 回放), 但它不知道屏幕上繪製的是什麼, 因爲 Animation 對象只是提供一個值表示當前需要展示的動畫, UI 如何繪製出圖形完全取決於 UI 自身如何在渲染和 build() 方法裏處理這個值, 當然也可以不做處理。 Animation<double>是一個比較常用的Animation類, 泛型也可以支持其它的類型,比如: Animation<Color>或 Animation<Size>。 Animation 對象就是會在一段時間內依次生成一個區間之間值的類, 它的輸出可以是線性的、曲線的、一個步進函數或者任何其他可以設計的映射 比如:CurvedAnimation

  • AnimationController

  AnimationController 是一個動畫控制器, 它控制動畫的播放狀態, 如例子裏面的: controller.forward()就是控制動畫"向前"播放。 所以構建 AnimationController 對象之後動畫並沒有立刻開始執行。 在默認情況下, AnimationController 會在給定的時間內線性地生成從 0.0 到 1.0 之間的數字。 AnimationController 是一種特殊的 Animation 對象了, 它父類其實是一個 Animation<double>, 當硬件準備好需要一個新的幀的時候它就會產生一個新的值。 由於 AnimationController 派生自 Animation <double>,因此可以在需要 Animation 對象的任何地方使用它。 但是 AnimationController 還有其他的方法來控制動畫的播放, 例如前面提到的 .forward()方法啓動動畫。

  AnimationController 生成的數字(默認是從 0.0 到 1.0) 是和屏幕刷新有關, 前面也提到它會在硬件需要一個新幀的時候產生新值。 因爲屏幕一般都是 60 幀/秒, 所以它也通常一秒內生成 60 個數字。 每個數字生成之後, 每個 Animation 對象都會調用綁定的監聽器對象。

  • Tween

  Tween 本身表示的就是一個 Animation 對象的取值範圍, 只需要設置開始和結束的邊界值(值也支持泛型)。 它唯一的工作就是定義輸入範圍到輸出範圍的映射, 輸入一般是 AnimationController 給出的值 0.0~1.0。 看下面的例子, 我們就能知道 animation 的 value 是怎麼樣通過 AnimationController 生成的值映射到 Tween 定義的取值範圍裏面的。

  1. Tween.animation通過傳入 aniamtionController 獲得一個_AnimatedEvaluation 類型的 animation 對象(基類爲 Animation), 並且將 aniamtionController 和 Tween 對象傳入了 _AnimatedEvaluation 對象。
    animation = new Tween(begin: 0.0, end: 300.0).animate(controller)
        ...
      Animation<T> animate(Animation<double> parent) {
        return _AnimatedEvaluation<T>(parent, this);
      }
  2.  animation.value方法即是調用 _evaluatable.evaluate(parent)方法, 而 _evaluatable 和 parent 分別爲 Tween 對象和 AnimationController 對象
    T get value => _evaluatable.evaluate(parent);
         ....
      class _AnimatedEvaluation<T> extends Animation<T> with AnimationWithParentMixin<double> {
         _AnimatedEvaluation(this.parent, this._evaluatable);
         ....
  3. 這裏的 animation 其實就是前面的 AnimationController 對象, transform 方法裏面的 animation.value則就是 AnimationController 線性生成的 0.0~1.0 直接的值。 在 lerp 方法裏面我們可以看到這個 0.0~1.0 的值被映射到了 begin 和 end 範圍內了。

    複製代碼

    T evaluate(Animation<double> animation) => transform(animation.value);
    
        T transform(double t) {
        if (t == 0.0)
          return begin;
        if (t == 1.0)
          return end;
        return lerp(t);
      }
    
        T lerp(double t) {
        assert(begin != null);
        assert(end != null);
        return begin + (end - begin) * t;
      }

    複製代碼

  • Flutter 的"時鐘"

  那麼 Flutter 是怎麼樣讓這個動畫在規定時間不斷地繪製的呢?

  1. 首先看 Widget 引入的 SingleTickerProviderStateMixin 類。SingleTickerProviderStateMixin 是以 with 關鍵字引入的, 這是 dart 語言的 mixin 特性, 可以理解成"繼承", 所以 widget 相當於是繼承了SingleTickerProviderStateMixin。 所以在 AnimationController 對象的構造方法參數 vsync: this, 我們看到了這個類的使用。 從 "vsync" 參數名意爲"垂直幀同步"可以看出, 這個是繪製動畫幀的"節奏器"。

    複製代碼

    AnimationController({
        double value,
        this.duration,
        this.debugLabel,
        this.lowerBound = 0.0,
        this.upperBound = 1.0,
        this.animationBehavior = AnimationBehavior.normal,
        @required TickerProvider vsync,
      }) : assert(lowerBound != null),
           assert(upperBound != null),
           assert(upperBound >= lowerBound),
           assert(vsync != null),
           _direction = _AnimationDirection.forward {
        _ticker = vsync.createTicker(_tick);
        _internalSetValue(value ?? lowerBound);
      }

    複製代碼

  2. 在 AnimationController 的構造方法中, SingleTickerProviderStateMixin 的父類 TickerProvider 會創建一個 Ticker, 並將_tick(TickerCallback 類型)回調方法綁定到了 這個 Ticker, 這樣 AnimationController 就將回調方法 _tick 和 Ticker 綁定了。
    @protected
    void scheduleTick({ bool rescheduling = false }) {
        assert(!scheduled);
        assert(shouldScheduleTick);
        _animationId = SchedulerBinding.instance.scheduleFrameCallback(_tick, rescheduling: rescheduling);
    }
  3. 而 Ticker 會在 start 函數內將_tick 被綁定到 SchedulerBinding 的幀回調方法內。 返回的_animationId 是 SchedulerBinding 給定的下一個動作回調的 ID, 可以根據_animationId 來取消 SchedulerBinding 上綁定的回調。

    SchedulerBinding 則是在構造方法中將自己的 _handleBeginFrame 函數和 window 的 onBeginFrame 綁定了回調。 這個回調會在屏幕需要準備顯示幀之前回調。

    再回到 AnimationController 看它是如何控制 Animation 的值的。

    複製代碼

    void _tick(Duration elapsed) {
        _lastElapsedDuration = elapsed;
        final double elapsedInSeconds = elapsed.inMicroseconds.toDouble() / Duration.microsecondsPerSecond;
        assert(elapsedInSeconds >= 0.0);
        _value = _simulation.x(elapsedInSeconds).clamp(lowerBound, upperBound);
        if (_simulation.isDone(elapsedInSeconds)) {
          _status = (_direction == _AnimationDirection.forward) ?
            AnimationStatus.completed :
            AnimationStatus.dismissed;
          stop(canceled: false);
        }
        notifyListeners();
        _checkStatusChanged();
      }

    複製代碼

  4. 在 AnimationController 的回調當中, 會有一個 Simulation 根據動畫運行了的時間(elapsed) 來計算當前的的_value 值, 而且這個值還需要處於 Animation 設置的區間之內。 除了計算_value 值之外, 該方法還會更新 Animation Status 的狀態, 判斷是否動畫已經結束。 最後通過 notifyListeners 和_checkStatusChanged 方法通知給監聽器 value 和 AnimationStatus 的變化。 監聽 AnimationStatus 值的變化有一個專門的註冊方法 addStatusListener

    通過監聽 AnimationStatus, 在動畫開始或者結束的時候反轉動畫, 就達到了動畫循環播放的效果。

    複製代碼

    ...
       animation.addStatusListener((status) {
          if (status == AnimationStatus.completed) {
            controller.reverse();
          } else if (status == AnimationStatus.dismissed) {
            controller.forward();
          }
        });
        controller.forward();
    
        ...

    複製代碼

  5. 回顧一下這個動畫繪製調用的順序就是, window 調用 SchedulerBinding 的_handleBeginFrame 方法, SchedulerBinding 調用 Ticker 的_tick 方法, Ticker 調用 AnimationController 的_tick 的方法, AnimationContoller 通知監聽器, 而監聽器調用 widget 的 setStatus 方法來調用 build 更新, 最後 build 使用了 Animation 對象當前的值來繪製動畫幀。

    看到這裏會有一個疑惑, 爲什麼監聽器是註冊在 Animation 上的, 監聽通知反而由 AnimationController 發送?

    還是看源碼吧。

    複製代碼

    Animation<T> animate(Animation<double> parent) {
        return _AnimatedEvaluation<T>(parent, this);
      }
    
    class _AnimatedEvaluation<T> extends Animation<T> with AnimationWithParentMixin<double> {
      _AnimatedEvaluation(this.parent, this._evaluatable);
    }
    
    mixin AnimationWithParentMixin<T> {
      Animation<T> get parent;
      /// Listeners can be removed with [removeListener].
      void addListener(VoidCallback listener) => parent.addListener(listener);
    }

    複製代碼

  6. 首先 Animation 對象是由 Tween 的 animate 方法生成的, 它傳入了 AnimationController(Animation 的子類) 參數 作爲 parent 參數, 然後我們發現返回的 _AnimatedEvaluation<T>泛型對象 使用 mixin "繼承" 了 AnimationWithParentMixin<double>, 最後我們看到 Animation 作爲 AnimationWithParentMixin 的"子類"實現的 addListener 方法其實是將監聽器註冊到 parent 對象上了, 也就是 AnimationController

三,動畫示例

  • 示例一

    複製代碼

    import 'package:flutter/material.dart';
    import 'package:flutter/animation.dart';
    
    void main() {
      //運行程序
      runApp(LogoApp());
    }
    
    class LogoApp extends StatefulWidget{
      @override
      State<StatefulWidget> createState(){
        return new _LogoAppState();
      }
    }
    
    //logo
    Widget ImageLogo = new Image(
        image: new AssetImage('images/logo.jpg'),
    );
    
    //with 是dart的關鍵字,混入的意思,將一個或者多個類的功能添加到自己的類無需繼承這些類
    //避免多重繼承問題
    //SingleTickerProviderStateMixin 初始化 animation 和 Controller的時候需要一個TickerProvider類型的參數Vsync
    //所依混入TickerProvider的子類
    class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin{
      //動畫的狀態,如動畫開啓,停止,前進,後退等
      Animation<double> animation;
      //管理者animation對象
      AnimationController controller;
      @override
      void initState() {
        // TODO: implement initState
        super.initState();
        //創建AnimationController
        //需要傳遞一個vsync參數,存在vsync時會防止屏幕外動畫(
        //譯者語:動畫的UI不在當前屏幕時)消耗不必要的資源。 通過將SingleTickerProviderStateMixin添加到類定義中,可以將stateful對象作爲vsync的值。
        controller = new AnimationController(
            //時間是3000毫秒
            duration: const Duration(
                milliseconds: 3000
            ),
            //vsync 在此處忽略不必要的情況
            vsync: this,
        );
        //補間動畫
        animation = new Tween(
          //開始的值是0
          begin: 0.0,
          //結束的值是200
          end : 200.0,
        ).animate(controller)//添加監聽器
          ..addListener((){
            //動畫值在發生變化時就會調用
            setState(() {
    
            });
          });
        //只顯示動畫一次
        controller.forward();
      }
      @override
      Widget build(BuildContext context){
        return new MaterialApp(
          theme: ThemeData(
              primarySwatch: Colors.red
    
          ),
          home: new Scaffold(
            appBar: new AppBar(
              title: Text("動畫demo"),
            ),
            body:new Center(
              child: new Container(
                //寬和高都是根據animation的值來變化
                height: animation.value,
                width: animation.value,
                child: ImageLogo,
              ),
            ),
          ),
        );
      }
    
      @override
      void dispose() {
        // TODO: implement dispose
        super.dispose();
        //資源釋放
        controller.dispose();
      }
    }

    複製代碼

    上面實現了圖像在3000毫秒間從寬高是0變化到寬高是200,主要分爲六部

    1. 混入SingleTickerProviderStateMixin,爲了傳入vsync對象
    2. 初始化AnimationController對象
    3. 初始化Animation對象,並關聯AnimationController對象
    4. 調用AnimationControllerforward開啓動畫
    5. widget根據Animationvalue值來設置寬高
    6. widgetdispose()方法中調用釋放資源
    最終效果如下:
    注意:上面創建Tween用了Dart語法的級聯符號 
    animation = tween.animate(controller)
              ..addListener(() {
                setState(() {
                  // the animation object’s value is the changed state
                });
              });

    等價於下面代碼:

    animation = tween.animate(controller);
    animation.addListener(() {
                setState(() {
                  // the animation object’s value is the changed state
                });
              });

    1.1.AnimatedWidget簡化

      使用AnimatedWidget對動畫進行簡化,使用AnimatedWidget創建一個可重用動畫的widget,而不是用addListener()setState()來給widget添加動畫。AnimatedWidget類允許從setState()調用中的動畫代碼中分離出widget代碼。AnimatedWidget不需要維護一個State對象了來保存動畫。

    複製代碼

    import 'package:flutter/material.dart';
    import 'package:flutter/animation.dart';
    
    void main() {
      //運行程序
      runApp(LogoApp());
    }
    
    class LogoApp extends StatefulWidget{
      @override
      State<StatefulWidget> createState(){
        return new _LogoAppState();
      }
    }
    
    //logo
    Widget ImageLogo = new Image(
        image: new AssetImage('images/logo.jpg'),
    );
    
    //抽象出來
    class AnimatedLogo extends AnimatedWidget{
      AnimatedLogo({
      Key key,Animation<double> animation
     }):super(key:key,listenable:animation);
    
      @override
      Widget build(BuildContext context){
        final Animation<double> animation = listenable;
        return new MaterialApp(
          theme: ThemeData(
              primarySwatch: Colors.red
          ),
          home: new Scaffold(
            appBar: new AppBar(
              title: Text("動畫demo"),
            ),
            body:new Center(
              child: new Container(
                //寬和高都是根據animation的值來變化
                height: animation.value,
                width: animation.value,
                child: ImageLogo,
              ),
            ),
          ),
        );
      }
    }
    
    //with 是dart的關鍵字,混入的意思,將一個或者多個類的功能添加到自己的類無需繼承這些類
    //避免多重繼承問題
    //SingleTickerProviderStateMixin 初始化 animation 和 Controller的時候需要一個TickerProvider類型的參數Vsync
    //所依混入TickerProvider的子類
    class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin{
      //動畫的狀態,如動畫開啓,停止,前進,後退等
      Animation<double> animation;
      //管理者animation對象
      AnimationController controller;
      @override
      void initState() {
        // TODO: implement initState
        super.initState();
        //創建AnimationController
        //需要傳遞一個vsync參數,存在vsync時會防止屏幕外動畫(
        //譯者語:動畫的UI不在當前屏幕時)消耗不必要的資源。 通過將SingleTickerProviderStateMixin添加到類定義中,可以將stateful對象作爲vsync的值。
        controller = new AnimationController(
            //時間是3000毫秒
            duration: const Duration(
                milliseconds: 3000
            ),
            //vsync 在此處忽略不必要的情況
            vsync: this,
        );
        //補間動畫
        animation = new Tween(
          //開始的值是0
          begin: 0.0,
          //結束的值是200
          end : 200.0,
        ).animate(controller);//添加監聽器
        //只顯示動畫一次
        controller.forward();
      }
      
      @override
      Widget build(BuildContext context){
          return AnimatedLogo(animation: animation);
      }
    
      @override
      void dispose() {
        // TODO: implement dispose
        super.dispose();
        //資源釋放
        controller.dispose();
      }
    }

    複製代碼

    可以發現AnimatedWidget中會自動調用addListenersetState()_LogoAppStateAnimation對象傳遞給基類並用animation.value設置Image寬高。

    1.2.監視動畫

    在平時開發,我們知道,很多時候都需要監聽動畫的狀態,好像完成、前進、倒退等。在Flutter中可以通過addStatusListener()來得到這個通知,以下代碼添加了動畫狀態

    複製代碼

    //補間動畫
        animation = new Tween(
          //開始的值是0
          begin: 0.0,
          //結束的值是200
          end : 200.0,
        ).animate(controller)
        //添加動畫狀態
        ..addStatusListener((state){
          return print('$state');
       });//添加監聽器

    複製代碼

    運行代碼會輸出下面結果:

    I/flutter (16745): AnimationStatus.forward //動畫開始
    Syncing files to device KNT AL10...
    I/zygote64(16745): Do partial code cache collection, code=30KB, data=25KB
    I/zygote64(16745): After code cache collection, code=30KB, data=25KB
    I/zygote64(16745): Increasing code cache capacity to 128KB
    I/flutter (16745): AnimationStatus.completed//動畫完成

    下面那就運用addStatusListener()開始或結束反轉動畫。那就產生循環效果:

    複製代碼

    //補間動畫
        animation = new Tween(
          //開始的值是0
          begin: 0.0,
          //結束的值是200
          end : 200.0,
        ).animate(controller)
        //添加動畫狀態
        ..addStatusListener((state){
          //如果動畫完成了
          if(state == AnimationStatus.completed){
            //開始反向這動畫
            controller.reverse();
          } else if(state == AnimationStatus.dismissed){
            //開始向前運行着動畫
            controller.forward();
          }
        });//添加監聽器

    複製代碼

    效果如下:

    1.3.用AnimatedBuilder重構

    上面的代碼存在一個問題:更改動畫需要更改顯示Imagewidget,更好的解決方案是將職責分離:

    1. 顯示圖像
    2. 定義Animation對象
    3. 渲染過渡效果 這時候可以藉助AnimatedBuilder類完成此分離。AnimatedBuilder是渲染樹中的一個獨立的類,與AnimatedWidget類似,AnimatedBuilder自動監聽來自Animation對象的通知,並根據需要將該控件樹標記爲髒(dirty),因此不需要手動調用addListener()

    複製代碼

    //AnimatedBuilder
    class GrowTransition extends StatelessWidget{
      final Widget child;
      final Animation<double> animation;
      GrowTransition({this.child,this.animation});
    
      @override
      Widget build(BuildContext context){
        return new MaterialApp(
          theme: ThemeData(
              primarySwatch: Colors.red
          ),
          home: new Scaffold(
            appBar: new AppBar(
              title: Text("動畫demo"),
            ),
            body:new Center(
                child: new AnimatedBuilder(
                    animation: animation,
                    builder: (BuildContext context,Widget child){
                      return new Container(
                        //寬和高都是根據animation的值來變化
                        height: animation.value,
                        width: animation.value,
                        child: child,
                      );
                    },
                  child: child,
                ),
    
            ),
          ),
        );
      }
      class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin{
      //動畫的狀態,如動畫開啓,停止,前進,後退等
      Animation animation;
      //管理者animation對象
      AnimationController controller;
      @override
      void initState() {
        // TODO: implement initState
        super.initState();
        //創建AnimationController
        //需要傳遞一個vsync參數,存在vsync時會防止屏幕外動畫(
        //譯者語:動畫的UI不在當前屏幕時)消耗不必要的資源。 通過將SingleTickerProviderStateMixin添加到類定義中,可以將stateful對象作爲vsync的值。
        controller = new AnimationController(
            //時間是3000毫秒
            duration: const Duration(
                milliseconds: 3000
            ),
            //vsync 在此處忽略不必要的情況
            vsync: this,
        );
        final CurvedAnimation curve  = new CurvedAnimation(parent: controller, curve: Curves.easeIn);
        //補間動畫
        animation = new Tween(
          //開始的值是0
          begin: 0.0,
          //結束的值是200
          end : 200.0,
        ).animate(curve)
    //    //添加動畫狀態
        ..addStatusListener((state){
          //如果動畫完成了
          if(state == AnimationStatus.completed){
            //開始反向這動畫
            controller.reverse();
          } else if(state == AnimationStatus.dismissed){
            //開始向前運行着動畫
            controller.forward();
          }
        });//添加監聽器
        //只顯示動畫一次
        controller.forward();
      }
    
      @override
      Widget build(BuildContext context){
          //return AnimatedLogo(animation: animation);
            return new GrowTransition(child:ImageLogo,animation: animation);
      }
    
      @override
      void dispose() {
        // TODO: implement dispose
        super.dispose();
        //資源釋放
        controller.dispose();
      }
    }

    複製代碼

    上面代碼有一個迷惑的問題是,child看起來好像是指定了兩次,但實際發生的事情是,將外部引用的child傳遞給AnimatedBuilderAnimatedBuilder將其傳遞給匿名構造器,然後將該對象用作其子對象。最終的結果是AnimatedBuilder插入到渲染樹中的兩個Widget之間。最後,在initState()方法創建一個AnimationController和一個Tween,然後通過animate()綁定,在build方法中,返回帶有一個Image爲子對象的GrowTransition對象和一個用於驅動過渡的動畫對象。如果只是想把可複用的動畫定義成一個widget,那就用AnimatedWidget

    1.4.並行動畫

    很多時候,一個動畫需要兩種或者兩種以上的動畫,在Flutter也是可以實現的,每一個Tween管理動畫的一種效果,如:

    final AnimationController controller =
        new AnimationController(duration: const Duration(milliseconds: 2000), vsync: this);
        final Animation<double> sizeAnimation =
        new Tween(begin: 0.0, end: 300.0).animate(controller);
        final Animation<double> opacityAnimation =
        new Tween(begin: 0.1, end: 1.0).animate(controller);

    可以通過sizeAnimation.Value來獲取大小,通過opacityAnimation.value來獲取不透明度,但AnimatedWidget的構造函數只能接受一個動畫對象,解決這個問題,需要動畫的widget創建了自己的Tween對象,上代碼:

    複製代碼

    //AnimatedBuilder
    class GrowTransition extends StatelessWidget {
      final Widget child;
      final Animation<double> animation;
    
      GrowTransition({this.child, this.animation});
      static final _opacityTween = new Tween<double>(begin: 0.1, end: 1.0);
      static final _sizeTween = new Tween<double>(begin: 0.0, end: 200.0);
    
      @override
      Widget build(BuildContext context) {
        return new MaterialApp(
          theme: ThemeData(primarySwatch: Colors.red),
          home: new Scaffold(
            appBar: new AppBar(
              title: Text("動畫demo"),
            ),
            body: new Center(
              child: new AnimatedBuilder(
                animation: animation,
                builder: (BuildContext context, Widget child) {
                  return new Opacity(
                      opacity: _opacityTween.evaluate(animation),
                    child: new Container(
                    //寬和高都是根據animation的值來變化
                    height: _sizeTween.evaluate(animation),
                    width: _sizeTween.evaluate(animation),
                    child: child,
                  ),
                  );
                },
                child: child,
              ),
            ),
          ),
        );
      }
    }
    
    class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
      //動畫的狀態,如動畫開啓,停止,前進,後退等
      Animation<double> animation;
    
      //管理者animation對象
      AnimationController controller;
    
      @override
      void initState() {
        // TODO: implement initState
        super.initState();
        //創建AnimationController
        //需要傳遞一個vsync參數,存在vsync時會防止屏幕外動畫(
        //譯者語:動畫的UI不在當前屏幕時)消耗不必要的資源。 通過將SingleTickerProviderStateMixin添加到類定義中,可以將stateful對象作爲vsync的值。
        controller = new AnimationController(
          //時間是3000毫秒
          duration: const Duration(milliseconds: 3000),
          //vsync 在此處忽略不必要的情況
          vsync: this,
        );
        //新增
        animation = new CurvedAnimation(parent: controller, curve: Curves.easeIn)
          ..addStatusListener((state) {
            //如果動畫完成了
            if (state == AnimationStatus.completed) {
              //開始反向這動畫
              controller.reverse();
            } else if (state == AnimationStatus.dismissed) {
              //開始向前運行着動畫
              controller.forward();
            }
          }); //添加監聽器
        //只顯示動畫一次
        controller.forward();
      }
    
      @override
      Widget build(BuildContext context) {
         return new GrowTransition(child:ImageLogo,animation: animation);
      }
    
      @override
      void dispose() {
        // TODO: implement dispose
        super.dispose();
        //資源釋放
        controller.dispose();
      }
    }

    複製代碼

    可以看到在GrowTransition定義兩個Tween動畫,並且加了不透明Opacitywidget,最後在initState方法中修改增加一句animation = new CurvedAnimation(parent: controller, curve: Curves.easeIn),最後的動畫效果:

     

    注意:可以通過改變Curves.easeIn值來實現非線性運動效果。


  • 2.自定義動畫

    示例2:

     

    2.1.自定義小球

    複製代碼

    class _bollView extends CustomPainter{
      //顏色
      Color color;
      //數量
      int count;
      //集合放動畫
      List<Animation<double>> ListAnimators;
      _bollView({this.color,this.count,this.ListAnimators});
      @override
      void paint(Canvas canvas,Size size){
         //繪製流程
         double boll_radius = (size.width - 15) / 8;
         Paint paint = new Paint();
         paint.color = color;
         paint.style = PaintingStyle.fill;
         //因爲這個wiaget是80 球和球之間相隔5
         for(int i = 0; i < count;i++){
           double value = ListAnimators[i].value;
           //確定圓心 半徑 畫筆
           //第一個球 r
           //第二個球 5 + 3r
           //第三個球 15 + 5r
           //第四個球 30 + 7r
           //半徑也是隨着動畫值改變
           canvas.drawCircle(new Offset((i+1) * boll_radius + i * boll_radius  + i * 5,size.height / 2), boll_radius * (value > 1 ? (2 - value) : value), paint);
         }
      }
    
      //刷新是否重繪
      @override
      bool shouldRepaint(CustomPainter oldDelegate){
        return oldDelegate != this;
      }
    }

    複製代碼

    2.2.配置小球屬性

    複製代碼

    class MyBalls extends StatefulWidget{
      Size size;
      Color color;
      int count;
      int seconds;
    
      //默認四個小球 紅色
      MyBalls({this.size,this.seconds : 400,this.color :Colors.redAccent,this.count : 4});
      @override
      State<StatefulWidget> createState(){
        return MyBallsState();
      }
    }

    複製代碼

    2.3.創建動畫

    複製代碼

    //繼承TickerProviderStateMixin,提供Ticker對象
    class MyBallsState extends State<MyBalls> with TickerProviderStateMixin {
      //動畫集合
      List<Animation<double>>animatios = [];
      //控制器集合
      List<AnimationController> animationControllers = [];
      //顏色
      Animation<Color> colors;
    
      @override
      void initState(){
        super.initState();
        for(int i = 0;i < widget.count;i++){
             //創建動畫控制器
             AnimationController animationController = new AnimationController(
                 vsync: this,
                 duration: Duration(
                   milliseconds: widget.count * widget.seconds
                 ));
             //添加到控制器集合
             animationControllers.add(animationController);
             //顏色隨機
             colors = ColorTween(begin: Colors.red,end:Colors.green).animate(animationController);
             //創建動畫 每個動畫都要綁定控制器
             Animation<double> animation = new Tween(begin: 0.1,end:1.9).animate(animationController);
             animatios.add(animation);
        }
        animatios[0].addListener((){
          //刷新
          setState(() {
          });
        });
    
        //延遲執行
        var delay = (widget.seconds ~/ (2 * animatios.length - 2));
        for(int i = 0;i < animatios.length;i++){
         Future.delayed(Duration(milliseconds: delay * i),(){
            animationControllers[i]
                ..repeat().orCancel;
          });
        }
      }
      @override
      Widget build(BuildContext context){
        return new CustomPaint(
          //自定義畫筆
          painter: _bollView(color: colors.value,count: widget.count,ListAnimators : animatios),
          size: widget.size,
        );
      }
      //釋放資源
      @override
      void dispose(){
        super.dispose();
        animatios[0].removeListener((){
          setState(() {
          });
        });
        animationControllers[0].dispose();
      }
    }

    複製代碼

    2.4.調用

    複製代碼

    class Ball extends StatelessWidget{
      @override
      Widget build(BuildContext context){
        return MaterialApp(
          home: Scaffold(
            appBar: AppBar(
              title: Text('Animation demo'),
            ),
            body: Center(
                child: MyBalls(size: new Size(80.0,20.0)),
            ),
          ),
        );
      }
    }

    複製代碼

四,總結

  本篇文章從簡單的例子出發, 並且結合了源碼, 分析了 Flutter 動畫實現的原理。Flutter 以硬件設備刷新爲驅動, 驅使 widget 依據給定的值生成新動畫幀, 從而實現了動畫效果。

鏈接:
  1. https://juejin.im/post/5cdbbc01f265da037b6134d9
      2.https://juejin.im/post/5c617e34f265da2d90581613

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章