Flutter繪製-12-動畫專項-基礎使用篇

天色將變發表於2021-05-14

檢視目錄-->

動畫的本質

動畫,即,動起來的畫。

Flutter,達到60FPS,即16ms一幀,每一幀即一個畫面。

如一個Image,初始時width=300,height=300

第1幀:width=300,height=300

第2幀:width=295,height=295

第3幀:width=290,height=290

。。。。。。

。。。。。。

第58幀:width=10,height=10

第59幀:width=5,height=5

第60幀:width=0,height=0

在一秒內,從第1幀刷到60幀,你看到的就是Image的縮小效果。這就是動畫。

Image的寬高屬性在1秒內連續發生了變化,是縮放;

Image的位置屬性,在1秒內連續發生變化,是移動效果;

Text的字型大小屬性,在一段時間內發生連續變化,是字型大小的縮放效果;

。。。。。。

也就是說,widget的某個或某幾個屬性,在一段時間內發生連續變化,就是該widget對應屬性的動畫效果。

關鍵點:

  • 作用在widget的屬性上
  • 一段時間
  • 連續變化的屬性值

上面的都是思路,那flutter中具體如何實現的呢?

flutter動畫的幾個類:

  • AnimationController,設定在一個時間段內,會持續的產生0-1之間的值,可以控制動畫的執行,如正向執行、反向執行、重複等,也可以新增狀態監聽,如每一幀的回撥、整個動畫狀態的回撥等。
  • Tween,專門設定取值範圍,AnimationController預設是0-1,通過Tween可以將這個取值範圍改變,如0-100,紅色-綠色等
  • Curve,AnimationController會持續的產生一個區間的值,Curve控制的是在這個區間內,值變化的規律,比如上面一開始的例子,是線性變化,每一次固定減小5;我們也可以改變這個演算法,如依次減少60、55、50.。。。,這是一個變化放緩的效果。Flutter內建了一些效果,後面我們一一看。

你是否注意到,上面的內容,說的都是在一段時間段內如何天花亂墜的產生天花亂墜的值,跟widget一點關係都沒有。

怎麼建立關聯?

在build中:

build(){
    return Widget( xxx屬性:_AnimationController.value);
}

複製程式碼

每次AnimationController產生的值發生變化後,都會最終呼叫到build方法,讓介面發生變化,最終形成連續的效果,也就是動畫。

QQ20210429-090335-HD.gif

上面的示例程式碼:

import 'package:flutter/material.dart';

class Test01AnimationController extends StatefulWidget {
  @override
  _Test01AnimationControllerState createState() =>
      _Test01AnimationControllerState();
}

class _Test01AnimationControllerState extends State<Test01AnimationController>
    with SingleTickerProviderStateMixin {
  int _count = 0;
  double _width = 0; // 圖片寬
  double _height = 0; // 圖片高
  AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _count = 0; // addListener回撥的次數
    // 建立一個動畫,設定持續時間duration為2000ms
    _controller = new AnimationController(
        vsync: this, duration: const Duration(milliseconds: 2000));
    // 每次值發生變化就回撥該方法
    _controller.addListener(() {
      // 每次值發生變化後,就讓圖片寬高也發生變化
      setState(() {
        _count++;
        _width = 300 * _controller.value;
        _height = 300 * _controller.value;
      });
    });
    // 動畫開始執行
    _controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("AnimationController"),
        centerTitle: true,
      ),
      body: Center(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Image.asset(
              "images/a.JPG",
              width: _width,
              height: _height,
            ),
            Text("${_count}  ${_controller.value}")
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}
複製程式碼

簡短總結:

  • 動畫AnimationnController,持續產生一系列的值
  • 每產生一次值,就setState一次,讓該值作用到widget的屬性上
  • 每次setState後,build方法重新執行
  • 最終看到一個變化的widget,即動畫效果

重要的類

AnimationController

構造方法

AnimationController
AnimationController({
    double? value,
    this.duration,
    this.reverseDuration,
    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);
  }
複製程式碼

此構造方法特點:

  • 有上下值邊界
  • 在動畫執行過程中持續產生上下邊界範圍內的值,通過controller.value考驗獲取到該值。
AnimationController.unbounded
AnimationController.unbounded({
    double value = 0.0,
    this.duration,
    this.reverseDuration,
    this.debugLabel,
    required TickerProvider vsync,
    this.animationBehavior = AnimationBehavior.preserve,
  }) : assert(value != null),
       assert(vsync != null),
       lowerBound = double.negativeInfinity,
       upperBound = double.infinity,
       _direction = _AnimationDirection.forward {
    _ticker = vsync.createTicker(_tick);
    _internalSetValue(value);
  }
複製程式碼

此構造方法特點:

  • lowerBound與upperBound是無限的
  • 通過controller.value 獲取到的值是Infinity.
  • 因此該構造方法的意義在於定義了一個持續的時間段,但是該時間段內的動畫需要自己定義。通常用於物理模擬。

屬性

value

有兩個作用:

  • 在動畫執行過程中,通過controller.value獲取當前時間點動畫產生的值。
  • 在建立AnimationController時設定初始值,比如value=0.5,AnimationController預設取值範圍是0-1,那麼在動畫執行時,是從0.5開始取值,直到1。如果你設定了duration為2秒,那麼實際執行時間約為1秒。如果你設定value=1,那麼動畫不執行。也就是說,AnimationController會按照duration去產生持續變化的值,設定value後,會取value-1的這一段。
```
_controller = new AnimationController(
    vsync: this,
    duration: const Duration(milliseconds: 2000),
    value: 0.9
);
```
複製程式碼

image.png

還有一個關鍵點,value取值在lowerBound與upperBound之間時才有效果,如果value小於lowerBound,取值從lowerBound開始;如果value大於upperBound,那麼動畫相當於直接結束,不再執行。value預設值是lowerBound。

duration

動畫持續時間 #####reverseDuration 當動畫執行revrse時的持續時間,如果不設定,那麼時間按duration計算。

lowerBound

預設0.0,設定時,lowerBound要小upperBounnd,否則報錯

upperBound

預設1.0

設定了lowerBound與upperBound的例子:

import 'package:flutter/material.dart';

class Test02AnimationController extends StatefulWidget {
  @override
  _Test02AnimationControllerState createState() =>
      _Test02AnimationControllerState();
}

class _Test02AnimationControllerState extends State<Test02AnimationController>
    with SingleTickerProviderStateMixin {
  int _count = 0;
  double _width = 0; // 圖片寬
  double _height = 0; // 圖片高
  AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _count = 0; // addListener回撥的次數
    // 建立一個動畫,設定持續時間duration為2000ms
    _controller = new AnimationController(
        vsync: this,
        duration: const Duration(milliseconds: 2000),
        value: 0,
        lowerBound: 0,
        upperBound: 20,
        animationBehavior: AnimationBehavior.normal
    );
    // 每次值發生變化就回撥該方法
    _controller.addListener(() {
      // 每次值發生變化後,就讓圖片寬高也發生變化
      setState(() {
        _count++;
        _width = 30 * _controller.value;
        _height = 30 * _controller.value;
      });
    });
    // 動畫開始執行
    _controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("AnimationController"),
        centerTitle: true,
      ),
      body: Center(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Image.asset(
              "images/a.JPG",
              width: _width,
              height: _height,
            ),
            Text("${_count}  ${_controller.value}")
          ],
        ),
      ),
    );
  }

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

複製程式碼
animationBehavior

注意:此處限個人理解,做個記錄,後續研究具體情形後再來更正。目前可略過此屬性不看

flutter支援很多平臺,但是有些動畫在某些平臺就是不支援或要求簡化,而AccessibilityFeatures.disableAnimations欄位就是返回的平臺支援情況,當為true時,裝置會要求flutter儘快減少或禁用動畫。有兩種方式控制:

  • AnimationBehavior.normal,當使用new AnimationController()時預設此值,通過減少動畫持續時間來讓簡化動畫,讓其儘早結束。
  • AnimationBehavior.preserve,當使用new AnimationController.unbounded()時預設此值,AnimationController會保留其行為,我理解的是慣性。對於重複性動畫,當不考慮AccessibilityFeatures.disableAnimations時,預設就是這種行為。
vsync

註冊螢幕的每一幀回撥監聽,防止螢幕外動畫,如鎖屏時不回撥,避免消耗不必要的資源。建立AnimationController時比須傳入此值,而AnimationController通常是在State中建立,讓State with 一個SingleTickerProviderStateMixin,傳vsync時,傳入this。

Curve

先看個效果:

3.gif

Curve描述動畫過程中速度變化的過程,AnimationController預設效果是勻速的,Curve可以改變這種效果,如實現加速、減速、反彈等。實際控制的是AnimationController在duration內產生值的規則。

如何使用:

  AnimationController _controller;
  CurvedAnimation _curvedAnimation;

  @override
  void initState() {
    super.initState();
    _controller = new AnimationController(
        vsync: this, duration: const Duration(milliseconds: 3000));
    _curvedAnimation =
        new CurvedAnimation(parent: _controller, curve: Curves.ease);
    // 動畫開始執行
    _controller.repeat();
  }
  
複製程式碼
  • 依然建立AnimationController
  • 建立CurvedAnimation時,將AnimationController 和 curve傳入,通過Curses.xxx,可以獲取flutter內建的效果,當前時間點有41種。
  • 依然是使用AnimationController進行動畫的控制,如repeat、forward、reverse等。
  • 以後取值改為從CurvedAnimation獲取,如_curvedAnimation.value。

上面效果的程式碼:

import 'package:flutter/material.dart';

class Test05CurveImage extends StatefulWidget {
  @override
  _Test05CurveImageState createState() =>
      _Test05CurveImageState();
}

class _Test05CurveImageState extends State<Test05CurveImage>
    with SingleTickerProviderStateMixin {
  double _width = 0; // 圖片寬
  double _height = 0; // 圖片高
  AnimationController _controller;
  CurvedAnimation _curvedAnimation;

  @override
  void initState() {
    super.initState();
    _controller = new AnimationController(
        vsync: this, duration: const Duration(milliseconds: 5000));
    _curvedAnimation = new CurvedAnimation(parent: _controller, curve: Curves.bounceInOut);
    _controller.addListener(() {
      setState(() {
        _width = 300 * _curvedAnimation.value;
        _height = 300 * _curvedAnimation.value;
      });
    });
    // 動畫開始執行
    _controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("AnimationController"),
        centerTitle: true,
      ),
      body: Center(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Image.asset(
              "images/a.JPG",
              width: _width,
              height: _height,
            ),
            Text("_curvedAnimation.value=${_curvedAnimation.value}")
          ],
        ),
      ),
    );
  }

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

複製程式碼

Curve來源:

  • flutter內建提供
  • 自定義

Flutter提供的Curve效果

當前是41種,見效果:

QQ20210507-094246-HD.gif

程式碼如下:

import 'package:flutter/material.dart';
import 'dart:math';
class Test04Curve extends StatefulWidget {
  @override
  _Test04CurveState createState() => _Test04CurveState();
}

class _Test04CurveState extends State<Test04Curve>
    with SingleTickerProviderStateMixin {
  List<ItemModel> curveList;
  double size = 120;

  @override
  void initState() {
    super.initState();
    curveList = [
      ItemModel('linear', Curves.linear),
      ItemModel('bounceIn', Curves.bounceIn),
      ItemModel('bounceOut', Curves.bounceOut),
      ItemModel('bounceInOut', Curves.bounceInOut),
      ItemModel('ease', Curves.ease),
      ItemModel('easeIn', Curves.easeIn),
      ItemModel('easeInBack', Curves.easeInBack),
      ItemModel('easeInCirc', Curves.easeInCirc),
      ItemModel('easeInCubic', Curves.easeInCubic),
      ItemModel('easeInExpo', Curves.easeInExpo),
      ItemModel('easeInQuad', Curves.easeInQuad),
      ItemModel('easeInQuart', Curves.easeInQuart),
      ItemModel('easeInQuint', Curves.easeInQuint),
      ItemModel('easeInSine', Curves.easeInSine),
      ItemModel('easeInToLinear', Curves.easeInToLinear),
      ItemModel('easeOut', Curves.easeOut),
      ItemModel('easeOutBack', Curves.easeOutBack),
      ItemModel('easeOutCirc', Curves.easeOutCirc),
      ItemModel('easeOutCubic', Curves.easeOutCubic),
      ItemModel('easeOutExpo', Curves.easeOutExpo),
      ItemModel('easeOutQuad', Curves.easeOutQuad),
      ItemModel('easeOutQuart', Curves.easeOutQuart),
      ItemModel('easeOutQuint', Curves.easeOutQuint),
      ItemModel('easeOutSine', Curves.easeOutSine),
      ItemModel('easeInOut', Curves.easeInOut),
      ItemModel('easeInOutBack', Curves.easeInOutBack),
      ItemModel('easeInOutCirc', Curves.easeInOutCirc),
      ItemModel('easeInOutCubic', Curves.easeInOutCubic),
      ItemModel('easeInOutExpo', Curves.easeInOutExpo),
      ItemModel('easeInOutQuad', Curves.easeInOutQuad),
      ItemModel('easeInOutQuart', Curves.easeInOutQuart),
      ItemModel('easeInOutQuint', Curves.easeInOutQuint),
      ItemModel('easeInOutSine', Curves.easeInOutSine),
      ItemModel('decelerate', Curves.decelerate),
      ItemModel('elasticIn', Curves.elasticIn),
      ItemModel('elasticOut', Curves.elasticOut),
      ItemModel('elasticInOut', Curves.elasticInOut),
      ItemModel('slowMiddle', Curves.slowMiddle),
      ItemModel('fastLinearToSlowEaseIn', Curves.fastLinearToSlowEaseIn),
      ItemModel('fastOutSlowIn', Curves.fastOutSlowIn),
      ItemModel('linearToEaseOut', Curves.linearToEaseOut),
    ];
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Curve"),
        centerTitle: true,
      ),
      body: Container(
        padding: EdgeInsets.all(10),
        child: GridView.builder(
          gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
              mainAxisExtent: size + 20,
              mainAxisSpacing: 10,
              crossAxisSpacing: 10,
              maxCrossAxisExtent: size + 10),
          itemBuilder: (context, index) {
            return Container(
              padding: EdgeInsets.symmetric(horizontal: 5),
              child: Column(
                children: [
                  MyCurveWidget(curveList[index].curve, size),
                  Text(curveList[index].name, style: TextStyle(fontSize: 10)),
                ],
              ),
            );
          },
          itemCount: curveList.length,
        ),
      ),
    );
  }
}

class MyCurveWidget extends StatefulWidget {
  Curve _curve;
  double _size;

  MyCurveWidget(this._curve, this._size);

  @override
  _MyCurveWidgetState createState() => _MyCurveWidgetState();
}

class _MyCurveWidgetState extends State<MyCurveWidget>
    with SingleTickerProviderStateMixin {
  AnimationController _controller;
  CurvedAnimation _curvedAnimation;

  @override
  void initState() {
    super.initState();
    // 建立一個動畫,設定持續時間duration為2000ms
    _controller = new AnimationController(
        vsync: this, duration: const Duration(milliseconds: 6000));
    _curvedAnimation =
        new CurvedAnimation(parent: _controller, curve: widget._curve);
    // 動畫開始執行
    _controller.repeat();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      height: widget._size,
      width: widget._size,
      child: Center(
        child: CustomPaint(
          size: Size(widget._size - 10, widget._size - 10),
          painter: MyCurvePainter(_curvedAnimation),
        ),
      ),
    );
  }

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

class MyCurvePainter extends CustomPainter {
  Animation<double> _animation;

  MyCurvePainter(this._animation) : super(repaint: _animation);

  @override
  void paint(Canvas canvas, Size size) {
    translateToCenter(canvas, size);
    drawBg(canvas, size);
    drawBoll(canvas, size);
  }

  void drawBg(Canvas canvas, Size size) {
    Paint paint = Paint()
      ..color = Colors.black12
      ..strokeWidth = 1
      ..style = PaintingStyle.stroke;
    Offset p1 = Offset(-size.width / 2, 0);
    Offset p2 = Offset(size.width / 2, 0);
    canvas.drawLine(p1, p2, paint);
    canvas.drawCircle(Offset.zero, size.width / 2, paint);
  }

  void drawBoll(Canvas canvas, Size size) {
    double radius = size.width / 2;
    Paint paint = Paint()
      ..color = Colors.orangeAccent
      ..style = PaintingStyle.fill;
    canvas.drawCircle(
        Offset(-radius + size.width * _animation.value, 0), 5, paint);

    double sweepAngle = pi + pi * 2 * _animation.value;
    canvas.drawCircle(
        Offset(radius * cos(sweepAngle), radius * sin(sweepAngle)), 5, paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }

  void translateToCenter(Canvas canvas, Size size) {
    canvas.translate(size.width / 2, size.height / 2);
  }
}

class ItemModel {
  String name;
  Curve curve;

  ItemModel(this.name, this.curve);
}

複製程式碼

自定義Curve

繼承Curve,然後重寫transformInternal方法,自己定義變化的規律。 如:

class MyCurve extends Curve {
  @override
  double transformInternal(double t) {
    return sin(pi / 2 * t);
  }
}
複製程式碼
  • t的範圍在0-1之間
  • sin(pi / 2 * t) 應用t做了一個正弦函式曲線

效果如下;

4.gif

程式碼如下:

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

class Test06CustomCurve extends StatefulWidget {
  @override
  _Test06CustomCurveState createState() => _Test06CustomCurveState();
}

class _Test06CustomCurveState extends State<Test06CustomCurve>
    with SingleTickerProviderStateMixin {
  double _width = 0; // 圖片寬
  double _height = 0; // 圖片高
  AnimationController _controller;
  CurvedAnimation _curvedAnimation;

  @override
  void initState() {
    super.initState();
    _controller = new AnimationController(
        vsync: this, duration: const Duration(milliseconds: 5000));
    _curvedAnimation =
        new CurvedAnimation(parent: _controller, curve: MyCurve());
    _controller.addListener(() {
      setState(() {
        _width = 300 * _curvedAnimation.value;
        _height = 300 * _curvedAnimation.value;
      });
    });
    // 動畫開始執行
    _controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("AnimationController"),
        centerTitle: true,
      ),
      body: Center(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Image.asset(
              "images/a.JPG",
              width: _width,
              height: _height,
            ),
            Text("_curvedAnimation.value=${_curvedAnimation.value}")
          ],
        ),
      ),
    );
  }

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

class MyCurve extends Curve {
  @override
  double transformInternal(double t) {
    return sin(pi / 2 * t);
  }
}

複製程式碼

Tween

AnimationController可以設定取值範圍lowerBound-upperBound,型別是double的。Tween擴充套件了取值範圍和型別,可以是顏色、int、double等。

有兩個重要的方法:

  • evaluate
  • animate

使用evaluate

1.gif

程式碼如下;

import 'package:flutter/material.dart';

class Test07Tween extends StatefulWidget {
  @override
  _Test07TweenState createState() => _Test07TweenState();
}

class _Test07TweenState extends State<Test07Tween>
    with SingleTickerProviderStateMixin {
  double _width = 0; // 圖片寬
  double _height = 0; // 圖片高
  AnimationController _controller;
  Tween sizeTween = new Tween(begin: 0.0, end: 300.0);

  @override
  void initState() {
    super.initState();
    _controller = new AnimationController(
        vsync: this, duration: const Duration(milliseconds: 5000),upperBound: 1);
    _controller.addListener(() {
      setState(() {
        _width = sizeTween.evaluate(_controller);
        _height = sizeTween.evaluate(_controller);
      });
    });
    // 動畫開始執行
    _controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("AnimationController"),
        centerTitle: true,
      ),
      body: Center(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Image.asset(
              "images/a.JPG",
              width: _width,
              height: _height,
            ),
            Row(
              children: [
                Text("sizeTween.evaluate(_controller)="),
                Text("${sizeTween.evaluate(_controller)}")
              ],
            )
          ],
        ),
      ),
    );
  }

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

複製程式碼

關鍵點:

  • new Tween(begin: 0.0, end: 300.0),建立Tween物件,設定取值範圍
  • 正常建立controller
  • sizeTween.evaluate(_controller),evaluate方法獲取當前時間點的插值

其實到這裡有個疑問,lowerBound、upperBound與Tween同時存在會怎麼樣?上面的例子upperBound=1,現在我們改成2看下效果:

22.gif

相比upperBound=1,圖片放大了一倍。因此推斷是相乘的關係。

使用animate

使用Tween提供的animate方法,可以獲取一個新的Animation ,通過此也可以獲取value值,如下程式碼:

import 'package:flutter/material.dart';

class Test08TweenAnimate extends StatefulWidget {
  @override
  _Test08TweenAnimateState createState() => _Test08TweenAnimateState();
}

class _Test08TweenAnimateState extends State<Test08TweenAnimate>
    with SingleTickerProviderStateMixin {
  double _width = 0; // 圖片寬
  double _height = 0; // 圖片高
  AnimationController _controller;
  Animation<dynamic> _animation;
  Tween sizeTween = new Tween(begin: 0.0, end: 300.0);

  @override
  void initState() {
    super.initState();
    _controller = new AnimationController(
        vsync: this, duration: const Duration(milliseconds: 5000));
    _animation = sizeTween.animate(_controller);
    _controller.addListener(() {

      setState(() {
        _width = _animation.value;
        _height = _animation.value;
      });
    });
    // 動畫開始執行
    _controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("AnimationController"),
        centerTitle: true,
      ),
      body: Center(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Image.asset(
              "images/a.JPG",
              width: _width,
              height: _height,
            ),
            Row(
              children: [
                Text("_animation.value="),
                Text("${_animation.value}")
              ],
            )
          ],
        ),
      ),
    );
  }

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

複製程式碼

關鍵點:

  • 建立Tween:Tween sizeTween = new Tween(begin: 0.0, end: 300.0)
  • 與controller關聯: _animation = sizeTween.animate(_controller);
  • 使用:_animation.value獲取插值
  • 注意依然是使用_controller來控制動畫的開始、重複等

Interval 組合動畫

Interval是Curve的子類,定義了一個區間範圍[0,1]內取值,可以是[0.1,0.3],也可以是[0.4,0.8]。我們都知道,AnimationController整體描述的是一個動畫過程,看做是[0,1],Interval則在[0,1]內選擇了一塊區域,如[0.3,0.5],當Tween在animate時指定該Interval後,表示該Tween在0.3時開始,0.5時結束。

舉個例子,一個正方形,在動畫的0-0.6完成大小變化,0.3-1.0完成顏色變化。 效果:

4.gif

程式碼:

import 'package:flutter/material.dart';

class Test29Interval extends StatefulWidget {
  @override
  _State createState() => _State();
}

class _State extends State<Test29Interval>
    with SingleTickerProviderStateMixin {
  AnimationController _controller;

  Tween _sizeTween = new Tween<double>(begin: 0.0, end: 200.0);
  Tween _colorTween = new ColorTween(begin: Colors.orangeAccent, end: Colors.green);
  Animation<double> _animationSize;
  Animation<Color> _animationColor;
  @override
  void initState() {
    super.initState();
    _controller = new AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 6000),
    );
    _animationSize = _sizeTween.animate(new CurvedAnimation(
        parent: _controller, curve: new Interval(0.0, 0.6)));
    _animationColor = _colorTween.animate(new CurvedAnimation(
        parent: _controller, curve: new Interval(0.3, 1.0)));
    _controller.addListener(() {
      setState(() {});
    });
    // 動畫開始執行
    _controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Test29Interval"),
        centerTitle: true,
      ),
      body: Center(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Container(
              width: _animationSize.value,
              height: _animationSize.value,
              margin: EdgeInsets.all(10),
              color: _animationColor.value,
            ),
            Container(
              width: 400,
              child: Text("_animationSize.value = ${_animationSize.value}",style: TextStyle(fontSize: 14),),
            )
          ],
        ),
      ),
    );
  }

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

複製程式碼

AnimatedWidget

大家應該注意到了,上面所有的動畫變化都是通過setState來維護的,對於一個通用功能,如果每次都使用setState,顯然比較繁瑣。因此,Flutter提供了AnimatedWidget,在使用時就不用顯示的呼叫setState了。

看個例子,效果:

5.gif

程式碼:

import 'package:flutter/material.dart';

class Test30AnimatiedWidget extends StatefulWidget {
  @override
  _State createState() => _State();
}

class _State extends State<Test30AnimatiedWidget>
    with SingleTickerProviderStateMixin {
  AnimationController controller;
  Animation<double> animation;
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    controller = new AnimationController(
        duration: const Duration(milliseconds: 6000), vsync: this);
    animation = new Tween(begin: 0.0, end: 300.0).animate(controller);
    controller.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        controller.reverse();
      } else if (status == AnimationStatus.dismissed) {
        controller.forward();
      }
    });
    controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return ImageAnimatiedWidget(
      animation: animation,
    );
  }

  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
    controller.dispose();
  }
}

class ImageAnimatiedWidget extends AnimatedWidget {
  @override
  Widget build(BuildContext context) {
    Animation animation = listenable;
    return Center(
      child: Container(
        child: Image.asset(
          "images/a2.png",
          width: animation.value,
          height: animation.value,
        ),
        // width: animation.value,
        // height: animation.value,
      ),
    );
  }

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

複製程式碼

AnimatedBuilder

同AnimatedWidget類似,隱了setState,提供了另一種widget組織方式。

同上面的例子,效果:

5.gif

程式碼:

import 'package:flutter/material.dart';

class Test31AnimateBuilder extends StatefulWidget {
  @override
  _State createState() => _State();
}

class _State extends State<Test31AnimateBuilder>
    with SingleTickerProviderStateMixin {
  AnimationController controller;
  Animation<double> animation;
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    controller = new AnimationController(
        duration: const Duration(milliseconds: 6000), vsync: this);
    animation = new Tween(begin: 0.0, end: 300.0).animate(controller);
    controller.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        controller.reverse();
      } else if (status == AnimationStatus.dismissed) {
        controller.forward();
      }
    });
    controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return ImageAnimateBuilder(animation, MyImage());
  }

  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
    controller.dispose();
  }
}

class ImageAnimateBuilder extends StatelessWidget {
  final Animation animation;
  final Widget child;

  ImageAnimateBuilder(this.animation, this.child);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: AnimatedBuilder(
        animation: animation,
        child: child,
        builder: (BuildContext context, Widget child) {
          return Container(
            width: animation.value,
            height: animation.value,
            child: child,
          );
        },
      ),
    );
  }
}

class MyImage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        child: Image.asset(
          "images/a2.png",
        ),
        // width: animation.value,
        // height: animation.value,
      ),
    );
  }
}

複製程式碼

AnimatedSwitcher

這是flutter提供的一個特殊的widget,效果是對於AnimatedSwitcher的child,當發生變化時,舊值執行去的動畫,新值執行來的動畫。去和來,指的是同一個動畫的forward與reverse。

看一個例子,數字發生變化,當前數字逐漸縮小隱藏,新的數字是放大的過程。

效果:

6.gif

程式碼:

import 'package:flutter/material.dart';

class Test32AnimatedSwitcher extends StatefulWidget {
  const Test32AnimatedSwitcher({Key key}) : super(key: key);

  @override
  _State createState() => _State();
}

class _State extends State<Test32AnimatedSwitcher> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          AnimatedSwitcher(
            duration: const Duration(milliseconds: 1000),
            transitionBuilder: (Widget child, Animation<double> animation) {
              //執行縮放動畫
              return ScaleTransition(child: child, scale: animation);
            },
            child: Text(
              '$_count',
              //顯示指定key,不同的key會被認為是不同的Text,這樣才能執行動畫
              key: ValueKey<int>(_count),
              style: TextStyle(fontSize: 30),
            ),
          ),
          RaisedButton(
            child: const Text('+1',),
            onPressed: () {
              setState(() {
                _count += 1;
              });
            },
          ),
        ],
      ),
    );
  }
}
複製程式碼

相關文章