Flutter開發之動畫

動畫做爲產品的重要組成部分,是提高用戶體驗的重要方式,一個恰當的動畫不只可以緩解用戶由於等待而帶來的情緒焦躁,還會增長應用的總體用戶體驗。所以,在應用中增長動畫的相關功能,能夠加強用戶的粘性。app

動畫的原理

不論是Android平臺仍是iOS平臺,咱們在使用應用時都能看到一些炫酷的動畫效果。做爲移動應用的重要組成部分,動畫是提升用戶體驗的重要手段,一個恰當的動畫,不只可以緩解用戶由於等待而帶來的情緒問題,還會提高用戶使用的體驗。
事實上,不論是什麼視圖框架,動畫的實現原理都是相同的,即在一段有限的時間內,屢次快速地改變視圖外觀來實現一個連續播放的效果。視圖的一次改變即稱爲一個動畫幀,對應一次屏幕刷新,而決定動畫流暢度的一個重要指標就是幀率FPS(Frame Per Second縮寫),即每秒的動畫幀數。很明顯,幀率越高則動畫就會越流暢。
目前,大多數設備的屏幕刷新頻率能夠到達60Hz,而對於人眼來講,動畫幀率超過16FPS就認爲是流暢的,超過32FPS基本就感覺不到任何卡頓。因爲動畫的每一幀都須要改變視圖的輸出,因此在一個時間段內連續的改變視圖輸出是比較耗費資源的,對設備的軟硬件系統要求也比較高。做爲衡量一個視圖框架優劣的標準,Flutter框架在理想狀況下是能夠實現60FPS的,這和原生應用的幀率標準是基本是持平的。
同時,爲了方便開發者建立並使用動畫,不一樣的視圖框架對動畫都進行了高度的抽象和封裝,好比在Android開發中,可使用XML來描述一個動畫而後再設置給一個視圖對象。一樣,Flutter也對動畫進行了高度的抽象,而且提供了Animation、Curve、Controller、Tween等四個動畫對象。
Animation是Flutter動畫的核心抽象類,包含動畫的當前值和狀態兩個屬性。AnimationController是Animation的控制器,動畫的開始、結束、中止、反向均由它控制,能夠經過Listener和StatusListener來管理動畫狀態的改變。框架

動畫API

在Flutter中,學習動畫相關的開發,其實就是圍繞Animation、Curve、Controller、Tween等四個動畫對象來展開的。less

Animation

在Flutter中,Animation是實現動畫的核心類,Animation的主要做用就是保存動畫的插值和狀態,它自己與視圖渲染沒有任何關係。Animation對象則是一個能夠在一段時間內依次生成一個區間值的類,其輸出值能夠是線性的、曲線的,能夠是一個步進函數或者任何其餘曲線函數等,由Curve來決定。Animation的核心源碼以下:ide

abstract class Animation<T> extends Listenable implements ValueListenable<T> {
  const Animation();
  
  // 添加動畫監聽器
  @override
  void addListener(VoidCallback listener);
  
  // 移除動畫監聽器
  @override
  void removeListener(VoidCallback listener);
 
  // 添加動畫狀態監聽器
  void addStatusListener(AnimationStatusListener listener);
 
  // 移除動畫狀態監聽器
  void removeStatusListener(AnimationStatusListener listener);
 
  // 獲取動畫當前狀態
  AnimationStatus get status;
 
  // 獲取動畫當前的值
  @override
  T get value;

Animation是一個抽象類,Widget能夠直接將這些動畫合併到本身的build方法中來讀取它們的當前值或者監聽它們的狀態變化。Animation提供了addListener和addStatusListener兩個方法來監聽動畫幀的變化。函數

addListener
addListener方法用於給Animation對象添加幀監聽器,每一幀都會被調用,當幀監聽器監聽到狀態發生改變後會調用setState()來觸發視圖的重建。這意味着:性能

  • 每當動畫的狀態值發生變化時,動畫都會通知全部經過 addListener 添加的監聽器。
  • 一個正在監聽動畫的state對象會調用自身的setState方法,將自身傳入這些監聽器的回調函數來通知 widget 系統須要根據新狀態值進行從新構建。

addStatusListener
addStatusListener方法用於給Animation對象添加動畫狀態改變監聽器,動畫開始、結束、正向或反向時會調用狀態改變的監聽器。這意味着:學習

  • 當動畫的狀態發生變化時,會通知全部經過 addStatusListener 添加的監聽器。
  • 動畫會從 dismissed 狀態開始,表示它處於變化區間的開始點。
  • 舉例來講,從 0.0 到 1.0 的動畫在 dismissed 狀態時的值應該是 0.0。
  • 動畫進行的下一狀態多是 forward(好比從 0.0 到 1.0)或者 reverse(好比從 1.0 到 0.0)。
  • 最終,若是動畫到達其區間的結束點(好比 1.0),則動畫會變成 completed 狀態

AnimationController

AnimationController,即動畫控制器,Animation是一個抽象類,並不能用來直接建立對象並實現動畫,它的主要用於控制動畫的開始、結束、中止、反向等操做。AnimationController是Animation的一個子類,默認狀況下,AnimationController會在給定的時間段內以線性的方式生成從0.0到1.0的數字。它的源碼以下:優化

class AnimationController extends Animation<double>
  with AnimationEagerListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin {
  AnimationController({
    // 初始化值
    double value,
    // 動畫執行的時間
    this.duration,
    // 反向動畫執行的時間
    this.reverseDuration,
    // 最小值
    this.lowerBound = 0.0,
    // 最大值
    this.upperBound = 1.0,
    // 刷新率ticker的回調(看下面詳細解析)
    @required TickerProvider vsync,
  }) 
}

其中,AnimationController有一個必傳的參數vsync,那麼AnimationController有什麼做用呢?以前我講過關於Flutter的渲染閉環,Flutter每次渲染一幀畫面以前都須要等待一個vsync信號。這裏也是爲了監聽vsync信號,當Flutter開發的應用程序再也不接受同步信號時(好比鎖屏或退到後臺),那麼繼續執行動畫會消耗性能,開發中比較常見的解決方法是將SingleTickerProviderStateMixin混入到State的定義中。例如,下面是一個比較簡單的數字自動增長動畫的示例。動畫

import 'package:flutter/material.dart';
import 'package:gc_data_app/utils/utils.dart';

class AnimText extends StatefulWidget {

  final int number;
  final int duration;
  final Color fontColor;
  final double fontSize;

  const AnimText({
    Key key,
    this.number,
    this.duration,
    this.fontColor,
    this.fontSize,
  }) : super(key: key);

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

class AnimState extends State<AnimText> with SingleTickerProviderStateMixin {

  AnimationController controller;
  Animation animation;
  var begin=0;

  @override
  void initState() {
    super.initState();
    controller = AnimationController(
        vsync: this, duration: Duration(milliseconds: widget.duration));
    final Animation curve=CurvedAnimation(parent: controller,curve: Curves.linear);
    animation = IntTween(begin: begin, end: widget.number).animate(curve)..addStatusListener((status) {
       if(status==AnimationStatus.completed){
//         controller.reverse();
       }
    });
  }

  @override
  Widget build(BuildContext context) {
    controller.forward();
    return AnimatedBuilder(
        animation: controller,
        builder: (context,child){
          return Container(
            child:Text(Utils.formatMoney(animation.value),
              style: TextStyle(fontSize: widget.fontSize, color: widget.fontColor,fontWeight: FontWeight.bold)),
          );
        } ,
    );
  }

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

CurvedAnimation

CurvedAnimation是Animation的一個實現類,它的目的是爲了給AnimationController增長動畫曲線。一般,動畫過程能夠是勻速的、勻加速的或者先加速後減速等。Flutter經過Curve來描述動畫過程,咱們能夠把勻速動畫稱爲線性動畫,把非勻速動畫稱爲非線性動畫。ui

CurvedAnimation能夠將AnimationController和Curve結合起來,生成一個新的Animation對象。例如:

class CurvedAnimation extends Animation<double> with AnimationWithParentMixin<double> {
  CurvedAnimation({
    // 一般傳入一個AnimationController
    @required this.parent,
    // Curve類型的對象
    @required this.curve,
    this.reverseCurve,
  });
}

Curve類型的對象的有一些常量Curves能夠直接使用,經常使用的有以下一些:

  • linear:勻速動畫
  • decelerate:勻減速動畫
  • ease:先加速後減速
  • easeIn:先快後慢動畫
  • easeOut:先慢後快動畫
  • easeInOut:先慢,而後加速,最後減速

Tween

默認狀況下,AnimationController對象的取值範圍是[0.0,1.0],若是須要給動畫設置不一樣的範圍或者類型的值時,可使用Tween來定義並生成不一樣範圍或類型的值。Tween的源碼很是簡單,傳入兩個值便可,以下所示。

class Tween<T extends dynamic> extends Animatable<T> {
  Tween({ this.begin, this.end });
}

Tween繼承自Animatable<T>,而不是繼承自Animation<T>,Animatable是一個控制動畫類型的類,主要定義了動畫值的映射規則。雖然,Animatable和Animation有不少類似之處,但它的類型能夠是除double的其餘類型。例如,下面是使用ColorTween實現顏色漸變的過渡動畫的例子。

Tween colorTween =new ColorTween(begin: Colors.transparent, end: Colors.black54);

Tween也有一些子類,好比ColorTween、BorderTween,能夠針對動畫或者邊框來設置動畫的值。

動畫示例

和原平生臺的動畫開發同樣,Flutter的動畫開發也有必定的規則,實際使用時,只須要按照遵循步驟便可。通用的步驟以下:
1.建立AnimationController和Animation;
2.設置動畫的類型,監聽動畫執行
3.銷燬動畫

例如,下面是Flutter動畫的基本使用示例,代碼以下:

import 'package:demos/page/anim_page.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter 動畫'),
      ),
      body: HeartAnimationWidget(key: animKey),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: () {
          if (!animKey.currentState.controller.isAnimating) {
            animKey.currentState.controller.forward();
          } else {
            animKey.currentState.controller.stop();
          }
        },
      ),
    );
  }
}



GlobalKey<_HeartAnimationPageState> animKey = GlobalKey();

class HeartAnimationPage extends StatefulWidget {

  HeartAnimationPage({Key key}): super(key: key);
  @override
  _HeartAnimationPageState createState() => _HeartAnimationPageState();
}

class _HeartAnimationPageState extends State<HeartAnimationPage> with SingleTickerProviderStateMixin {
  AnimationController controller;
  Animation<double> animation;

  @override
  void initState() {
    super.initState();
    // 1.建立AnimationController
    controller = AnimationController(duration: Duration(seconds: 1), vsync: this);
    // 2.動畫添加Curve效果
    animation = CurvedAnimation(parent: controller, curve: Curves.elasticInOut, reverseCurve: Curves.easeOut);
    // 3.監聽動畫
    animation.addListener(() {
      setState(() {});
    });
    // 4.控制動畫的翻轉
    animation.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        controller.reverse();
      } else if (status == AnimationStatus.dismissed) {
        controller.forward();
      }
    });
    // 5.設置值的範圍
    animation = Tween(begin: 50.0, end: 120.0).animate(controller);
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Icon(Icons.favorite, color: Colors.red, size: animation.value,),
    );
  }

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

運行上面的代碼,當點擊案例後執行一個心跳動畫,能夠反覆執行,再次點擊能夠暫停和從新開始動畫,運行效果以下圖所示。
在這裏插入圖片描述

AnimatedWidget

經過addListener()和setState()來更新視圖是動畫實現的通用的作法,但缺點是須要在每一個動畫中都添加監聽函數,代碼比較冗餘,而且調用setState()方法意味着整個State類中的build方法就會被從新構建,性能損耗比較嚴重。所以,官方推薦使用AnimatedWidget類來實現一樣的動畫效果,由於AnimatedWidget類簡化了addListener()和setState()的調用流程,並隱藏了底層額實現細節。

對於上面的示例,咱們西安建立一個繼承自AnimatedWidget的Widget,以下所示。

class HeatAnimationWidget extends AnimatedWidget {
  HeatAnimationWidget(Animation animation): super(listenable: animation);

  @override
  Widget build(BuildContext context) {
    Animation animation = listenable;
    return Icon(Icons.favorite, color: Colors.red, size: animation.value,);
  }
}

而後,咱們對HeartAnimationPage的代碼進行以下修改。

class HeartAnimationPage extends StatefulWidget {

  HeartAnimationPage({Key key}): super(key: key);
  @override
  _HeartAnimationPageState createState() => _HeartAnimationPageState();
}

class _HeartAnimationPageState extends State<HeartAnimationPage> with SingleTickerProviderStateMixin {

  AnimationController controller;
  Animation<double> animation;

  @override
  void initState() {
    super.initState();
    // 1.建立AnimationController
    controller = AnimationController(duration: Duration(seconds: 1), vsync: this);
    // 2.動畫添加Curve效果
    animation = CurvedAnimation(parent: controller, curve: Curves.elasticInOut, reverseCurve: Curves.easeOut);
    // 3.監聽動畫
    // 4.控制動畫的翻轉
    animation.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        controller.reverse();
      } else if (status == AnimationStatus.dismissed) {
        controller.forward();
      }
    });
    // 5.設置值的範圍
    animation = Tween(begin: 50.0, end: 120.0).animate(controller);
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: HeatAnimationWidget(animation),
    );
  }

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

AnimatedBuilder

經過AnimatedWidget類,咱們能夠從動畫中分離出組件,從而將動畫和組件分離開來,不過動畫的渲染過程仍然在AnimatedWidget中執行。若是想要將動畫的渲染過程分離出來,可使用AnimatedBuilder類,與AnimatedWidget的做用相似,AnimatedBuilder能夠自動監聽Animation的變化,而後根據須要自動刷新視圖。

所以,在上面的示例中,咱們可使用AnimatedBuilder進行以下的優化:

class HeartAnimationPage extends StatefulWidget {

  HeartAnimationPage({Key key}): super(key: key);
  @override
  _HeartAnimationPageState createState() => _HeartAnimationPageState();
}

class _HeartAnimationPageState extends State<HeartAnimationPage> with SingleTickerProviderStateMixin {

  AnimationController controller;
  Animation<double> animation;

  @override
  void initState() {
    super.initState();
    // 1.建立AnimationController
    controller = AnimationController(duration: Duration(seconds: 1), vsync: this);
    // 2.動畫添加Curve效果
    animation = CurvedAnimation(parent: controller, curve: Curves.elasticInOut, reverseCurve: Curves.easeOut);
    // 3.監聽動畫
    // 4.控制動畫的翻轉
    animation.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        controller.reverse();
      } else if (status == AnimationStatus.dismissed) {
        controller.forward();
      }
    });
    // 5.設置值的範圍
    animation = Tween(begin: 50.0, end: 120.0).animate(controller);
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: AnimatedBuilder(
        animation: animation,
        builder: (ctx, child) {
          return Icon(Icons.favorite, color: Colors.red, size: animation.value,);
        },
      )
    );
  }

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

除了上面介紹的動畫外,Flutter還提供了交錯動畫和Hero動畫。

相關文章
相關標籤/搜索