隱式動畫 Widget


要在 Flutter 中使用動畫,可以有多種不同的方式,這視你的目的而定,可以觀看〈How to choose which Flutter Animation Widget is right for you? 〉中的說明,得到些初淺的概念。

簡單來說,如果你只是想讓 Widget 在尺寸、透明度等方面,可以展現出一些動畫,Flutter 提供了一些既有的元件,可以讓你輕鬆展現效果,如果這些元件無法滿足,才進一步考慮自行控制 AnimationController 等細節來做這些事,如果你希望的是更複雜的繪圖變化(像是遊戲畫面),就要瞭解更多動畫的底層細節。

這邊會先從讓 Widget 在特性變化方面,可以展現出一些動畫效果開始,首先來看個範例:

import 'package:flutter/material.dart';

void main() => runApp(
  MaterialApp(
    home: Caterpillar(),
  )
);

class Caterpillar extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _CaterpillarState();
}

class _CaterpillarState extends State<Caterpillar> {
  var zoom_in = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(
            'openhome.cc'
        ),
      ),
      body: Center(
        child: Container(
          width: zoom_in ? 100 : 300,
          child: Image.network('https://openhome.cc/Gossip/images/caterpillar.jpg')
        )
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => setState(() {
          zoom_in = !zoom_in;
        }), 
        tooltip: zoom_in ? 'Zoom in' : 'Zoom out',
        child: Icon(zoom_in ? Icons.zoom_in : Icons.zoom_out),
      ),
    );
  }
}

這個範例在按下按鈕後,只會純綷地放大或縮小圖案,沒有動畫效果:

隱式動畫 Widget

如果想以漸漸放大或縮小的動畫來展現呢?你需要做的,就是將這段:

  ...
  body: Center(
    child: Container(
      width: zoom_in ? 100 : 300,
      child: Image.network('https://openhome.cc/Gossip/images/caterpillar.jpg')
    )
  )

改為這段:

  ...
  body: Center(
    child: AnimatedContainer(
      width: zoom_in ? 100 : 300,
      child: Image.network('https://openhome.cc/Gossip/images/caterpillar.jpg'),
      duration: Duration(seconds: 1),
    )
  ),

最大的差別就是,使用了 AnimatedContainer 來代替 Container,並設定了 duration 為 1 秒,也就是設定動畫持續時間,就可以呈現出以下的效果:

隱式動畫 Widget

100 與 300 之間的尺寸變化,AnimatedContainer 預設會使用線性內插來計算,這可以由 curve 特性控制,它的預設值是 Curves.linear,在 Curves 文件,你可以看到各種不同的變化曲線,其中也有標示各曲線大致的效果展示,例如,改用 Curves.slowMiddle

  ...
  body: Center(
    child: AnimatedContainer(
      width: zoom_in ? 100 : 300,
      child: Image.network('https://openhome.cc/Gossip/images/caterpillar.jpg'),
      duration: Duration(seconds: 1),
      curve: Curves.slowMiddle,
    )
  ),

來看看效果:

隱式動畫 Widget

AnimatedContainer 這類元件,只要簡單地設置 durationcurve 之類特性,無需接觸 AnimationController 等細節,被稱為隱式動畫 Widget,一切細節都由 AnimatedContainer隱式地控制,AnimatedContainer 的文件 中可以看到,它是 ImplicitlyAnimatedWidget 的子類:

Widget > StatefulWidget > ImplicitlyAnimatedWidget > AnimatedContainer

可以控制並不限於尺寸,AnimatedContainer 的文件 中可以看到還包含了顏色的漸變效果範例:

隱式動畫 Widget

ImplicitlyAnimatedWidget 的文件 中,可以看到一些與非動畫元件相對應的隱式動畫元件,像是 AnimatedAlign 對應了 AlignAnimatedOpacity 對應了 Opacity 等,對於 Widget 元件特性變更時希望展現動畫效果,可以先看看這些元件是否適用。