flutter-animations について
flutter-animationsは、Flutter開発者向けのアニメーションスキルです。暗黙的なアニメーションから物理シミュレーションまで、一般的な表現の実装例とベストプラクティスを紹介します。
主な機能
- 01適切なアニメーション形式の選択
- 02暗黙的・明示的アニメーションの実装
- 03Hero画面遷移の作成
- 04段階的なアニメーションの編成
- 05物理ベースのアニメーション作成
- 06実装例とベストプラクティスの確認
活用シーン
- 01状態変化に応じたプロパティのアニメーション
- 02画面間の共有要素の遷移
- 03リストやメニュー項目の順次表示
- 04バネやドラッグ操作の表現
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ im5tu/ claude --skill flutter-animationsインストール後、Claude Code で/flutter-animationsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
assets/templates/explicit_animation.darthero_transition.dartimplicit_animation.dartstaggered_animation.dartreferences/curves.mdexplicit.mdhero.mdimplicit.mdphysics.mdstaggered.md詳細
Flutter アニメーション
概要
ユースケースに適した方法を使って、Flutter で滑らかで高性能なアニメーションを作成します。このスキルでは、暗黙的/明示的な方法の選択から、ヒーロー遷移や段階的アニメーションなどの複雑な効果の実装まで、アニメーションのワークフロー全体を扱います。
アニメーションの種類を選ぶ判断フロー
要件に応じて適切なアニメーションの種類を選びます。
暗黙的アニメーション - 次の場合に使用します:
- 単一のプロパティ(色、サイズ、位置)をアニメーションさせる
- 状態の変化によってアニメーションがトリガーされる
- 細かい制御が必要ない
明示的アニメーション - 次の場合に使用します:
- アニメーションのライフサイクルを完全に制御する必要がある
- 複数のプロパティを同時にアニメーションさせる
- アニメーションの状態変化に応じた処理が必要である
- カスタムアニメーションやトランジションを作成する
ヒーローアニメーション - 次の場合に使用します:
- 2 つの画面間で要素を共有する
- 共有要素のトランジションを作成する
- 要素がルート間を「飛んで」移動することをユーザーが期待する
段階的アニメーション - 次の場合に使用します:
- 複数のアニメーションを順番に実行する、または重ねて実行する
- 波紋効果や順次表示を作成する
- リスト項目を順番にアニメーションさせる
物理ベースのアニメーション - 次の場合に使用します:
- アニメーションに自然な物理的感覚を持たせる
- バネのような動きやスクロールジェスチャーを表現する
- ドラッグ可能な操作を実装する
暗黙的アニメーション
暗黙的アニメーションは、プロパティが変化したときのアニメーションを自動的に処理します。コントローラーは不要です。
よく使われる暗黙的ウィジェット
AnimatedContainer - 複数のプロパティ(サイズ、色、装飾、パディング)をアニメーションさせます:
AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
width: _expanded ? 200 : 100,
height: _expanded ? 200 : 100,
color: _expanded ? Colors.blue : Colors.red,
child: const FlutterLogo(),
)
AnimatedOpacity - シンプルなフェードアニメーションです:
AnimatedOpacity(
opacity: _visible ? 1.0 : 0.0,
duration: const Duration(milliseconds: 300),
child: const Text('Hello'),
)
TweenAnimationBuilder - 定型コードなしでカスタムツイーンアニメーションを実装します:
TweenAnimationBuilder<double>(
tween: Tween<double>(begin: 0, end: 1),
duration: const Duration(seconds: 1),
builder: (context, value, child) {
return Opacity(
opacity: value,
child: Transform.scale(
scale: value,
child: child,
),
);
},
child: const FlutterLogo(),
)
その他の暗黙的ウィジェット:
AnimatedPadding- パディングのアニメーションAnimatedPositioned- 位置のアニメーション(Stack 内)AnimatedAlign- 配置のアニメーションAnimatedContainer- 複数のプロパティAnimatedSwitcher- ウィジェット間のクロスフェードAnimatedDefaultTextStyle- テキストスタイルのアニメーション
ベストプラクティス
- シンプルなケースでは暗黙的アニメーションを優先します
- 自然な動きになるよう、適切なカーブを使用します(
Curvesクラスを参照) - 予測可能な動作になるよう、
curveとdurationを設定します - 必要に応じて
onEndコールバックを使用します - パフォーマンスを維持するため、暗黙的アニメーションのネストは避けます
明示的アニメーション
明示的アニメーションは、AnimationController によって完全な制御を実現します。
主なコンポーネント
AnimationController - アニメーションを駆動します:
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
Tween - 開始値と終了値の間を補間します:
animation = Tween<double>(begin: 0, end: 300).animate(_controller);
CurvedAnimation - アニメーションにカーブを適用します:
animation = CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
);
AnimatedWidget パターン
再利用可能なアニメーションウィジェットに最適です:
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(
height: animation.value,
width: animation.value,
child: const FlutterLogo(),
),
);
}
}
AnimatedBuilder パターン
アニメーションを含む複雑なウィジェットに最適です:
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,
),
);
}
}
アニメーション状態の監視
animation.addStatusListener((status) {
switch (status) {
case AnimationStatus.completed:
_controller.reverse();
break;
case AnimationStatus.dismissed:
_controller.forward();
break;
default:
break;
}
});
複数のアニメーションを同時に実行する
class AnimatedLogo extends AnimatedWidget {
const AnimatedLogo({super.key, required Animation<double> animation})
: super(listenable: animation);
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(
height: _sizeTween.evaluate(animation),
width: _sizeTween.evaluate(animation),
child: const FlutterLogo(),
),
),
);
}
}
組み込みの明示的トランジション
Flutter にはすぐに使えるトランジションが用意されています:
FadeTransition- フェードアニメーションScaleTransition- 拡大縮小アニメーションSlideTransition- スライドアニメーションSizeTransition- サイズアニメーションRotationTransition- 回転アニメーションPositionedTransition- 位置のアニメーション(Stack 内)
例:
FadeTransition(
opacity: _animation,
child: const FlutterLogo(),
)
パフォーマンスのヒント
- ウィジェットが削除されるときにコントローラーを破棄します
- 再ビルドを最適化するため、
AnimatedBuilderを使用します - アニメーションリスナー内で
setState()を呼び出すのは避けます(AnimatedWidget/AnimatedBuilderを使用します) - デバッグ中にアニメーションを遅くするには
timeDilationを使用します
ヒーローアニメーション
ヒーローアニメーションは、画面間の共有要素トランジションを作成します。
基本的なヒーローアニメーション
遷移元の画面:
Hero(
tag: 'hero-image',
child: Image.asset('images/logo.png'),
)
遷移先の画面:
Hero(
tag: 'hero-image', // Same tag!
child: Image.asset('images/logo.png'),
)
完全な例
class PhotoHero extends StatelessWidget {
const PhotoHero({
super.key,
required this.photo,
this.onTap,
required this.width,
});
final String photo;
final VoidCallback? onTap;
final double width;
@override
Widget build(BuildContext context) {
return SizedBox(
width: width,
child: Hero(
tag: photo,
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: onTap,
child: Image.asset(photo, fit: BoxFit.contain),
),
),
),
);
}
}
画面間の移動:
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (context) {
return Scaffold(
appBar: AppBar(title: const Text('Detail')),
body: Center(
child: PhotoHero(
photo: 'images/logo.png',
width: 300.0,
onTap: () => Navigator.of(context).pop(),
),
),
);
},
),
);
放射状ヒーローアニメーション
トランジション中に円から長方形へ変形します:
class RadialExpansion extends StatelessWidget {
const RadialExpansion({
super.key,
required this.maxRadius,
this.child,
}) : clipRectSize = 2.0 * (maxRadius / math.sqrt2);
final double maxRadius;
final double clipRectSize;
final Widget? child;
@override
Widget build(BuildContext context) {
return ClipOval(
child: Center(
child: SizedBox(
width: clipRectSize,
height: clipRectSize,
child: ClipRect(child: child),
),
),
);
}
}
中心を基準とした補間には MaterialRectCenterArcTween と組み合わせて使用します:
static RectTween _createRectTween(Rect? begin, Rect? end) {
return MaterialRectCenterArcTween(begin: begin, end: end);
}
Hero のベストプラクティス
- 一意で一貫性のあるタグを使用します(多くの場合、データオブジェクト自体を指定します)
- ルート間で Hero ウィジェットツリーの構成を似せます
- 「ポップ」効果を出すには、画像を色を透明にした
Materialでラップします - トランジションをデバッグするには
timeDilationを使用します - 必要に応じて Hero アニメーションを無効にするために
HeroModeを検討します
スタッガーアニメーション
タイミングを変えて複数のアニメーションを実行します。
基本的なスタッガーアニメーション
すべてのアニメーションで同じコントローラーを使用します。
class StaggerAnimation extends StatelessWidget {
StaggerAnimation({super.key, required this.controller})
: opacity = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: controller,
curve: const Interval(0.0, 0.100, curve: Curves.ease),
),
),
width = Tween<double>(begin: 50.0, end: 150.0).animate(
CurvedAnimation(
parent: controller,
curve: const Interval(0.125, 0.250, curve: Curves.ease),
),
);
final AnimationController controller;
final Animation<double> opacity;
final Animation<double> width;
Widget _buildAnimation(BuildContext context, Widget? child) {
return Container(
alignment: Alignment.bottomCenter,
child: Opacity(
opacity: opacity.value,
child: Container(
width: width.value,
height: 150,
color: Colors.blue,
),
),
);
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: controller,
builder: _buildAnimation,
);
}
}
Interval ベースのタイミング
各アニメーションには、0.0 から 1.0 の範囲の Interval を設定します。
animation = Tween<double>(begin: 0, end: 300).animate(
CurvedAnimation(
parent: controller,
curve: const Interval(
0.25, // Start at 25% of controller duration
0.50, // End at 50% of controller duration
curve: Curves.ease,
),
),
);
よく使用する Tween
borderRadius = BorderRadiusTween(
begin: BorderRadius.circular(4),
end: BorderRadius.circular(75),
).animate(
CurvedAnimation(
parent: controller,
curve: const Interval(0.375, 0.500, curve: Curves.ease),
),
);
スタッガーメニューアニメーション
class _MenuState extends State<Menu> with SingleTickerProviderStateMixin {
static const _initialDelayTime = Duration(milliseconds: 50);
static const _itemSlideTime = Duration(milliseconds: 250);
static const _staggerTime = Duration(milliseconds: 50);
static const _buttonDelayTime = Duration(milliseconds: 150);
static const _buttonTime = Duration(milliseconds: 500);
final _animationDuration =
_initialDelayTime +
(_staggerTime * _menuTitles.length) +
_buttonDelayTime +
_buttonTime;
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: _animationDuration,
vsync: this,
);
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
スタッガーのベストプラクティス
- アニメーションの開始時刻をずらすには
Intervalを使用します - コントローラーの再生時間がすべての Interval をカバーするようにします
- Interval 内で自然な動きになるようにカーブを使用します
- タイミングをデバッグするには
timeDilationを検討します - 波紋効果を出すため、遅延を段階的に増やしながらメニュー項目をスタッガー表示します
物理ベースのアニメーション
物理シミュレーションを使用して、自然な動きのアニメーションを作成します。
フリングアニメーション
_controller.fling(
velocity: 2.0, // Units per second
);
カスタム物理シミュレーション
_controller.animateWith(
SpringSimulation(
spring: const SpringDescription(
mass: 1,
stiffness: 100,
damping: 10,
),
start: 0.0,
end: 1.0,
velocity: 0.0,
),
);
よく使用する物理シミュレーション
SpringSimulation- バネの物理特性BouncingScrollSimulation- バウンドするスクロールClampingScrollSimulation- バウンドしないスクロールGravitySimulation- 重力ベース
ベストプラクティス
推奨事項
- ウィジェットの破棄時に AnimationController を破棄します
- リスナー内で
setState()を使う代わりに、AnimatedBuilder/AnimatedWidgetを使用します - 自然な動きになる適切なカーブを選択します
- アニメーションのデバッグには
timeDilationを使用します - パフォーマンスを考慮します(アニメーションのビルド内で負荷の高いウィジェットを避けます)
- さまざまなデバイスでアニメーションをテストします
- 直感的な操作感を得るため、逆方向のアニメーションにも対応します
禁止事項
- AnimationController の破棄を忘れないでください(メモリリークにつながります)
AnimatedBuilderで十分な場合に、アニメーションリスナー内でsetState()を使用しないでください- アニメーションが即座に完了すると思い込まないでください(
AnimationStatusを処理してください) - アニメーションを過剰に使用しないでください(ユーザーの注意をそらすことがあります)
- 「ぎくしゃく」したアニメーションを作成しないでください(滑らかなカーブを使用してください)
- アクセシビリティを無視しないでください(
disableAnimationsの設定を尊重してください)
リソース
references/
implicit.md - 暗黙的なアニメーションウィジェットの完全なリファレンスと、例およびベストプラクティスです。
explicit.md - 明示的なアニメーション、AnimationController、およびパターンを詳しく解説します。
hero.md - 標準および放射状トランジションを含む Hero アニメーションのガイドです。
staggered.md - スタッガーアニメーションのパターンとタイミング戦略です。
physics.md - 物理ベースのアニメーションとシミュレーションです。
curves.md - Curves クラスと適切なカーブの選び方に関するリファレンスです。
assets/templates/
一般的なアニメーションパターンのテンプレートコードです。
implicit_animation.dart- 暗黙的なアニメーションの例explicit_animation.dart- 明示的なアニメーションのセットアップhero_transition.dart- Hero アニメーションのボイラープレートstaggered_animation.dart- スタッガーアニメーションのテンプレート