跳至主要內容

交錯式動畫

交錯動畫是一個簡單的概念:視覺變化以一系列操作發生,而不是同時發生。動畫可以是純粹循序的,一個變化接一個變化地發生,或者它可以部分或完全重疊。它也可能會有間隙,其中沒有發生任何變化。

本指南說明如何在 Flutter 中建立交錯動畫。

以下影片示範了 basic_staggered_animation 執行的動畫


在影片中,您會看到單一 Widget 的以下動畫,該動畫一開始是一個帶有稍微圓角的藍色方塊。方塊會依以下順序進行變更

  1. 淡入
  2. 變寬
  3. 在向上移動的同時變得更高
  4. 轉換成帶有邊框的圓形
  5. 將顏色變更為橘色

在向前執行後,動畫會反向執行。

交錯動畫的基本結構

#

下圖顯示 basic_staggered_animation 範例中使用的 Interval。您可能會注意到以下特性

  • 不透明度在時間軸的前 10% 期間變更。
  • 不透明度變更與寬度變更之間會出現一個很小的間隙。
  • 在時間軸的最後 25% 期間沒有動畫。
  • 增加邊距會讓 Widget 看起來像是向上移動。
  • 將邊框圓角半徑增加到 0.5,會將帶有圓角的方形轉換成圓形。
  • 邊距和高度變更發生在完全相同的間隔內,但它們不必如此。

Diagram showing the interval specified for each motion

若要設定動畫

  • 建立一個管理所有 AnimationAnimationController
  • 為每個要進行動畫的屬性建立一個 Tween
    • Tween 定義值的範圍。
    • Tweenanimate 方法需要 parent 控制器,並產生該屬性的 Animation
  • Animationcurve 屬性上指定間隔。

當控制動畫的值變更時,新動畫的值會變更,從而觸發 UI 更新。

以下程式碼會建立 width 屬性的補間。它會建立一個 CurvedAnimation,指定一個緩和曲線。請參閱 Curves 以取得其他可用的預先定義動畫曲線。

dart
width = Tween<double>(
  begin: 50.0,
  end: 150.0,
).animate(
  CurvedAnimation(
    parent: controller,
    curve: const Interval(
      0.125,
      0.250,
      curve: Curves.ease,
    ),
  ),
),

beginend 值不必是 double。以下程式碼會使用 BorderRadius.circular(),為 borderRadius 屬性(控制方形邊角的圓度)建立補間。

dart
borderRadius = BorderRadiusTween(
  begin: BorderRadius.circular(4),
  end: BorderRadius.circular(75),
).animate(
  CurvedAnimation(
    parent: controller,
    curve: const Interval(
      0.375,
      0.500,
      curve: Curves.ease,
    ),
  ),
),

完整的交錯動畫

#

與所有互動式 Widget 一樣,完整的動畫由一對 Widget 組成:一個無狀態和一個有狀態 Widget。

無狀態 Widget 指定 Tween,定義 Animation 物件,並提供一個 build() 函式,該函式負責建立 Widget 樹狀結構的動畫部分。

有狀態 Widget 建立控制器、播放動畫,並建立 Widget 樹狀結構的非動畫部分。當在螢幕中的任何位置偵測到點擊時,動畫就會開始。

basic_staggered_animation 的完整程式碼位於 main.dart 中

無狀態 Widget:StaggerAnimation

#

在無狀態 Widget StaggerAnimation 中,build() 函式會執行個體化一個 AnimatedBuilder——一個用於建立動畫的通用 Widget。AnimatedBuilder 會建立一個 Widget,並使用 Tween 的目前值來設定其設定。此範例會建立一個名為 _buildAnimation() 的函式(執行實際的 UI 更新),並將其指派給其 builder 屬性。AnimatedBuilder 會監聽來自動畫控制器的通知,並在值變更時將 Widget 樹狀結構標示為「髒」。對於動畫的每個刻度,值都會更新,從而導致呼叫 _buildAnimation()

dart
class StaggerAnimation extends StatelessWidget {
  StaggerAnimation({super.key, required this.controller}) :

    // Each animation defined here transforms its value during the subset
    // of the controller's duration defined by the animation's interval.
    // For example the opacity animation transforms its value during
    // the first 10% of the controller's duration.

    opacity = Tween<double>(
      begin: 0.0,
      end: 1.0,
    ).animate(
      CurvedAnimation(
        parent: controller,
        curve: const Interval(
          0.0,
          0.100,
          curve: Curves.ease,
        ),
      ),
    ),

    // ... Other tween definitions ...
    );

  final AnimationController controller;
  final Animation<double> opacity;
  final Animation<double> width;
  final Animation<double> height;
  final Animation<EdgeInsets> padding;
  final Animation<BorderRadius?> borderRadius;
  final Animation<Color?> color;

  // This function is called each time the controller "ticks" a new frame.
  // When it runs, all of the animation's values will have been
  // updated to reflect the controller's current value.
  Widget _buildAnimation(BuildContext context, Widget? child) {
    return Container(
      padding: padding.value,
      alignment: Alignment.bottomCenter,
      child: Opacity(
        opacity: opacity.value,
        child: Container(
          width: width.value,
          height: height.value,
          decoration: BoxDecoration(
            color: color.value,
            border: Border.all(
              color: Colors.indigo[300]!,
              width: 3,
            ),
            borderRadius: borderRadius.value,
          ),
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      builder: _buildAnimation,
      animation: controller,
    );
  }
}

有狀態 Widget:StaggerDemo

#

有狀態 Widget StaggerDemo 會建立 AnimationController(統籌一切的控制器),指定 2000 毫秒的持續時間。它會播放動畫,並建立 Widget 樹狀結構的非動畫部分。當在螢幕中偵測到點擊時,動畫就會開始。動畫會向前執行,然後向後執行。

dart
class StaggerDemo extends StatefulWidget {
  @override
  State<StaggerDemo> createState() => _StaggerDemoState();
}

class _StaggerDemoState extends State<StaggerDemo>
    with TickerProviderStateMixin {
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();

    _controller = AnimationController(
      duration: const Duration(milliseconds: 2000),
      vsync: this,
    );
  }

  // ...Boilerplate...

  Future<void> _playAnimation() async {
    try {
      await _controller.forward().orCancel;
      await _controller.reverse().orCancel;
    } on TickerCanceled {
      // The animation got canceled, probably because it was disposed of.
    }
  }

  @override
  Widget build(BuildContext context) {
    timeDilation = 10.0; // 1.0 is normal animation speed.
    return Scaffold(
      appBar: AppBar(
        title: const Text('Staggered Animation'),
      ),
      body: GestureDetector(
        behavior: HitTestBehavior.opaque,
        onTap: () {
          _playAnimation();
        },
        child: Center(
          child: Container(
            width: 300,
            height: 300,
            decoration: BoxDecoration(
              color: Colors.black.withOpacity(0.1),
              border: Border.all(
                color:  Colors.black.withOpacity(0.5),
              ),
            ),
            child: StaggerAnimation(controller:_controller.view),
          ),
        ),
      ),
    );
  }
}