Flutter中的動畫

參考來源:https://flutterchina.club/animations/

思維導(dǎo)圖.jpg

Widget執(zhí)行動畫的方法.jpg

動畫類型

  • 補(bǔ)間(Tween)動畫
    :在補(bǔ)間動畫中,定義了開始點(diǎn)和結(jié)束點(diǎn)、時間線以及定義轉(zhuǎn)換時間和速度的曲線,然后由框架自動計(jì)算如何從開始點(diǎn)過渡到結(jié)束點(diǎn)。

  • 基于物理的動畫
    :在基于物理的動畫中,運(yùn)動被模擬為與真實(shí)世界的行為相似。例如,當(dāng)你擲球時,它在何處落地,取決于拋球速度有多快、球有多重、距離地面有多遠(yuǎn)。 類似地,將連接在彈簧上的球落下(并彈起)與連接到繩子上的球放下的方式也是不同。

常見的動畫模式

基本的動畫概念和類

Animation對象:

  • Animation對象是Flutter動畫庫中的一個核心類,它生成指導(dǎo)動畫的值。
  • Animation對象知道動畫的當(dāng)前狀態(tài)(例如,它是開始、停止還是向前或向后移動),但它不知道屏幕上顯示的內(nèi)容。
  • Flutter中的Animation對象是一個在一段時間內(nèi)依次生成一個區(qū)間之間值的類。Animation對象的輸出可以是線性的、曲線的、一個步進(jìn)函數(shù)或者任何其他可以設(shè)計(jì)的映射。 根據(jù)Animation對象的控制方式,動畫可以反向運(yùn)行,甚至可以在中間切換方向。
  • Animation還可以生成除double之外的其他類型值,如:Animation<Color> 或 Animation<Size>
  • Animation對象有狀態(tài),可以通過訪問其value屬性獲取動畫的當(dāng)前值
  • Animation對象本身和UI渲染沒有任何關(guān)系

Interval

在[begin]之前為0.0的曲線,然后根據(jù)[curve曲線]在[end]時從0.0到1.0,然后是1.0。
可以使用[Interval]來延遲動畫。例如,使用[Interval]將[begin]設(shè)置為0.5,將[end]設(shè)置為1.0的[Interval]使用[Interval],這6秒的動畫將本質(zhì)上變成三秒鐘后開始的動畫。

CurvedAnimation:將動畫過程定義為一個非線性曲線,屬于Animation<double>類型

final CurvedAnimation curve =
    new CurvedAnimation(parent: controller, curve: Curves.easeIn);

注: Curves 類類定義了許多常用的曲線,也可以創(chuàng)建自己的,例如:

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

AnimationController:

  • AnimationController是一個特殊的Animation對象,在屏幕刷新的每一幀,就會生成一個新的值,默認(rèn)情況下,AnimationController在給定的時間段內(nèi)會線性的生成從0.0到1.0的數(shù)字
  • 屬于Animation<double>類型
  • 具有控制動畫的方法,例如,.forward()方法可以啟動動畫
  • 當(dāng)創(chuàng)建一個AnimationController時,需要傳遞一個vsync參數(shù),存在vsync時會防止屏幕外動畫(動畫的UI不在當(dāng)前屏幕時)消耗不必要的資源。
    通過將SingleTickerProviderStateMixin添加到類定義中,可以將stateful對象作為vsync的值。如果要使用自定義的State對象作為vsync時,請包含TickerProviderStateMixin。
// 下面代碼創(chuàng)建一個Animation對象,但不會啟動它運(yùn)行:
final AnimationController controller = new AnimationController(
    duration: const Duration(milliseconds: 2000), vsync: this);

Tween:

  • 默認(rèn)情況下,AnimationController對象的范圍從0.0到1.0,使用Tween使動畫可以生成不同的范圍或數(shù)據(jù)類型的值。
  • Tween是一個無狀態(tài)(stateless)對象,需要begin和end值,Tween的唯一作用就是定義從輸入范圍到輸出范圍的映射。
  • Tween繼承自Animatable<T>,而不是繼承自Animation<T>。Animatable與Animation相似,不是必須輸出double值。例如,ColorTween指定兩種顏色之間的過渡。
  • evaluate(Animation<double> animation)方法將映射函數(shù)應(yīng)用于動畫當(dāng)前值,Animation對象的當(dāng)前值可以通過value()方法取到。
  • 要使用Tween對象,請調(diào)用其animate()方法,傳入一個控制器對象(Tween.animate),注意animate()返回的是一個Animation,而不是一個Animatable。
// 以下示例,Tween生成從-200.0到0.0的值
final Tween doubleTween = new Tween<double>(begin: -200.0, end: 0.0);
// ColorTween指定兩種顏色之間的過渡
final Tween colorTween =
    new ColorTween(begin: Colors.transparent, end: Colors.black54);
// 以下代碼在500毫秒內(nèi)生成從0到255的整數(shù)值
final AnimationController controller = new AnimationController(
    duration: const Duration(milliseconds: 500), vsync: this);
Animation<int> alpha = new IntTween(begin: 0, end: 255).animate(controller);
// 以下示例構(gòu)建了一個控制器、一條曲線和一個Tween:
final AnimationController controller = new AnimationController(
    duration: const Duration(milliseconds: 500), vsync: this);
final Animation curve =
    new CurvedAnimation(parent: controller, curve: Curves.easeOut);
Animation<int> alpha = new IntTween(begin: 0, end: 255).animate(curve);

動畫通知

  • 一個Animation對象可以擁有Listeners和StatusListeners監(jiān)聽器,可以用addListener()和addStatusListener()來添加
  • 只要動畫的值發(fā)生變化,就會調(diào)用監(jiān)聽器
  • 動畫開始、結(jié)束、向前移動或向后移動(如AnimationStatus所定義)時會調(diào)用StatusListener
  • 一個Listener最常見的行為是調(diào)用setState()來觸發(fā)UI重建。

動畫示例
要使用Animation<>對象進(jìn)行渲染,請將Animation對象存儲為Widget的成員,然后使用其value值來決定如何繪制

考慮下面的應(yīng)用程序,它繪制Flutter logo時沒有動畫:

import 'package:flutter/material.dart';

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

class _LogoAppState extends State<LogoApp> {
  Widget build(BuildContext context) {
    return new Center(
      child: new Container(
        margin: new EdgeInsets.symmetric(vertical: 10.0),
        height: 300.0,
        width: 300.0,
        child: new FlutterLogo(),
      ),
    );
  }
}

void main() {
  runApp(new LogoApp());
}

修改以上代碼,通過一個逐漸放大的動畫顯示logo。定義AnimationController時,必須傳入一個vsync對象。

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

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

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

  initState() {
    super.initState();
    controller = new AnimationController(
        duration: const Duration(milliseconds: 2000), vsync: this);
    animation = new Tween(begin: 0.0, end: 300.0).animate(controller)
      ..addListener(() {
        setState(() {
          // the state that has changed here is the animation object’s value
        });
      });
    controller.forward();
  }

  Widget build(BuildContext context) {
    return new Center(
      child: new Container(
        margin: new EdgeInsets.symmetric(vertical: 10.0),
        height: animation.value,
        width: animation.value,
        child: new FlutterLogo(),
      ),
    );
  }

  dispose() {
    controller.dispose();
    super.dispose();
  }
}

void main() {
  runApp(new LogoApp());
}

該addListener()函數(shù)調(diào)用了setState(),所以每次動畫生成一個新的數(shù)字時,當(dāng)前幀被標(biāo)記為臟(dirty),這會導(dǎo)致widget的build()方法再次被調(diào)用。 在build()中,改變container大小,因?yàn)樗母叨群蛯挾痊F(xiàn)在使用的是animation.value。動畫完成時釋放控制器(調(diào)用dispose()方法)以防止內(nèi)存泄漏。

用AnimatedWidget簡化

  • 使用AnimatedWidget助手類(而不是addListener()和setState())來給widget添加動畫
  • 使用AnimatedWidget創(chuàng)建一個可重用動畫的widget。要從widget中分離出動畫過渡,請使用AnimatedBuilder
  • Flutter API提供的關(guān)于AnimatedWidget的示例包括:AnimatedBuilder、AnimatedModalBarrier、DecoratedBoxTransition、FadeTransition、PositionedTransition、RelativePositionedTransition、RotationTransition、ScaleTransition、SizeTransition、SlideTransition。
  • AnimatedWidget類允許您從setState()調(diào)用中的動畫代碼中分離出widget代碼。AnimatedWidget不需要維護(hù)一個State對象來保存動畫。

在下面的重構(gòu)示例中,LogoApp現(xiàn)在繼承自AnimatedWidget而不是StatefulWidget。AnimatedWidget在繪制時使用動畫的當(dāng)前值。LogoApp仍然管理著AnimationController和Tween。

// Demonstrate a simple animation with AnimatedWidget

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();
  }
}

void main() {
  runApp(new LogoApp());
}

LogoApp將Animation對象傳遞給基類并用animation.value設(shè)置容器的高度和寬度,因此它的工作原理與之前完全相同。

監(jiān)視動畫的過程

  • 使用addStatusListener來處理動畫狀態(tài)更改的通知,例如啟動、停止或反轉(zhuǎn)方向。

知道動畫何時改變狀態(tài)通常很有用的,如完成、前進(jìn)或倒退。你可以通過addStatusListener()來得到這個通知。

// 以下代碼用來監(jiān)聽動態(tài)狀態(tài)更改并打印更新:
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)
      ..addStatusListener((state) => print("$state"));
    controller.forward();
  }
  //...
}

運(yùn)行此代碼將輸出以下內(nèi)容:

AnimationStatus.forward
AnimationStatus.completed

接下來,使用addStatusListener()在開始或結(jié)束時反轉(zhuǎn)動畫。這產(chǎn)生了循環(huán)效果:

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);

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

用AnimatedBuilder重構(gòu)

  • AnimatedBuilder用于將widget與動畫分離
  • AnimatedBuilder了解如何渲染過渡.
  • AnimatedBuilder 不知道如何渲染widget,也不知道如何管理Animation對象。
  • 使用AnimatedBuilder將動畫描述為另一個widget的build方法的一部分。如果你只是想用可復(fù)用的動畫定義一個widget,請使用AnimatedWidget。
  • Flutter API中AnimatedBuilder的示例包括: BottomSheet、ExpansionTile、 PopupMenu、ProgressIndicator、RefreshIndicator、Scaffold、SnackBar、TabBar、TextField。
  • 與AnimatedWidget類似,AnimatedBuilder自動監(jiān)聽來自Animation對象的通知,并根據(jù)需要將該控件樹標(biāo)記為臟(dirty),因此不需要手動調(diào)用addListener()。

從widget樹的底部開始,渲染logo的代碼直接明了:

class LogoWidget extends StatelessWidget {
  // Leave out the height and width so it fills the animating parent
  build(BuildContext context) {
    return new Container(
      margin: new EdgeInsets.symmetric(vertical: 10.0),
      child: new FlutterLogo(),
    );
  }
}
class GrowTransition extends StatelessWidget {
  GrowTransition({this.child, this.animation});

  final Widget child;
  final Animation<double> animation;

  Widget build(BuildContext context) {
    return new Center(
      child: new AnimatedBuilder(
          animation: animation,
          builder: (BuildContext context, Widget child) {
            return new Container(
                height: animation.value, width: animation.value, child: child);
          },
          child: child),
    );
  }
}
class LogoApp extends StatefulWidget {
  _LogoAppState createState() => new _LogoAppState();
}

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

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

  Widget build(BuildContext context) {
    return new GrowTransition(child: new LogoWidget(), animation: animation);
  }

  dispose() {
    controller.dispose();
    super.dispose();
  }
}

void main() {
  runApp(new LogoApp());
}

initState()方法創(chuàng)建一個AnimationController和一個Tween,然后通過animate()綁定它們。魔術(shù)發(fā)生在build()方法中,該方法返回一個帶有LogoWidget作為子對象的GrowTransition對象,以及一個用于驅(qū)動過渡的動畫對象。

并行動畫

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(),
        ),
      ),
    );
  }
}

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

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

  initState() {
    super.initState();
    controller = new AnimationController(
        duration: const Duration(milliseconds: 2000), vsync: this);
    animation = new CurvedAnimation(parent: controller, curve: Curves.easeIn);

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

    controller.forward();
  }

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

  dispose() {
    controller.dispose();
    super.dispose();
  }
}

void main() {
  runApp(new LogoApp());
}

AnimatedWidget的構(gòu)造函數(shù)只接受一個動畫對象。 為了解決這個問題,該示例創(chuàng)建了自己的Tween對象并顯式計(jì)算了這些值。其build方法.evaluate()在父級的動畫對象上調(diào)用Tween函數(shù)以計(jì)算所需的size和opacity值。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。