Contents
1. Flutter SDK のインストールと IDE 設定
このセクションでは、2026 年時点で提供されている 最新安定版 Flutter(バージョン番号は随時変わります)を手元のマシンに導入し、開発効率を高めるための IDE 環境を整える手順を示します。
1.1 最新安定版 Flutter の取得
Flutter の公式サイトから「Get started」→「Windows / macOS / Linux」を選択すると、最新の安定版 SDK がダウンロードできます。バージョン番号は flutter --version コマンドで確認してください。
|
1 2 3 4 5 |
# 例: Windows PowerShell でのインストール手順 git clone https://github.com/flutter/flutter.git -b stable $HOME/flutter $Env:PATH += ";$HOME\flutter\bin" flutter doctor |
- ポイント
- SDK に同梱されている Dart のバージョンは、Flutter のリリースごとに自動で合わせられます(2026 年現在は Dart 3.2 系が多く採用されています)。個別に Dart をインストールする必要はありません。
flutter doctorが示す警告は必ず解消してから開発を始めましょう。
1.2 推奨 IDE とプラグイン設定
| IDE | 必須プラグイン・拡張機能 | 推奨設定例 |
|---|---|---|
| VS Code | Flutter、Dart(必須)flutter_riverpod(任意) |
"dart.formatOnSave": true、"editor.tabSize": 2 |
| Android Studio / IntelliJ | Flutter、Dart(プラグイン) |
「Enable Hot Reload on Save」オン推奨 |
- 共通のベストプラクティス
- エディタの自動保存とフォーマットを有効にすると、コードレビュー時の指摘が減ります。
- デバッグ実行時は「Flutter DevTools」パネルを開き、パフォーマンスやレイアウト情報を随時確認できるようにしておくと便利です。
2. アニメーション基礎概念:ウィジェットツリー・フレーム・Curve・Tween
ここでは、Flutter の描画サイクルとアニメーションがどのように連携するかを把握します。概念を整理すれば、暗黙的・明示的どちらの手法でも正しい設計が可能です。
2.1 ウィジェットツリーとフレーム
- ウィジェットツリーは UI の宣言的構造で、状態が変化するとその subtree が再ビルドされます。
- フレームは 1/60 秒(≈16 ms)ごとの描画サイクルです。Flutter はこのタイミングで UI スレッドと GPU レンダリングスレッドを同期させ、滑らかなアニメーションを実現します。
注: 60 fps を保つための目安は「1 フレームあたりの総処理時間が 16 ms 未満」ですが、実際にはデバイス性能や描画内容に依存するため、DevTools の Performance タブで測定しながら最適化します。
2.2 Curve と Tween の役割
| 用語 | 説明 | 主な使用場所 |
|---|---|---|
| Curve | 時間(0〜1)に対する速度プロファイル。Curves.easeInOut や Curves.bounceIn などがある。 |
CurvedAnimation、AnimatedContainer.curve |
| Tween\ |
開始値と終了値の線形補間ロジック。数値だけでなく色やサイズも扱える。 | Tween<double>、ColorTween |
流れ:AnimationController → Curve → Tween → Widget の順にデータが流れることで、「状態変化 → 時間経過 → 具体的な UI 変更」が実現します。
3. 暗黙的(Implicit)アニメーションの基本と活用例
暗黙的ウィジェットは、プロパティの変更だけで自動的にアニメーションを生成します。コード量が少なく、初心者でもすぐに効果を体感できる点が魅力です。
3.1 AnimatedContainer の実装手順
以下では AnimatedContainer を使ってサイズ・色の遷移を実装する流れを示します。まずは必要なインポートから確認しましょう。
|
1 2 |
import 'package:flutter/material.dart'; |
コード例(完全版)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
class ImplicitDemo extends StatefulWidget { const ImplicitDemo({super.key}); @override State<ImplicitDemo> createState() => _ImplicitDemoState(); } class _ImplicitDemoState extends State<ImplicitDemo> { bool _expanded = false; @override Widget build(BuildContext context) { return Center( child: Column(mainAxisSize: MainAxisSize.min, children: [ AnimatedContainer( width: _expanded ? 200 : 100, height: _expanded ? 200 : 100, color: _expanded ? Colors.blue : Colors.red, duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, ), const SizedBox(height: 16), ElevatedButton( onPressed: () => setState(() => _expanded = !_expanded), child: Text(_expanded ? '縮小' : '拡大'), ) ]), ); } } |
durationとcurveを指定するだけで、サイズと色が滑らかに変化します。- 状態管理は
setStateのみで完結し、余計なリスナーやコントローラは不要です。
3.2 その他の暗黙的ウィジェット
| ウィジェット | 主なプロパティ | 典型的な利用シーン |
|---|---|---|
AnimatedOpacity |
opacity, duration |
フェードイン/アウト |
AnimatedPositioned |
left/top/right/bottom, duration |
Stack 内での位置移動 |
AnimatedSwitcher |
child, duration, transitionBuilder |
子ウィジェット入れ替え時のクロスフェード |
AnimatedOpacity のサンプル
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
class FadeDemo extends StatefulWidget { const FadeDemo({super.key}); @override State<FadeDemo> createState() => _FadeDemoState(); } class _FadeDemoState extends State<FadeDemo> { bool _visible = true; @override Widget build(BuildContext context) { return Column(mainAxisSize: MainAxisSize.min, children: [ AnimatedOpacity( opacity: _visible ? 1.0 : 0.0, duration: const Duration(milliseconds: 500), child: const Text('フェードテキスト', style: TextStyle(fontSize: 24)), ), ElevatedButton( onPressed: () => setState(() => _visible = !_visible), child: const Text('切替'), ) ]); } } |
4. 明示的(Explicit)アニメーションの構成要素と実装ベストプラクティス
明示的アニメーションは AnimationController を中心に、時間管理・値変換・描画更新を細かく制御できます。高度な UI 表現やシーケンスアニメーションが必要な場面で活躍します。
4.1 AnimationController と CurvedAnimation の作り方
以下は StatefulWidget 内でコントローラと曲線付きアニメーションを生成する最小構成です。インポートは必ず material.dart を入れておきます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
import 'package:flutter/material.dart'; class ExplicitDemo extends StatefulWidget { const ExplicitDemo({super.key}); @override State<ExplicitDemo> createState() => _ExplicitDemoState(); } class _ExplicitDemoState extends State<ExplicitDemo> with SingleTickerProviderStateMixin { late final AnimationController _controller = AnimationController( vsync: this, duration: const Duration(seconds: 2), ); late final Animation<double> _curveAnim = CurvedAnimation( parent: _controller, curve: Curves.elasticOut, ); @override void initState() { super.initState(); _controller.forward(); // 再生開始 } @override void dispose() { _controller.dispose(); // リソース解放は必須 super.dispose(); } @override Widget build(BuildContext context) { return Center( child: AnimatedBuilder( animation: _curveAnim, builder: (context, child) { final size = 100 + (_curveAnim.value * 100); return Container(width: size, height: size, color: Colors.orange); }, ), ); } } |
vsyncにTickerProviderStateMixin(またはSingleTickerProviderStateMixin)を渡すことで、不要なフレーム描画を防ぎます。forward()のみで開始でき、reverse(),repeat()などのメソッドも同様に利用できます。
4.2 Tween と CurvedAnimation を組み合わせた具体例
|
1 2 3 |
final Animation<double> sizeAnim = Tween<double>(begin: 100, end: 200) .animate(CurvedAnimation(parent: _controller, curve: Curves.easeInOut)); |
この sizeAnim は AnimatedBuilder または FadeTransition のようなウィジェットに直接渡すことができ、リビルド範囲を最小化できます。
4.3 Listener vs AnimatedBuilder
| 手法 | 実装例 | メリット | デメリット |
|---|---|---|---|
| addListener + setState | _controller.addListener(() => setState(() {})); |
実装がシンプル | 毎フレーム全体ツリーを再描画し、CPU 負荷が高くなる |
| AnimatedBuilder | AnimatedBuilder(animation: _animation, builder: ...) |
再描画対象を builder の子に限定できる |
若干のコード量増加 |
実務では AnimatedBuilder(または専用ウィジェット例:FadeTransition, ScaleTransition)を推奨します。
4.4 Curves の選び方とデモコード
| Curve | 特徴 | 推奨シーン |
|---|---|---|
Curves.easeInOut |
緩やかな開始・終了 | ボタンのフェード、カード展開 |
Curves.bounceIn |
バウンド感が強い | アイコン出現、ゲーム UI |
Curves.elasticOut |
弾性伸縮が顕著 | ダイアログ表示、カード回転 |
BounceIn デモ(ScaleTransition)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
class BounceDemo extends StatefulWidget { const BounceDemo({super.key}); @override State<BounceDemo> createState() => _BounceDemoState(); } class _BounceDemoState extends State<BounceDemo> with SingleTickerProviderStateMixin { late final AnimationController _c = AnimationController( vsync: this, duration: const Duration(milliseconds: 800), ); @override void initState() { super.initState(); _c.forward(); } @override void dispose() { _c.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final anim = Tween<double>(begin: 0, end: 1).animate( CurvedAnimation(parent: _c, curve: Curves.bounceIn), ); return Center( child: ScaleTransition(scale: anim, child: const Icon(Icons.star, size: 48)), ); } } |
5. パフォーマンス最適化と 2026 年版アニメーション支援パッケージ
滑らかな UI を保つには、描画コストの測定・削減が不可欠です。本章では RepaintBoundary の正しい使い方と、最新パッケージ(flutter_animate と rive_flutter)の導入手順を解説します。
5.1 RepaintBoundary の活用指針
- 役割:ウィジェットツリーの一部を独立レイヤーに分離し、変更があったときだけその領域を再描画させます。
- 適用タイミング:DevTools の Performance タブで「Repaint」時間が高い(例: 8 ms 以上)ウィジェットや、頻繁にアニメーションするコンテナに付与します。過剰に配置するとレイヤー数が増えて逆に負荷が上がるため注意が必要です。
|
1 2 3 4 5 6 7 8 9 |
RepaintBoundary( child: AnimatedContainer( duration: const Duration(milliseconds: 400), width: _size, height: _size, color: Colors.purple, ), ); |
5.2 DevTools でフレームタイムを測定する手順
- ターミナルで
flutter run --profile(プロファイルモード)を実行 - IDE の Flutter DevTools を起動し、Performance タブへ移動
- 「Record」ボタンを押してアニメーション操作を行い、停止後にタイムラインを確認
- 赤くハイライトされた区間(
Raster Time > 16 ms)があれば該当ウィジェットを最適化対象とします
5.3 flutter_animate の正しい使い方
flutter_animate は拡張メソッドで簡潔に連鎖アニメーションを書けます。.ms の表記はパッケージが提供する DurationExtension が利用可能な場合のみ有効です。そのため、必ず import を追加してください。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; class AnimateDemo extends StatelessWidget { const AnimateDemo({super.key}); @override Widget build(BuildContext context) { return Center( child: Text('Welcome') .animate() // ← animate() が拡張メソッドを付与 .fadeIn(duration: const Duration(milliseconds: 800)) .move(begin: const Offset(0, 30), end: Offset.zero), ); } } |
注意:
DurationExtension(800.ms) はimport 'package:flutter_animate/flutter_animate.dart';が必要です。プロジェクトで拡張子を使用しない場合は従来通りconst Duration(milliseconds: 800)を書く方が安全です。
5.4 Rive の導入とベクターモーション
Rive(旧 Flare)はベクターアニメーションをリアルタイムで描画でき、複雑なインタラクションに最適です。以下は最新の rive_flutter パッケージ(2026 年版)を利用した最小構成です。
|
1 2 |
flutter pub add rive_flutter |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import 'package:flutter/material.dart'; import 'package:rive/rive.dart'; class RiveDemo extends StatelessWidget { const RiveDemo({super.key}); @override Widget build(BuildContext context) { return const Center( child: RiveAnimation.asset( 'assets/animation.riv', animations: ['idle'], // アセット内のアニメーション名 fit: BoxFit.contain, ), ); } } |
- GPU オフロード:Rive の描画は内部で Skia に最適化され、CPU 負荷が低減します。
- インタラクティブ制御:
StateMachineControllerを使用すれば UI 操作に応じたリアルタイム遷移が可能です。
6. 実践ミニプロジェクト:カード展開デモアプリ
本章では、暗黙的と明示的の両手法を組み合わせた 「ボタンタップでカードが拡大・色変化し、テキストがフェードイン」 デモアプリを完成させます。コードはコンパイル可能な形に整えてありますので、そのまま main.dart に貼り付けて動作確認してください。
6.1 要件と画面構成
- UI:中央にカード(
AnimatedContainer)+下部にトグルボタン - アニメーション
- カードのサイズ・背景色は暗黙的
AnimatedContainerが担当 - カード内部のテキストは明示的
AnimationControllerとFadeTransitionで制御
6.2 完全コード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; // オプション:追加アニメーションに使用 void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) => MaterialApp( title: 'カード展開デモ', theme: ThemeData(useMaterial3: true), home: const CardDemoPage(), ); } class CardDemoPage extends StatefulWidget { const CardDemoPage({super.key}); @override State<CardDemoPage> createState() => _CardDemoPageState(); } class _CardDemoPageState extends State<CardDemoPage> with SingleTickerProviderStateMixin { // 暗黙的アニメーションのトリガー bool _expanded = false; // 明示的アニメーション用コントローラ(テキストフェード) late final AnimationController _controller = AnimationController( vsync: this, duration: const Duration(milliseconds: 400), ); @override void initState() { super.initState(); // 初期は非表示にしておく _controller.value = 0.0; } @override void dispose() { _controller.dispose(); // リソース解放は必ず実施 super.dispose(); } void _toggleCard() { setState(() => _expanded = !_expanded); if (_expanded) { _controller.forward(); } else { _controller.reverse(); } } @override Widget build(BuildContext context) { // RepaintBoundary による描画最適化(必要に応じて有効化) return Scaffold( appBar: AppBar(title: const Text('カード展開デモ')), body: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ RepaintBoundary( child: AnimatedContainer( width: _expanded ? 300 : 150, height: _expanded ? 200 : 100, duration: const Duration(milliseconds: 400), curve: Curves.easeInOut, decoration: BoxDecoration( color: _expanded ? Colors.teal : Colors.orangeAccent, borderRadius: BorderRadius.circular(12), ), // テキストのフェードは明示的に制御 child: Center( child: FadeTransition( opacity: _controller.drive(Tween<double>(begin: 0.0, end: 1.0)), child: const Text( 'Hello Flutter!', style: TextStyle(fontSize: 20, color: Colors.white), ), ), ), ), ), const SizedBox(height: 24), ElevatedButton( onPressed: _toggleCard, child: Text(_expanded ? '閉じる' : '展開する'), ), ], ), ), ); } } |
ポイント解説
| 項目 | 内容 |
|---|---|
| 暗黙的 | AnimatedContainer がサイズ・背景色の遷移を自動で処理。状態変化は _expanded のみで完結。 |
| 明示的 | AnimationController と FadeTransition によりテキストだけを独立してフェードイン/アウト。リビルド範囲が限定され、パフォーマンスが向上。 |
| 最適化 | RepaintBoundary をカード全体に付与し、サイズ変更時の再描画領域を局所化。DevTools で「Repaint」時間が減少すれば効果あり。 |
6.3 デバッグ・テスト時の落とし穴と回避策
| 落とし穴 | 原因 | 回避策 |
|---|---|---|
| アニメーションが途中で止まる | dispose() でコントローラ解放忘れ |
必ず dispose 内で _controller.dispose() を呼ぶ |
| フレームドロップが頻発 | setState が過剰に走る |
UI 更新は FadeTransition / AnimatedBuilder に委譲し、setState はトリガーだけに使用 |
| カードサイズが端末外に出る | 固定ピクセル指定 | MediaQuery.of(context).size.width * 0.8 のように相対レイアウトを採用 |
検証手順:flutter run --profile → DevTools → Performance タブで UI Thread Time と Raster Time が 16 ms 以下か確認。超えている場合は RepaintBoundary の追加や AnimatedContainer の duration 調整を行います。
7. まとめ
- 開発環境は公式サイトの最新安定版 SDK を取得し、VS Code または Android Studio に必須プラグインを導入すればすぐにコーディング開始できます。
- アニメーション概念(ウィジェットツリー・フレーム・Curve·Tween)を押さえておくと、暗黙的でも明示的でも正しい設計が可能です。
- 暗黙的ウィジェットはコード量が少なく初心者向きですが、複雑なシナリオでは 明示的コントローラ に切り替えると柔軟性が増します。
- パフォーマンス最適化は測定ツール(DevTools)で根拠を持って行い、
RepaintBoundaryは「再描画時間が高い」ウィジェットに限定して使用しましょう。 - 2026 年版の flutter_animate と rive_flutter を活用すれば、数行のコードで高度なモーションやベクターベースアニメーションを実装できます。
これらの知識と手順を踏めば、Flutter アプリにおける UI アニメーション開発がスムーズに進みます。ぜひ本稿のサンプルコードを基に、自身のプロジェクトで試してみてください。