Flutter 掌握動畫開發

主本文主要說明動畫的基本原理和簡單的動畫的實例,若有不當之處敬請指正。git

閱讀本文大約須要 6 分鐘github

背景

給UI界面設計合理的動畫,可讓用戶以爲更加流暢、直觀,提升用戶的交互使用感覺,改善用戶體驗。markdown

在 Flutter 中動畫分爲兩類:基於補間 (Tween) 的和基於物理 (Physics) 的;less

補間動畫是介於二者之間的簡稱,在補間動畫中定義起點和終點、時間點以及定義時間變化和速度的曲線,而後由系統計算如何從開始點到結束點。ide

物理動畫是運動被模擬爲與真實世界的行爲類似,好比拋一件物體,它落在什麼地方取決於這個物體的重量,拋出去的速度以及這個物體與地面的高度,相似數學中的拋物線運動軌跡。函數

介紹

在 Flutter 中想要實現動畫效果離不開幾個核心的角色:Animation(動畫對象),AnimationController(動畫控制器),Tweens(插值器),Curves(動畫曲線);學習

一、Animation

在 Flutter 中動畫自己和UI渲染沒有任何關係,Animation是一個抽象類,它擁有其當前值和狀態(完成或中止),Flutter 中的動畫系統就是基於 Animation 對象的。其中比較經常使用的就是Animation類是Animation。它能夠經過其 value 屬性來獲取當前動畫的值。動畫

Animation 除了能夠生成 double 的值以外還能夠生成如:顏色--Animation<Color> 或者大小--Animation<Size>ui

Animation 對象能夠擁有 Listeners 和 StatusListeners 監聽器,能夠用 addListener()addStatusListener() 來添加。只要動畫的的值發生變化,就會調用監聽器。正常咱們在 Listeners 中調用setState() 來觸發UI重建;動畫開始、結束、向前移動或向後移動時會調用StatusListener。this

二、AnimationController

AnimationController 是一個特殊的 Animation 對象,在屏幕刷新的每一幀,就會生成一個新的值。默認狀況下,AnimationController 會在特定的時間內線性的生成0.0到1.0的數字。AnimationController派生於 Animation<double>,所以能夠在須要Animation對象的任何地方使用。不但如此,AnimationController還具備控制動畫的其餘方法,好比 forward()方法能夠啓動動畫。

AnimationController({
    double value,
    this.duration,
    this.reverseDuration,
    this.debugLabel,
    this.lowerBound = 0.0,
    this.upperBound = 1.0,
    this.animationBehavior = AnimationBehavior.normal,
    @required TickerProvider vsync,
  })
複製代碼

建立 AnimationController 必須需傳入 vsync,傳入 vsunc 是爲了防止動畫的UI不在當前屏幕時,不須要繪製,從而防止消耗沒必要要的資源。經過將 SingleTickerProviderStateMixin 混入到類定義中,就能夠將 statefu l對象做爲 vsync 的值。

除了 vsync 還能夠傳入正向動畫執行的時間 duration 以及反向動畫執行時間 reverseDuration 等。

經常使用函數:

序號 方法 介紹
1 forward() 開始播放動畫
2 stop() 中止動畫
3 reset() 重製動畫
4 reverse() 反向播放動畫,必須處於正向動畫播放完成的狀態以後纔有用
5 dispose() 釋放動畫佔用資源
6 repeat() 循環播放動畫

注意:動畫完成時釋放控制器(調用 dispose() 方法)以防止內存泄漏

@override
void dispose() {
  animationController.dispose();
  super.dispose();
}
複製代碼

三、Tween

默認狀況下,AnimationController對象的範圍從0.0到1.0。若是您須要不一樣的範圍或不一樣的數據類型,則可使用Tween來配置動畫以生成不一樣的範圍或數據類型的值。好比,能夠生產從0-100的數字:

final Tween doubleTween = new Tween<double>(begin: 0.0, end: 100.0);
複製代碼

Tween是一個無狀態(stateless)對象,繼承自Animatable<T>,而不是繼承自 Animation<T>。Tween 須要兩個值,分別是:begin 和 end。Tween的惟一職責就是定義從輸入範圍到輸出範圍的映射。

Animatable與Animation類似,不是必須輸出double值,也能夠是顏色,好比,從白色到黑色:

final Tween colorTween = new ColorTween(begin: Colors.withe, end: Colors.black);
複製代碼

Tween 能夠經過 animate() 方法傳入 controller 對象建立 Animation 對象。以下

AnimationController _animationController = AnimationController(animationBehavior:AnimationBehavior.normal,vsync: this);
Tween<double> _tween = Tween<double>(begin: 0.0, end: 100.0)..animate(_animationController);
複製代碼

四、CurvedAnimation

Curves 用來調整動畫過程當中隨時間的變化率,默認狀況下,動畫以均勻的線性模型變化。Flutter 內部也提供了一系列實現相應變化率的 Curves 對象:linea ------ 線性,decelerate ------ 減速等。

固然,也能夠自定義繼承 Curves 的類來定義動畫的變化率,如:

class ShakeCurve extends Curve {
  @override
  double transform(double t) {
    return math.sin(t * math.PI * 2);
  }
}
複製代碼

五、添加監聽

目前爲止動畫只是實現了自身數值的變化,並無讓 Widget 動起來,這裏咱們須要對動畫數值進行監聽,而後使用 setstatus 來更新 Widget 的屬性,從而使 Widget 動起來。

添加數值監聽:

Animation animation = CurvedAnimation(parent: _animationController, curve: Curves.linear);
    animation.addListener((){
      setState(() {
        
      });
    });
複製代碼

除此以外咱們還能夠監聽動畫的狀態變動,當動畫結束時咱們反轉動畫,當動畫的反轉也結束後咱們重新開始動畫,這樣動畫就會一直這樣循環下去。

狀態變動監聽:

animation.addStatusListener((status){
      print(status);
    });
複製代碼

六、AnimatedWidget

AnimatedWidget 類容許您從 setState() 調用中的動畫代碼中分離出 widget 代碼。AnimatedWidget 不須要維護一個 State 對象來保存動畫。

如下代碼爲官方文檔自定義 AnimatedLogo

import 'package:flutter/animation.dart';
import 'package:flutter/material.dart';

class AnimatedLogo extends AnimatedWidget {
  AnimatedLogo({Key key, Animation<double> animation})
      : super(key: key, listenable: animation);

  Widget build(BuildContext context) {
    final Animation<double> animation = listenable;
    return new Center(
      child: new Container(
        margin: new EdgeInsets.symmetric(vertical: 10.0),
        height: animation.value,
        width: animation.value,
        child: new FlutterLogo(),
      ),
    );
  }
}

class LogoApp extends StatefulWidget {
  _LogoAppState createState() => new _LogoAppState();
}

class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
  AnimationController controller;
  Animation<double> animation;

  initState() {
    super.initState();
    controller = new AnimationController(
        duration: const Duration(milliseconds: 2000), vsync: this);
    animation = new Tween(begin: 0.0, end: 300.0).animate(controller);
    controller.forward();
  }

  Widget build(BuildContext context) {
    return new AnimatedLogo(animation: animation);
  }

  dispose() {
    controller.dispose();
    super.dispose();
  }
}
複製代碼

AnimatedWidget 爲何不須要維護一個 State 對象來保存動畫呢?

AnimatedWidget 源碼中看一看出 AnimatedWidget 是繼承自 StatefulWidget 類,在 AnimatedWidget 中,建立 state 是建立了 _AnimatedState,接着看 _AnimatedState 類部分源碼:

abstract class AnimatedWidget extends StatefulWidget{
  
   @override
  _AnimatedState createState() => _AnimatedState();
  
}
複製代碼

_AnimatedState 類的 initState 方法添加了監聽 _handleChange,並在 didUpdateWidgetdispose 方法中移除了,_handleChange 裏面只有一行代碼就是 setState 方法:

_AnimatedState 源碼

class _AnimatedState extends State<AnimatedWidget> {
  @override
  void initState() {
    super.initState();
    // 添加監聽
    widget.listenable.addListener(_handleChange);
  }

  @override
  void didUpdateWidget(AnimatedWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    // 移除
    if (widget.listenable != oldWidget.listenable) {
      oldWidget.listenable.removeListener(_handleChange);
      widget.listenable.addListener(_handleChange);
    }
  }

  @override
  void dispose() {
    // 移除
    widget.listenable.removeListener(_handleChange);
    super.dispose();
  }

  void _handleChange() {
    // 更新狀態
    setState(() {
      // The listenable's state is our build state, and it changed already.
    });
  }

  @override
  Widget build(BuildContext context) => widget.build(context);
}
複製代碼

七、並行動畫

所謂的並行動畫就是一塊兒執行多個動畫,在 Flutter 中能夠在同一個動畫控制器上使用多個Tween,而後每一個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的構造函數只接受一個動畫對象。 爲了解決這個問題,能夠建立了本身的Tween對象並顯式計算了這些值。

build方法.evaluate()在父級的動畫對象上調用Tween函數以計算所需的sizeopacity值。

import 'package:flutter/animation.dart';
import 'package:flutter/material.dart';

class AnimatedLogo extends AnimatedWidget {
  // The Tweens are static because they don't change.
  static final _opacityTween = new Tween<double>(begin: 0.1, end: 1.0);
  static final _sizeTween = new Tween<double>(begin: 0.0, end: 300.0);

  AnimatedLogo({Key key, Animation<double> animation})
      : super(key: key, listenable: animation);

  Widget build(BuildContext context) {
    final Animation<double> animation = listenable;
    return new Center(
      child: new Opacity(
        opacity: _opacityTween.evaluate(animation),
        child: new Container(
          margin: new EdgeInsets.symmetric(vertical: 10.0),
          height: _sizeTween.evaluate(animation),
          width: _sizeTween.evaluate(animation),
          child: new FlutterLogo(),
        ),
      ),
    );
  }
}
複製代碼

實例

效果圖

一、縮放動畫

直接貼代碼

///放大縮小動畫
  Widget scale() {
    return Column(
      children: <Widget>[
        Container(
          height: 170,
          child: Center(
            child: Container(
              width: _scaleAnimation.value,
              height: _scaleAnimation.value,
              child: new FlutterLogo(),
            ),
          ),
        ),
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            RaisedButton(
              color: Colors.blue,
              child: Text(
                "放大",
                style: TextStyle(color: Colors.white),
              ),
              onPressed: () {
                _scaleController.forward();
              },
            ),
            RaisedButton(
              color: Colors.red,
              child: Text(
                "縮小",
                style: TextStyle(color: Colors.white),
              ),
              onPressed: () {
                _scaleController.reverse();
              },
            )
          ],
        ),
      ],
    );
  }
複製代碼

二、淡入淡出動畫

代碼:

/// 淡入淡出
  Widget alpha() {
    return Column(
      children: <Widget>[
        Container(
          height: 170,
          child: Center(
            child: Container(
              height: 100,
              width: 100,
              child: Opacity(
                opacity: _alphaAnimation.value,
                child: FlutterLogo(),
              ),
            ),
          ),
        ),
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            RaisedButton(
              color: Colors.blue,
              child: Text(
                "淡入",
                style: TextStyle(color: Colors.white),
              ),
              onPressed: () {
                _alphaController.forward();
              },
            ),
            RaisedButton(
              color: Colors.red,
              child: Text(
                "淡出",
                style: TextStyle(color: Colors.white),
              ),
              onPressed: () {
                _alphaController.reverse();
              },
            )
          ],
        ),
      ],
    );
  }
複製代碼

注意,一個 Widget 使用多個animationController 須要修改混入SingleTickerProviderStateMixin 爲 TickerProviderStateMixin。

結尾

完整代碼奉上GitHub地址:fluter_demo ,歡迎star和fork。

到此,本文就結束了,若有不當之處敬請指正,一塊兒學習探討,謝謝🙏。

相關文章
相關標籤/搜索