애니메이션 기초 — AnimatedContainer, AnimatedOpacity
애니메이션 기초 — AnimatedContainer, AnimatedOpacity
Flutter의 애니메이션은 암시적(Implicit) 과 명시적(Explicit) 두 가지로 나뉩니다. 암시적 애니메이션은 속성 값만 바꾸면 자동으로 전환 효과가 적용되어 매우 간편합니다.
암시적 vs 명시적 애니메이션
| 구분 | 암시적 | 명시적 |
|---|---|---|
| 난이도 | 쉬움 | 어려움 |
| 제어력 | 제한적 | 완전 제어 |
| 사용 시점 | 단순 전환 효과 | 복잡한 시퀀스 |
| 예시 | AnimatedContainer | AnimationController |
면접 포인트: 간단한 애니메이션은 암시적 위젯으로 충분하고, AnimationController는 반복, 역방향, 시퀀스 등 세밀한 제어가 필요할 때 사용합니다.
AnimatedContainer
Container의 속성이 변하면 자동으로 애니메이션이 적용됩니다.
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 — 투명도 전환
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 — 두 위젯 전환
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 — 자식 위젯 교체 시 전환
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
AnimatedPadding(
padding: EdgeInsets.all(_isExpanded ? 32 : 8),
duration: const Duration(milliseconds: 300),
child: Container(color: Colors.blue, height: 100),
)
AnimatedAlign
AnimatedAlign(
alignment: _isLeft ? Alignment.centerLeft : Alignment.centerRight,
duration: const Duration(milliseconds: 500),
child: Container(width: 50, height: 50, color: Colors.red),
)
AnimatedDefaultTextStyle
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 내부)
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
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 | 감속 |
실전 예제: 좋아요 버튼
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로 애니메이션 속도 변화를 조절할 수 있습니다- 단순한 전환에는 암시적 애니메이션, 복잡한 제어가 필요하면 명시적 애니메이션을 사용하세요
댓글 로딩 중...