Flutter官方文檔動畫示例小結

目錄

  • 基礎動畫概念和動畫類
  • 原始無動畫部件
  • 動畫示例1(從小變大過渡)
  • 動畫示例2(使用 AnimatedWidget )
  • 動畫示例3(無限循環動畫)
  • 動畫示例4(分離部件與動畫)
  • 動畫示例5(非線性動畫)

基礎動畫概念和動畫類

  • Animation Flutter動畫庫的核心類,任何動畫都基於Animation
  • Animation對象能夠知道當前動畫的狀態(value值),不過屏幕上顯示的效果是一律不知的,即Animation對象只負責動畫過程當中的數值變化
  • AnimationController管理動畫,例如播放、反轉,停用等
  • CurvedAnimation用於實現非線性動畫,如快進慢出,慢進快出
  • Tween區間過渡插值,如數字從0到255,動畫從黃到綠,分別能夠用IntTweenColorTween

原始無動畫部件

下面是一個沒有動畫,寬高固定爲300的部件(Widget)bash

import 'package:flutter/material.dart';

void main() => runApp(LogoApp());

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

class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        margin: EdgeInsets.symmetric(vertical: 10),
        // 寬高固定
        height: 300,
        width: 300,
        child: FlutterLogo(),
      ),
    );
  }
}

複製代碼

動畫示例1(從小變大過渡)

實現圖片從小到大(從寬高爲0到寬高爲300),步驟以下:less

  1. 引入動畫庫import 'package:flutter/animation.dart';,並混入SingleTickerProviderStateMixin
  2. 建立動畫控制類,此處能夠定義動畫的時長
  3. 利用Tween建立動畫類,令值在0~300之間變化
  4. 利用addListener方法調用setState更新value
import 'package:flutter/material.dart';
import 'package:flutter/animation.dart';

void main() => runApp(LogoApp());

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

class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
  // 定義動畫變量
  Animation<double> animation;
  AnimationController controller;

  @override
  void initState() {
    super.initState();
    // 實例化控制類,時長3秒, vsync能夠在當前動畫對用戶不可見時節省機器資源(不進行動畫計算)
    controller =
        AnimationController(duration: const Duration(seconds: 2), vsync: this);
    // 在0~300之間變更
    animation = Tween<double>(begin: 0, end: 300).animate(controller)
      ..addListener(() {
        // 經過setState令動畫中的value產生變化
        setState(() {});
      });

    // 開始播放動畫
    controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        margin: EdgeInsets.symmetric(vertical: 10),
        // setState時value的變化產生在這裏
        height: animation.value,
        width: animation.value,
        child: FlutterLogo(),
      ),
    );
  }

  @override
  void dispose() {
    // 註銷動畫實例,釋放內存
    controller.dispose();
    super.dispose();
  }
}
複製代碼

動畫示例2(使用 AnimatedWidget )

使用AnimatedWidget組件簡化示例1的代碼,再也不須要手動調用setState,步驟以下:ide

  1. 使用AnimatedWidget建立動畫部件
  2. 在構造參數中接收Animation參數,並傳遞給父構造函數的listenable參數
  3. build中經過listenable得到動畫中的value
import 'package:flutter/material.dart';
import 'package:flutter/animation.dart';

void main() => runApp(LogoApp());

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

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

  @override
  void initState() {
    super.initState();
    controller =
        AnimationController(duration: const Duration(seconds: 2), vsync: this);
    // 再也不須要手動setState
    animation = Tween<double>(begin: 0, end: 300).animate(controller);
    controller.forward();
  }

  @override
  Widget build(BuildContext context) => AnimatedLogo(
        animation: animation,
      );

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

class AnimatedLogo extends AnimatedWidget {
  // 構造參數中的第二個參數爲Animation,傳遞給父構造函數的listenable
  AnimatedLogo({Key key, Animation<double> animation})
      : super(key: key, listenable: animation);

  Widget build(BuildContext context) {
    // 經過listenable獲取value
    final Animation<double> animation = listenable;
    return Center(
      child: Container(
        margin: EdgeInsets.symmetric(vertical: 10),
        height: animation.value,
        width: animation.value,
        child: FlutterLogo(),
      ),
    );
  }
}
複製代碼

動畫示例3(無限循環動畫)

監控動畫狀態進度,並在過程當中根據狀態改變更畫,實現動畫的無限循環,步驟以下:函數

  1. 增長狀態監聽addStatusListener
  2. 在監聽到動畫狀態爲completed(播放完畢)時,調用reverse()方法反轉動畫,使動畫回到初始狀態
  3. 動畫回到初始狀態後中止時,狀態爲dismissed,此時再調用forward()方法繼續播放,實現無限循環
import 'package:flutter/material.dart';
import 'package:flutter/animation.dart';

void main() => runApp(LogoApp());

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

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

  @override
  void initState() {
    super.initState();
    controller =
        AnimationController(duration: const Duration(seconds: 2), vsync: this);
    /***
     * 經過addStatusListener得到當前動畫進度,實現無限循環的動畫
     *
     * AnimationStatus的四個狀態
     * 1. AnimationStatus.forward   動畫播放
     * 2. AnimationStatus.completed 動畫播放完成
     * 3. AnimationStatus.dismissed 動畫回到初始狀態後中止
     * 4. AnimationStatus.reverse   動畫反轉
     */
    animation = Tween<double>(begin: 0, end: 300).animate(controller)
      ..addStatusListener((status) {
        if (status == AnimationStatus.completed) {
          // 動畫完成後反轉
          controller.reverse();
        } else if (status == AnimationStatus.dismissed) {
          // 反轉回初始狀態時繼續播放,實現無限循環
          controller.forward();
        }
      });
    // 播放動畫
    controller.forward();
  }

  @override
  Widget build(BuildContext context) => AnimatedLogo(
        animation: animation,
      );

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

class AnimatedLogo extends AnimatedWidget {
  // 構造參數中的第二個參數爲Animation,傳遞給父構造函數的listenable
  AnimatedLogo({Key key, Animation<double> animation})
      : super(key: key, listenable: animation);

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

複製代碼

動畫示例4(分離部件與動畫)

在前面的動畫中能夠看到,部件顯示的內容和動畫混合在一塊兒了,Flutter官網給出了一個可使動畫和部件內容分離的寫法 按我理解,全部只是改變寬高(僅當前示例)的內容部件均可以使用這個GrowTransition動畫過渡部件 步驟以下:動畫

  1. 將部件內容建立爲一個單獨的內容部件
  2. 將過渡動畫也建立爲一個單獨的部件,並使用AnimatedBuilder編寫過渡的部分
  3. 將動畫部件和內容部件組合在一塊兒
import 'package:flutter/material.dart';
import 'package:flutter/animation.dart';

void main() => runApp(LogoApp());

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

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

  @override
  void initState() {
    super.initState();
    // 建立動畫
    controller =
        AnimationController(duration: const Duration(seconds: 2), vsync: this);
    animation = Tween<double>(begin: 0, end: 300).animate(controller);
    controller.forward();
  }

  // 將內容部件和動畫組合起來
  @override
  Widget build(BuildContext context) => GrowTransition(
        animation: animation,
        child: LogoWidget(),
      );

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

// 內容部件
class LogoWidget extends StatelessWidget {
  Widget build(BuildContext context) => Container(
        margin: EdgeInsets.symmetric(vertical: 10),
        child: FlutterLogo(),
      );
}

// 動畫過渡部件
class GrowTransition extends StatelessWidget {
  GrowTransition({this.child, this.animation});

  // 須要有動畫過渡效果的部件
  final Widget child;
  // 爲部件使用的動畫
  final Animation<double> animation;

  Widget build(BuildContext context) => Center(
        /**
         * AnimatedBuilder將寬高定義爲過渡狀態
         * 按我理解,全部只是改變寬高的內容部件均可以使用這個GrowTransition動畫過渡部件
         * 固然也能夠定義一個只改變透明度的動畫過渡部件複用
         */
        child: AnimatedBuilder(
            animation: animation,
            builder: (context, child) => Container(
                  height: animation.value,
                  width: animation.value,
                  child: child,
                ),
            child: child),
      );
}

複製代碼

動畫示例5(非線性動畫)

非線性動畫很好理解,就慢進快出,快進慢出等。使用CurvedAnimation類,步驟以下:ui

  1. 建立AnimationController,定義相關參數
  2. 建立CurvedAnimation,是得其parent參數爲前一步建立的動畫控制器,參數curve爲動畫效果
  3. 建立Tween過渡值,調用evaluate()方法,參數即爲上一步建立的CurvedAnimation對象
  4. 接着就調用forward()播放動畫便可
import 'package:flutter/material.dart';
import 'package:flutter/animation.dart';

void main() => runApp(LogoApp());

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

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

  @override
  void initState() {
    super.initState();
    controller =
        AnimationController(duration: const Duration(seconds: 2), vsync: this);
    // 曲線動畫的核心的代碼
    animation = CurvedAnimation(parent: controller, curve: Curves.easeOut);
    controller.forward();
  }

  @override
  Widget build(BuildContext context) => AnimatedLogo(
        animation: animation,
      );

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

class AnimatedLogo extends AnimatedWidget {
  // 曲線動畫使用的過渡效果
  static final _sizeTween = Tween<double>(begin: 0, end: 300);

  // 構造參數中的第二個參數爲Animation,傳遞給父構造函數的listenable
  AnimatedLogo({Key key, Animation<double> animation})
      : super(key: key, listenable: animation);

  Widget build(BuildContext context) {
    // 經過listenable獲取value
    final Animation<double> animation = listenable;
    return Center(
      child: Container(
        margin: EdgeInsets.symmetric(vertical: 10),
        // 曲線動畫部分開始
        height: _sizeTween.evaluate(animation),
        width: _sizeTween.evaluate(animation),
        // 曲線動畫部分結束
        child: FlutterLogo(),
      ),
    );
  }
}
複製代碼

補充,另外一個簡單示例(修改動畫示例1便可)this

/**
     *  非曲線動畫的的另外一個示例
     *  把animate的參數從controller改爲animate便可
     */
    final Animation curve = CurvedAnimation(parent: controller, curve: Curves.easeOut);
    animation = Tween<double>(begin: 0, end: 300).animate(curve);
    controller.forward();
複製代碼

參考

Flutter Animationslua

相關文章
相關標籤/搜索