인터페이스는 현지화되어 있으며, Skill 소개와 설명 문서는 선택한 언어로 AI가 자동 번역합니다. 코드·명령어·경로·전문 용어는 원문을 유지하며 원작자 문서가 우선합니다.
Skills 둘러보기
디자인 및 창작 · SKILL.md

flutter-animations

flutter-animations

Enterprise Flutter animation skill — Rive + Lottie dual-engine strategy for production mobile apps. Use when adding animations to Flutter, asking about Rive, Lottie, animated UI components, motion design, interactive states, loading indicators, onboarding flows, micro-interactions, character animations, splash screens, empty states, or any animated widget. …

Independent GitHub snapshot · Automated classification · Original documentation governs

Skill 문서

GitHub

name: flutter-animations description: "Enterprise Flutter animation skill — Rive + Lottie dual-engine strategy for production mobile apps. Use when adding animations to Flutter, asking about Rive, Lottie, animated UI components, motion design, interactive states, loading indicators, onboarding flows, micro-interactions, character animations, splash screens, empty states, or any animated widget. Load BEFORE writing any Flutter animation code — enforces correct engine choice, architecture patterns, performance constraints, and accessibility." risk: low source: community (adapted for workspace) date_added: "2026-03-15" allowed-tools: Bash, Read, Write, Edit metadata: triggers: animation, Rive, Lottie, animated widget, motion design, loading indicator, onboarding animation, micro-interaction, character animation, splash screen, empty state animation, interactive toggle, progress ring, skeleton loader, confetti, state machine animation related-skills: flutter-mobile, mobile-design, riverpod-patterns, ui-standards-tokens, accessibility-audit domain: frontend role: specialist scope: implementation output-format: code last-reviewed: "2026-03-15"

Iron Law

RESOLVE ENGINE CHOICE FIRST (Rive vs Lottie) before writing a single line of animation code — use the decision table below. Then read the relevant reference file. Never hardcode asset paths.

Flutter Animations — Rive + Lottie Dual-Engine

Engine Decision (Always resolve first)

Use CaseEngineWhy
Interactive buttons, toggles, tab barsRiveState machines, pointer input
Onboarding with user-driven progressionRiveInputs map to gestures
Progress indicators tied to real dataRiveData binding at runtime
Auth flows (loading → success → error)RiveMulti-state without code branching
Games, character animationsRive60fps, blending, bone rigs
Splash / intro screens (play-once)LottieAF export, decorative
Empty states, "no results" illustrationsLottieDesigner-owned, static loop
Celebration / confetti effectsLottieAfter Effects shine
Icon animations (email sent, checkmark)LottieSimple, small JSON
Skeleton loaders with fixed timingLottiePredictable, no interaction

If unsure: "Does this animation need to respond to app state or user input?" Yes → Rive. No → Lottie.


Package Versions

dependencies:
  rive: ^0.14.4          # rive.app verified publisher
  lottie: ^3.3.2         # xaha.dev verified publisher

Architecture

Never put animation controller logic directly in a screen widget. Use service + widget separation.

lib/
+-- core/
|   +-- animations/
|       +-- animation_assets.dart          # Typed asset path constants
|       +-- animation_preloader.dart       # Startup preloader (Rive + Lottie)
|       +-- animation_service.dart         # Unified facade for DI
|       +-- rive_controller_factory.dart   # State machine controller factory
|       +-- lottie_cache_manager.dart      # Composition cache singleton
+-- widgets/
|   +-- animations/
|       +-- rive_widget.dart               # Reusable Rive wrapper
|       +-- lottie_widget.dart             # Reusable Lottie wrappers (loop/once)
|       +-- animation_placeholder.dart     # Shimmer shown while loading
|       +-- safe_animation.dart            # Error-boundary animation widget
+-- assets/
    +-- rive/      # .riv files (< 500 KB each)
    +-- lottie/    # .json / .lottie files (< 300 KB each)

Quick Rules (always apply)

  1. Preload on app start — never lazy-load .riv or .json at first render
  2. Call await RiveNative.init() in main() before runApp
  3. Dispose every controller — FileLoader, RiveWidgetController, AnimationController must all be disposed
  4. Use renderCache: RenderCache.raster for all Lottie loops
  5. Choose renderer explicitly — Factory.rive for complex vector; Factory.flutter for simple UI
  6. Never hardcode asset paths — use typed AnimationAssets constants class
  7. Wrap in error boundary — RiveWidgetBuilder loading/failed states for Rive; try/catch for Lottie
  8. Semantic labels on all animations — Semantics(label: '...', child: ...)
  9. Reduce motion — check MediaQuery.of(context).disableAnimations and skip/pause
  10. Test with flutter_test — pump and verify state transitions; mock asset loading
  11. Size budget — .riv < 500 KB; .json < 300 KB; prefer .lottie (dotLottie zip) over raw JSON
  12. Multiple Rive widgets on screen — wrap in RivePanel(useSharedTexture: true) for GPU performance

Minimal Working Examples

Rive — Interactive Toggle (modern API, rive ^0.14.4)

NOTE: RiveAnimation.asset + StateMachineController.fromArtboard is the LEGACY API. Use FileLoader + RiveWidgetController + RiveWidgetBuilder for all new code.

import 'package:flutter/material.dart';
import 'package:rive/rive.dart';

class AnimatedToggle extends StatefulWidget {
  final ValueChanged<bool> onChanged;
  const AnimatedToggle({super.key, required this.onChanged});

  @override
  State<AnimatedToggle> createState() => _AnimatedToggleState();
}

class _AnimatedToggleState extends State<AnimatedToggle> {
  late final FileLoader _fileLoader;
  late final RiveWidgetController _controller;
  SMIBool? _isOnInput;
  bool _isOn = false;

  @override
  void initState() {
    super.initState();
    _fileLoader = FileLoader.fromAsset(
      AnimationAssets.toggle,
      riveFactory: Factory.rive,
    );
    _controller = RiveWidgetController(
      _fileLoader,
      artboardSelector: ArtboardSelector.byName('ToggleArtboard'),
      stateMachineSelector: StateMachineSelector.byName('Toggle'),
    );
    _isOnInput =
        _controller.stateMachine('Toggle').input<bool>('isOn') as SMIBool?;
  }

  @override
  void dispose() {
    _fileLoader.dispose();
    _controller.dispose();
    super.dispose();
  }

  void _toggle() {
    final newValue = !_isOn;
    setState(() => _isOn = newValue);
    _isOnInput?.change(newValue);
    widget.onChanged(newValue);
  }

  @override
  Widget build(BuildContext context) {
    final reduceMotion = MediaQuery.of(context).disableAnimations;
    return Semantics(
      label: 'Toggle button',
      button: true,
      child: GestureDetector(
        onTap: reduceMotion ? null : _toggle,
        child: reduceMotion
            ? _StaticToggleFallback(isOn: _isOn)
            : SizedBox(
                width: 64,
                height: 32,
                child: RiveWidgetBuilder(
                  fileLoader: _fileLoader,
                  builder: (context, state) => switch (state) {
                    RiveLoading() => const Center(
                        child: CircularProgressIndicator(strokeWidth: 2),
                      ),
                    RiveFailed() => const Icon(
                        Icons.error_outline,
                        color: Colors.red,
                        size: 20,
                      ),
                    RiveLoaded() => RiveWidget(
                        controller: _controller,
                        fit: BoxFit.contain,
                      ),
                  },
                ),
              ),
      ),
    );
  }
}

Lottie — Cached Empty State

import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart';

class EmptyStateAnimation extends StatefulWidget {
  final String message;
  const EmptyStateAnimation({super.key, required this.message});

  @override
  State<EmptyStateAnimation> createState() => _EmptyStateAnimationState();
}

class _EmptyStateAnimationState extends State<EmptyStateAnimation>
    with SingleTickerProviderStateMixin {
  late final AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this);
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final reduceMotion = MediaQuery.of(context).disableAnimations;
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        Semantics(
          label: 'Empty state illustration',
          child: Lottie.asset(
            AnimationAssets.emptyState,
            controller: reduceMotion ? (_controller..stop()) : _controller,
            renderCache: RenderCache.raster,
            frameRate: FrameRate.composition,
            width: 240,
            height: 240,
            fit: BoxFit.contain,
            onLoaded: (composition) {
              if (!reduceMotion) {
                _controller
                  ..duration = composition.duration
                  ..repeat();
              }
            },
          ),
        ),
        const SizedBox(height: 16),
        Text(widget.message, style: Theme.of(context).textTheme.bodyLarge),
      ],
    );
  }
}

Typed Asset Constants

// lib/core/animations/animation_assets.dart
abstract final class AnimationAssets {
  // Rive
  static const String toggle       = 'assets/rive/toggle.riv';
  static const String authFlow     = 'assets/rive/auth_flow.riv';
  static const String progressRing = 'assets/rive/progress_ring.riv';
  static const String onboarding   = 'assets/rive/onboarding.riv';
  static const String bottomNav    = 'assets/rive/bottom_nav.riv';

  // Lottie
  static const String splashLogo   = 'assets/lottie/splash_logo.lottie';
  static const String emptyState   = 'assets/lottie/empty_state.lottie';
  static const String successCheck = 'assets/lottie/success_check.lottie';
  static const String errorShake   = 'assets/lottie/error_shake.lottie';
  static const String skeleton     = 'assets/lottie/skeleton.lottie';
  static const String confetti     = 'assets/lottie/confetti.lottie';
}

pubspec.yaml Asset Registration

flutter:
  assets:
    - assets/rive/
    - assets/lottie/

When to Read Reference Files

  • Building a Rive state machine, data binding, or multi-artboard flow → read references/rive.md
  • Building a Lottie scene with dynamic color, runtime text, or performance tuning → read references/lottie.md
  • Setting up service layer, DI, preloading, CI/CD size checks, or code review checklist → read references/architecture.md

Related Skills

  • flutter-mobile — Flutter 3.41.x screen patterns, Riverpod, Freezed. Load alongside for full feature context.
  • mobile-design — Load BEFORE this skill for touch psychology and MFRI scoring
  • riverpod-patterns — Wire animation state to Riverpod providers
  • ui-standards-tokens — ThemeData tokens for static fallback widgets
  • accessibility-audit — WCAG 2.1 AA audit including reduce-motion compliance
저장한 Skills