애니메이션 기초 — AnimatedContainer, AnimatedOpacity

Flutter의 애니메이션은 암시적(Implicit) 과 명시적(Explicit) 두 가지로 나뉩니다. 암시적 애니메이션은 속성 값만 바꾸면 자동으로 전환 효과가 적용되어 매우 간편합니다.


암시적 vs 명시적 애니메이션

구분암시적명시적
난이도쉬움어려움
제어력제한적완전 제어
사용 시점단순 전환 효과복잡한 시퀀스
예시AnimatedContainerAnimationController

면접 포인트: 간단한 애니메이션은 암시적 위젯으로 충분하고, AnimationController는 반복, 역방향, 시퀀스 등 세밀한 제어가 필요할 때 사용합니다.


AnimatedContainer

Container의 속성이 변하면 자동으로 애니메이션이 적용됩니다.

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

  @override
  State<AnimatedContainerExample> createState() =>
      _AnimatedContainerExampleState();
}

class _AnimatedContainerExampleState extends State<AnimatedContainerExample> {
  bool _isExpanded = false;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () => setState(() => _isExpanded = !_isExpanded),
      child: AnimatedContainer(
        duration: const Duration(milliseconds: 300),
        curve: Curves.easeInOut,
        width: _isExpanded ? 300 : 100,
        height: _isExpanded ? 300 : 100,
        decoration: BoxDecoration(
          color: _isExpanded ? Colors.blue : Colors.red,
          borderRadius: BorderRadius.circular(
            _isExpanded ? 16 : 50,
          ),
          boxShadow: [
            BoxShadow(
              color: Colors.black.withOpacity(_isExpanded ? 0.3 : 0.1),
              blurRadius: _isExpanded ? 20 : 5,
              offset: Offset(0, _isExpanded ? 10 : 2),
            ),
          ],
        ),
        alignment: Alignment.center,
        child: const Text(
          '탭하세요',
          style: TextStyle(color: Colors.white),
        ),
      ),
    );
  }
}

AnimatedOpacity — 투명도 전환

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

  @override
  State<FadeExample> createState() => _FadeExampleState();
}

class _FadeExampleState extends State<FadeExample> {
  bool _isVisible = true;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        ElevatedButton(
          onPressed: () => setState(() => _isVisible = !_isVisible),
          child: Text(_isVisible ? '숨기기' : '보이기'),
        ),
        AnimatedOpacity(
          opacity: _isVisible ? 1.0 : 0.0,
          duration: const Duration(milliseconds: 500),
          curve: Curves.easeIn,
          child: Container(
            width: 200,
            height: 200,
            color: Colors.blue,
            child: const Center(child: Text('나타나고 사라짐')),
          ),
        ),
      ],
    );
  }
}

AnimatedCrossFade — 두 위젯 전환

DART
AnimatedCrossFade(
  duration: const Duration(milliseconds: 300),
  crossFadeState: _showFirst
      ? CrossFadeState.showFirst
      : CrossFadeState.showSecond,
  firstChild: Container(
    width: 200,
    height: 200,
    color: Colors.blue,
    child: const Center(child: Text('첫 번째')),
  ),
  secondChild: Container(
    width: 200,
    height: 200,
    color: Colors.red,
    child: const Center(child: Text('두 번째')),
  ),
)

AnimatedSwitcher — 자식 위젯 교체 시 전환

DART
AnimatedSwitcher(
  duration: const Duration(milliseconds: 300),
  transitionBuilder: (child, animation) {
    return ScaleTransition(
      scale: animation,
      child: child,
    );
  },
  child: Text(
    '$_count',
    // key가 달라야 전환 애니메이션이 동작
    key: ValueKey<int>(_count),
    style: const TextStyle(fontSize: 48),
  ),
)

기타 암시적 애니메이션 위젯들

AnimatedPadding

DART
AnimatedPadding(
  padding: EdgeInsets.all(_isExpanded ? 32 : 8),
  duration: const Duration(milliseconds: 300),
  child: Container(color: Colors.blue, height: 100),
)

AnimatedAlign

DART
AnimatedAlign(
  alignment: _isLeft ? Alignment.centerLeft : Alignment.centerRight,
  duration: const Duration(milliseconds: 500),
  child: Container(width: 50, height: 50, color: Colors.red),
)

AnimatedDefaultTextStyle

DART
AnimatedDefaultTextStyle(
  style: TextStyle(
    fontSize: _isLarge ? 36 : 16,
    fontWeight: _isLarge ? FontWeight.bold : FontWeight.normal,
    color: _isLarge ? Colors.blue : Colors.black,
  ),
  duration: const Duration(milliseconds: 300),
  child: const Text('텍스트 애니메이션'),
)

AnimatedPositioned (Stack 내부)

DART
Stack(
  children: [
    AnimatedPositioned(
      duration: const Duration(milliseconds: 500),
      curve: Curves.elasticOut,
      left: _isMoved ? 200 : 0,
      top: _isMoved ? 200 : 0,
      child: Container(
        width: 50,
        height: 50,
        decoration: const BoxDecoration(
          color: Colors.blue,
          shape: BoxShape.circle,
        ),
      ),
    ),
  ],
)

AnimatedSlide

DART
AnimatedSlide(
  offset: _isVisible ? Offset.zero : const Offset(0, 1),
  duration: const Duration(milliseconds: 300),
  child: Container(
    padding: const EdgeInsets.all(16),
    color: Colors.blue,
    child: const Text('슬라이드'),
  ),
)

Curve (이징 함수)

애니메이션의 속도 변화를 결정합니다.

Curve설명
Curves.linear일정 속도
Curves.easeIn천천히 시작
Curves.easeOut천천히 종료
Curves.easeInOut천천히 시작+종료 (가장 흔함)
Curves.bounceOut바운스 효과
Curves.elasticOut탄성 효과
Curves.decelerate감속

실전 예제: 좋아요 버튼

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

  @override
  State<LikeButton> createState() => _LikeButtonState();
}

class _LikeButtonState extends State<LikeButton> {
  bool _isLiked = false;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () => setState(() => _isLiked = !_isLiked),
      child: AnimatedContainer(
        duration: const Duration(milliseconds: 200),
        padding: const EdgeInsets.all(8),
        decoration: BoxDecoration(
          color: _isLiked ? Colors.red.shade50 : Colors.grey.shade100,
          borderRadius: BorderRadius.circular(20),
        ),
        child: Row(
          mainAxisSize: MainAxisSize.min,
          children: [
            AnimatedSwitcher(
              duration: const Duration(milliseconds: 200),
              transitionBuilder: (child, animation) {
                return ScaleTransition(scale: animation, child: child);
              },
              child: Icon(
                _isLiked ? Icons.favorite : Icons.favorite_border,
                key: ValueKey(_isLiked),
                color: _isLiked ? Colors.red : Colors.grey,
              ),
            ),
            const SizedBox(width: 4),
            const Text('좋아요'),
          ],
        ),
      ),
    );
  }
}

정리

  • 암시적 애니메이션은 속성 값만 바꾸면 자동으로 전환됩니다
  • AnimatedContainer가 가장 범용적이고 자주 사용됩니다
  • AnimatedSwitcher에서는 key가 달라야 전환이 동작합니다
  • Curve로 애니메이션 속도 변화를 조절할 수 있습니다
  • 단순한 전환에는 암시적 애니메이션, 복잡한 제어가 필요하면 명시적 애니메이션을 사용하세요
댓글 로딩 중...