<

アニメーションのチュートリアル

このチュートリアルでは、Flutter で明示的なアニメーションを構築する方法を説明します。 いくつかの重要な概念、クラスを紹介した後、 およびアニメーション ライブラリのメソッドについて、5 つの手順を説明します。 アニメーションの例。例は相互に構築されており、 アニメーション ライブラリのさまざまな側面を紹介します。

Flutter SDK には、組み込みの明示的なアニメーションも提供されます。 そのようなFadeTransitionSizeTransition、 とSlideTransition。これらの単純なアニメーションは、 開始点と終了点を設定することでトリガーされます。 実装が簡単です ここで説明するカスタムの明示的なアニメーションよりも優れています。

アニメーションの基本的な概念とクラス

Flutter のアニメーション システムは型指定に基づいています。Animationオブジェクト。ウィジェットは次のいずれかを組み込むことができます ビルド関数内のこれらのアニメーションは、次のように直接実行されます。 現在の値を読み取り、その状態を聞く 変更することも、アニメーションをベースとして使用することもできます。 より精巧なアニメーションが引き継がれます 他のウィジェット。

アニメーション<ダブル>

Flutter では、Animationオブジェクトは何について何も知りません が画面上にあります。アンAnimationは抽象クラスです。 現在の値とその状態 (完了または拒否) を理解します。 最も一般的に使用されるアニメーション タイプの 1 つは次のとおりです。Animation<double>

アンAnimationオブジェクトが順次生成する 一定期間にわたる 2 つの値の間の補間された数値。 の出力Animationオブジェクトは線形である可能性がありますが、 曲線、ステップ関数、またはその他のマッピングを考案できます。 方法に応じて、Animationオブジェクトが制御され、 逆走したり、方向を変えたりする可能性もあります。 真ん中。

アニメーションでは、double 以外のタイプも補間できます。Animation<Color>またAnimation<Size>

アン98bf9592-0d73-4a18-8304-fc8e6eaaaacaeオブジェクトには状態があります。現在の値は でいつでも利用可能.valueメンバー。

アンAnimationオブジェクトはレンダリングについて何も知らない、またはbuild()機能。

曲線アニメーション

CurvedAnimationアニメーションの進行状況を定義します 非線形曲線として。

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

CurvedAnimationAnimationController(次のセクションで説明します) どちらもタイプですAnimation<double>, したがって、これらを交換して渡すことができます。 のCurvedAnimation変更しているオブジェクトをラップします。 サブクラス化しないAnimationController曲線を実装します。

アニメーションコントローラー

AnimationController特別ですAnimationハードウェアが壊れるたびに新しい値を生成するオブジェクト 新しいフレームの準備ができています。デフォルトでは、 のAnimationController数値を線形に生成します 指定された期間中に 0.0 から 1.0 まで変化します。 たとえば、このコードはAnimation物体、 しかし、実行は開始されません:

controller =
    AnimationController(duration: const Duration(seconds: 2), vsync: this);

AnimationControllerから派生Animation<double>, 使えるので どこにでもAnimationオブジェクトが必要です。しかしAnimationControllerにはアニメーションを制御する追加のメソッドがあります。たとえば、あなたは始めます を使ったアニメーション.forward()方法。数値の生成は、 画面の更新に関連付けられているため、通常は 1 回につき 60 個の数値が生成されます。 2番。各数値が生成された後、それぞれAnimationオブジェクトが呼び出す 添付Listenerオブジェクト。それぞれのカスタム表示リストを作成するには 子供、見てくださいRepaintBoundary

を作成するときは、AnimationController、あなたはそれを渡しますvsync口論。 の存在vsyncオフスクリーンアニメーションが消費されるのを防ぎます 不要なリソース。 を追加することで、ステートフル オブジェクトを vsync として使用できます。SingleTickerProviderStateMixinクラス定義に。 この例は次のとおりです。アニメート1GitHub 上で。

トゥイーン

デフォルトでは、AnimationControllerオブジェクトの範囲は 0.0 ~ 1.0 です。 別の範囲または別のデータ型が必要な場合は、Tweenに補間するアニメーションを設定するには 範囲またはデータ型が異なります。たとえば、 続くTween-200.0 から 0.0 まで変化します。

tween = Tween<double>(begin: -200, end: 0);

Tweenのみを受け取るステートレスオブジェクトですbeginend。 の唯一の仕事Tweenからマッピングを定義することです。 入力レンジから出力レンジまで。入力レンジは一般的に 0.0 ~ 1.0 ですが、これは必須ではありません。

Tweenから継承しますAnimatable<T>、からではありませんAnimation<T>。 アンAnimatable、 好きAnimation、double を出力する必要はありません。 例えば、ColorTween2 つの色の間の進行を指定します。

colorTween = ColorTween(begin: Colors.transparent, end: Colors.black54);

Tweenオブジェクトは状態を保存しません。代わりに、evaluate(Animation<double> animation)を使用するメソッドtransformアニメーションの現在値をマップする関数 (0.0 ~ 1.0)、実際のアニメーション値に合わせます。

現在の値Animationオブジェクトは次の場所にあります。.value方法。評価関数はハウスキーピングも実行します。 たとえば、次のときに begin と end が返されるようにするなどです。 アニメーション値はそれぞれ 0.0 と 1.0 です。

トゥイーンアニメイト

を使用するにはTweenオブジェクト、呼び出しanimate()Tween、 コントローラーオブジェクトを渡します。例えば、 次のコードは、 500 ミリ秒にわたる 0 ~ 255 の整数値。

AnimationController controller = AnimationController(
    duration: const Duration(milliseconds: 500), vsync: this);
Animation<int> alpha = IntTween(begin: 0, end: 255).animate(controller);

次の例は、コントローラー、カーブ、およびTween:

AnimationController controller = AnimationController(
    duration: const Duration(milliseconds: 500), vsync: this);
final Animation<double> curve =
    CurvedAnimation(parent: controller, curve: Curves.easeOut);
Animation<int> alpha = IntTween(begin: 0, end: 255).animate(curve);

アニメーション通知

アンAnimationオブジェクトは持つことができますListenerStatusListenerさん、 で定義されたaddListener()addStatusListener()。 あListenerアニメーションの値が変更されるたびに呼び出されます。 最も一般的な行動は、Listener電話することですsetState()再構築を引き起こすため。あStatusListenerアニメーションの開始時に呼び出されます。 で定義されるように、終了、前進、または後退します。AnimationStatus。 次のセクションでは例を示します。addListener()方法、 とアニメーションの進行状況を監視するを示しています の例addStatusListener()


アニメーションの例

このセクションでは、5 つのアニメーションの例を説明します。 各セクションには、その例のソース コードへのリンクが含まれています。

アニメーションのレンダリング

ここまでは、時間の経過とともに一連の数値を生成する方法を学習しました。 画面には何もレンダリングされていません。を使用してレンダリングするにはAnimationオブジェクトを保存するAnimationとしてのオブジェクト ウィジェットのメンバーを作成し、その値を使用して描画方法を決定します。

アニメーションを使用せずに Flutter ロゴを描画する次のアプリを考えてみましょう。

import 'package:flutter/material.dart';

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

class LogoApp extends StatefulWidget {
  const LogoApp({super.key});

  @override
  State<LogoApp> createState() => _LogoAppState();
}

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

アプリのソース: アニメイト0

以下は、アニメーション化するために変更された同じコードを示しています。 ロゴをゼロからフルサイズまで成長させます。 を定義するとき、AnimationControllerを渡す必要があります。vsync物体。のvsyncパラメータについては、AnimationControllerセクション

アニメーション化されていない例からの変更点が強調表示されています。

{animate0 → animate1}/lib/main.dart
@@ -9,16 +9,39 @@
9
9
  State<LogoApp> createState() => _LogoAppState();
10
10
  }
11
- class _LogoAppState extends State<LogoApp> {
11
+ class _LogoAppState は State<LogoApp> を拡張しますSingleTickerProviderStateMixin を使用{
12
+ 後半のアニメーション<double>アニメーション。
13
+ 後期のAnimationControllerコントローラー。
14
+
15
+ @オーバーライド
16
+ void initState() {
17
+ super.initState();
18
+ コントローラー =
19
+ AnimationController(duration:constDuration(秒:2),vsync:this);
20
+ アニメーション = Tween<double>(開始: 0、終了: 300).animate(コントローラー)
21
+ ..addListener(() {
22
+ setState(() {
23
+ // ここで変化した状態はアニメーションオブジェクトの値です。
24
+ });
25
+ });
26
+ コントローラー.フォワード();
27
+ }
28
+
12
29
  @オーバーライド
13
30
  ウィジェットのビルド(BuildContext context) {
14
31
  リターンセンター(
15
32
  子: コンテナ(
16
33
  マージン: const EdgeInsets.metric(vertical: 10)、
17
- 身長:300
18
- 幅:300
34
+ 身長:アニメーションの値
35
+ 幅:アニメーションの値
19
36
  子: const FlutterLogo()、
20
37
  )、
21
38
  );
22
39
  }
40
+
41
+ @オーバーライド
42
+ void destroy() {
43
+ コントローラー.dispose();
44
+ super.dispose();
45
+ }
23
46
  }

アプリのソース: アニメート1

addListener()関数呼び出しsetState()、 だから毎回Animation新しい番号を生成し、 現在のフレームはダーティとしてマークされているため、build()また呼ばれることに。のbuild()、 コンテナの高さによってサイズが変わります。 現在使用されている幅animation.valueハードコードされた値の代わりに。 コントローラは次の場合に廃棄してください。Stateオブジェクトは メモリリークを防ぐために破棄されます。

これらのいくつかの変更により、 Flutter で最初のアニメーションが作成されました。

AnimatedWidget による簡素化

AnimatedWidget基本クラスを使用すると分離できます アニメーション コードからのコア ウィジェット コード。AnimatedWidgetを維持する必要はありませんStateアニメーションを保持するオブジェクト。以下を追加しますAnimatedLogoクラス:

lib/main.dart (アニメーションロゴ)
class AnimatedLogo extends AnimatedWidget {
  const AnimatedLogo({super.key, required Animation<double> animation})
      : super(listenable: animation);

  @override
  Widget build(BuildContext context) {
    final animation = listenable as Animation<double>;
    return Center(
      child: Container(
        margin: const EdgeInsets.symmetric(vertical: 10),
        height: animation.value,
        width: animation.value,
        child: const FlutterLogo(),
      ),
    );
  }
}

AnimatedLogoの現在値を使用します。animationそれ自体を描くとき。

LogoAppまだ管理していますAnimationControllerそしてそのTween、 そしてそれは通過しますAnimationに反対するAnimatedLogo:

{animate1 → animate2}/lib/main.dart
@@ -1,10 +1,28 @@
1
1
  import 'パッケージ:flutter/material.dart';
2
2
  void main() => runApp(const LogoApp());
3
+ class AnimatedLogo extends AnimatedWidget {
4
+ const AnimatedLogo({super.key, 必須のアニメーション<double> アニメーション})
5
+ : スーパー(聴ける: アニメーション);
6
+
7
+ @オーバーライド
8
+ ウィジェットのビルド(BuildContext context) {
9
+ 最終アニメーション =Animation<double> として聞くことができます。
10
+ リターンセンター(
11
+ 子: コンテナ(
12
+ マージン: const EdgeInsets.metric(vertical: 10)、
13
+ 高さ: アニメーション.値、
14
+ 幅: アニメーション.値、
15
+ 子: const FlutterLogo()、
16
+ )、
17
+ );
18
+ }
19
+ }
20
+
3
21
  class LogoApp extends StatefulWidget {
4
22
  const LogoApp({super.key});
5
23
  @オーバーライド
6
24
  State<LogoApp> createState() => _LogoAppState();
7
25
  }
@@ -15,32 +33,18 @@
15
33
  @オーバーライド
16
34
  void initState() {
17
35
  super.initState();
18
36
  コントローラー =
19
37
  AnimationController(duration:constDuration(秒:2),vsync:this);
20
- アニメーション = Tween<double>(開始: 0、終了: 300).animate(コントローラー)
21
- ..addListener(() {
22
- setState(() {
23
- // ここで変化した状態はアニメーションオブジェクトの値です。
24
- });
25
- });
38
+ アニメーション = Tween<double>(開始: 0、終了: 300).animate(コントローラー);
26
39
  コントローラー.フォワード();
27
40
  }
28
41
  @オーバーライド
29
- ウィジェットのビルド(BuildContext コンテキスト){
30
- リターンセンター(
31
- 子: コンテナ(
32
- マージン: const EdgeInsets.metric(vertical: 10)、
33
- 高さ: アニメーション.値、
34
- 幅: アニメーション.値、
35
- 子: const FlutterLogo()、
36
- )、
37
- );
38
- }
42
+ ウィジェットのビルド(BuildContext コンテキスト)=> アニメーションロゴ(アニメーション:アニメーション);
39
43
  @オーバーライド
40
44
  void destroy() {
41
45
  コントローラー.dispose();
42
46
  super.dispose();
43
47
  }

アプリのソース: アニメート2

アニメーションの進行状況を監視する

アニメーションの状態が変化するタイミングを知ると役立つことがよくあります。 終了、前進、後進など。 この通知を受け取るには、addStatusListener()。 次のコードは、前の例を次のように変更します。 状態の変化をリッスンし、更新を出力します。 強調表示された行は変更を示しています。

class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
  late Animation<double> animation;
  late 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)
      ..addStatusListener((status) => print('$status'));
    controller.forward();
  }
  // ...
}

このコードを実行すると、次の出力が生成されます。

AnimationStatus.forward
AnimationStatus.completed

次に、使用しますaddStatusListener()アニメーションを逆にするには 最初か最後に。これにより、「呼吸」エフェクトが作成されます。

{animate2 → animate3}/lib/main.dart
@@ -35,7 +35,15 @@
35
35
  void initState() {
36
36
  super.initState();
37
37
  コントローラー =
38
38
  AnimationController(duration:constDuration(秒:2),vsync:this);
39
- アニメーション = Tween<double>(開始: 0、終了: 300).animate(コントローラー);
39
+ アニメーション = Tween<double>(開始: 0、終了: 300).animate(コントローラー)
40
+ ..addStatusListener((ステータス) {
41
+ if (ステータス == AnimationStatus.completed) {
42
+ コントローラー.リバース();
43
+ else if (status == AnimationStatus.dismissed) {
44
+ コントローラー.フォワード();
45
+ }
46
+ })
47
+ ..addStatusListener((ステータス) => print('$status'));
40
48
  コントローラー.フォワード();
41
49
  }

アプリのソース: アニメイト3

AnimatedBuilder を使用したリファクタリング

のコードに関する 1 つの問題アニメイト3例、 アニメーションを変更するにはウィジェットを変更する必要があるということです ロゴをレンダリングします。より良い解決策 責任を異なるクラスに分けることです。

  • ロゴをレンダリングする
  • を定義しますAnimation物体
  • トランジションをレンダリングする

この分離は、AnimatedBuilderクラス。アンAnimatedBuilderです レンダーツリー内の別のクラス。好きAnimatedWidgetAnimatedBuilder自動的に通知を聞く からディー6db4e-3dad-4da6-8b0a-bc6509153d31オブジェクトを作成し、ウィジェット ツリーをマークします 必要に応じて汚れるので、電話する必要はありませんaddListener()

のウィジェット ツリーアニメイト4例は次のようになります。

AnimatedBuilder widget tree

ウィジェット ツリーの一番下から、レンダリング用のコードが続きます。 ロゴは単純です:

class LogoWidget extends StatelessWidget {
  const LogoWidget({super.key});

  // Leave out the height and width so it fills the animating parent
  @override
  Widget build(BuildContext context) {
    return Container(
      margin: const EdgeInsets.symmetric(vertical: 10),
      child: const FlutterLogo(),
    );
  }
}

図の中央の 3 つのブロックはすべて、build()のメソッドGrowTransition、 下に示された。 のGrowTransitionウィジェット自体はステートレスであり、保持されます 遷移アニメーションを定義するために必要な最終変数のセット。 build() 関数は、AnimatedBuilder、 これは (Anonymousbuilder) メソッドとLogoWidgetオブジェクトをパラメータとして指定します。レンダリングする作業は、 遷移は実際には (Anonymousビルダー) メソッドを作成します。Container適切なサイズの 強制的にLogoWidgetぴったりと縮むこと。

以下のコードの注意が必要な点は、子が次のように見えることです。 2回指定されているようなものです。何が起こっているのかというと、 子の外部参照が渡されます19ベッド071-17d6-4346-9b8e-b0f9552483dc、 これはそれを匿名クロージャに渡し、次にそれを使用します。 そのオブジェクトをその子として扱います。最終的な結果は、AnimatedBuilder2 つのウィジェットの間に挿入されます レンダーツリー内。

class GrowTransition extends StatelessWidget {
  const GrowTransition(
      {required this.child, required this.animation, super.key});

  final Widget child;
  final Animation<double> animation;

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

最後に、アニメーションを初期化するコードは非常に見栄えがします。 に似ていますアニメート2例。のinitState()メソッドが作成するAnimationControllerそしてcda0adfc-a​​e32-44eb-ad00-da9e1e7e0bec、 次にそれらをバインドしますanimate()。魔法が起こるのは、 のbuild()メソッド。GrowTransitionオブジェクトLogoWidget子として、アニメーション オブジェクトとして 移行を推進します。この3つの要素が挙げられています 上記の箇条書きで説明します。

{animate2 → animate4}/lib/main.dart
@@ -1,27 +1,47 @@
1
1
  import 'パッケージ:flutter/material.dart';
2
2
  void main() => runApp(const LogoApp());
3
- クラスアニメーションロゴ伸びるアニメーションウィジェット{
4
- 定数アニメーションロゴ({スーパーキー、必須Animation<double>アニメーション})
5
- : スーパー(聴ける: アニメーション);
3
+ クラスロゴウィジェット伸びるステートレスウィジェット{
4
+ 定数ロゴウィジェット({スーパーキー});
5
+
6
+ // 高さと幅を省略して、アニメーション化する親を埋めるようにします。
7
+ @オーバーライド
8
+ ウィジェットのビルド(BuildContext context) {
9
+ コンテナを返す(
10
+ マージン: const EdgeInsets.metric(vertical: 10)、
11
+ 子: const FlutterLogo()、
12
+ );
13
+ }
14
+ }
15
+
16
+ class GrowTransition extends StatelessWidget {
17
+ const GrowTransition(
18
+ {this.child が必要、this.animation、super.key が必要});
19
+
20
+ 最後のウィジェットの子。
21
+ 最終アニメーション<double>アニメーション;
6
22
  @オーバーライド
7
23
  ウィジェットのビルド(BuildContext context) {
8
- 最終アニメーション =Animation<double> として聞くことができます。
9
24
  リターンセンター(
10
- 子供:容器(
11
- マージン:const EdgeInsets.metric(垂直: 10)
12
- 身長:アニメーションの値
13
- 幅: アニメーション.値、
14
- 子供:const FlutterLogo()
25
+ 子供:アニメーションビルダー(
26
+ アニメーション:アニメーション
27
+ ビルダー:(コンテクスト子供) {
28
+ 戻る SizedBox(
29
+ 身長:アニメーションの値
30
+ 幅: アニメーション.値、
31
+ 子供:子供、
32
+ );
33
+ }、
34
+ 子供:子供、
15
35
  )、
16
36
  );
17
37
  }
18
38
  }
19
39
  class LogoApp extends StatefulWidget {
20
40
  const LogoApp({super.key});
21
41
  @オーバーライド
22
42
  State<LogoApp> createState() => _LogoAppState();
@@ -34,18 +54,23 @@
34
54
  @オーバーライド
35
55
  void initState() {
36
56
  super.initState();
37
57
  コントローラー =
38
58
  AnimationController(duration:constDuration(秒:2),vsync:this);
39
59
  アニメーション = Tween<double>(開始: 0, 終了: 300).animate(コントローラー);
40
60
  コントローラー.フォワード();
41
61
  }
42
62
  @オーバーライド
43
- ウィジェットのビルド(BuildContext コンテキスト)=> アニメーションロゴ(アニメーション:アニメーション);
63
+ ウィジェットのビルド(BuildContext コンテキスト){
64
+ return GrowTransition(
65
+ アニメーション: アニメーション、
66
+ 子: const LogoWidget()、
67
+ );
68
+ }
44
69
  @オーバーライド
45
70
  void destroy() {
46
71
  コントローラー.dispose();
47
72
  super.dispose();
48
73
  }
49
74
  }

アプリのソース: アニメイト4

同時アニメーション

このセクションでは、次の例に基づいて構築します。アニメーションの進行状況を監視する(アニメイト3)、使用したAnimatedWidget継続的に出入りをアニメーション化します。場合を考えてみましょう 途中でアニメーションを付けたい場所と外側の場所を指定します。 不透明度は透明から不透明にアニメーションします。

各トゥイーンはアニメーションの側面を管理します。例えば:

controller =
    AnimationController(duration: const Duration(seconds: 2), vsync: this);
sizeAnimation = Tween<double>(begin: 0, end: 300).animate(controller);
opacityAnimation = Tween<double>(begin: 0.1, end: 1).animate(controller);

サイズは次のように取得できますsizeAnimation.valueそして不透明度 とopacityAnimation.value、しかし、のコンストラクターAnimatedWidget1つだけかかりますAnimation物体。この問題を解決するために、 この例では独自のものを作成しますTweenオブジェクトを作成し、明示的に計算します。 価値観。

変化AnimatedLogo独自のものをカプセル化するTweenオブジェクト、 そしてそのbuild()メソッド呼び出しTween.evaluate()親のアニメーション オブジェクトで計算します 必要なサイズと不透明度の値。 次のコードは、変更を強調表示して示しています。

class AnimatedLogo extends AnimatedWidget {
  const AnimatedLogo({super.key, required Animation<double> animation})
      : super(listenable: animation);

  // Make the Tweens static because they don't change.
  static final _opacityTween = Tween<double>(begin: 0.1, end: 1);
  static final _sizeTween = Tween<double>(begin: 0, end: 300);

  @override
  Widget build(BuildContext context) {
    final animation = listenable as Animation<double>;
    return Center(
      child: Opacity(
        opacity: _opacityTween.evaluate(animation),
        child: Container(
          margin: const EdgeInsets.symmetric(vertical: 10),
          height: _sizeTween.evaluate(animation),
          width: _sizeTween.evaluate(animation),
          child: const FlutterLogo(),
        ),
      ),
    );
  }
}

class LogoApp extends StatefulWidget {
  const LogoApp({super.key});

  @override
  State<LogoApp> createState() => _LogoAppState();
}

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

  @override
  void initState() {
    super.initState();
    controller =
        AnimationController(duration: const Duration(seconds: 2), vsync: this);
    animation = CurvedAnimation(parent: controller, curve: Curves.easeIn)
      ..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();
  }
}

アプリのソース: アニメイト5

次のステップ

このチュートリアルでは、アニメーションを作成するための基礎を提供します。 flutterを使用してTweens, しかし、探索すべきクラスは他にもたくさんあります。 専門的なことを調べてみるとよいでしょうTweenクラス、 マテリアル デザインに固有のアニメーション、ReverseAnimation、 共有要素のトランジション (ヒーロー アニメーションとも呼ばれます)、 物理シミュレーションとfling()方法。 を参照してください。アニメーションのランディング ページ最新の利用可能なドキュメントと例については、こちらをご覧ください。