Flutter의 암시적(Implicit) 애니메이션 위젯은 무엇인가요?
질문
Flutter의 암시적(Implicit) 애니메이션 위젯은 무엇이며, 어떻게 사용하나요?
답변
Flutter의 암시적(Implicit) 애니메이션 위젯은 애니메이션을 쉽게 구현할 수 있도록 만들어진 위젯들입니다. 이러한 위젯들은 Animated
로 시작하는 이름을 가지며, 속성 값의 변경을 감지하여 자동으로 이전 값에서 새로운 값으로 부드럽게 애니메이션 처리합니다. 명시적(Explicit) 애니메이션과 달리 애니메이션 컨트롤러를 직접 관리할 필요가 없어 사용이 간편합니다.
암시적 애니메이션의 작동 원리
암시적 애니메이션 위젯은 다음과 같이 작동합니다:
- 속성 값이 변경되면 위젯이 이를 감지합니다.
- 지정된 지속 시간(duration)과 커브(curve)에 따라 이전 값에서 새로운 값으로 자동으로 애니메이션을 적용합니다.
- 개발자는 애니메이션 컨트롤러를 생성하거나 관리할 필요 없이 단순히 위젯의 속성 값만 변경하면 됩니다.
주요 암시적 애니메이션 위젯
1. AnimatedContainer
가장 범용적으로 사용되는 암시적 애니메이션 위젯으로, Container의 모든 속성(크기, 색상, 테두리, 패딩 등)에 애니메이션을 적용할 수 있습니다.
class AnimatedContainerExample extends StatefulWidget {
@override
_AnimatedContainerExampleState createState() => _AnimatedContainerExampleState();
}
class _AnimatedContainerExampleState extends State<AnimatedContainerExample> {
bool _isExpanded = false;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
setState(() {
_isExpanded = !_isExpanded;
});
},
child: Center(
child: AnimatedContainer(
width: _isExpanded ? 200.0 : 100.0,
height: _isExpanded ? 200.0 : 100.0,
color: _isExpanded ? Colors.blue : Colors.red,
alignment: _isExpanded ? Alignment.center : Alignment.topLeft,
duration: Duration(milliseconds: 500),
curve: Curves.fastOutSlowIn,
child: FlutterLogo(size: 50),
),
),
);
}
}
이 예제에서는 탭할 때마다 AnimatedContainer
의 크기, 색상, 정렬이 애니메이션과 함께 변경됩니다.
2. AnimatedOpacity
위젯의 투명도(opacity)를 애니메이션화합니다.
AnimatedOpacity(
opacity: _visible ? 1.0 : 0.0,
duration: Duration(milliseconds: 500),
child: Container(
width: 200,
height: 200,
color: Colors.blue,
child: Center(
child: Text(
'안녕하세요!',
style: TextStyle(color: Colors.white, fontSize: 24),
),
),
),
)
이 위젯을 사용하면 요소를 부드럽게 나타내거나 사라지게 할 수 있습니다.
3. AnimatedPositioned
Stack
내에서 위젯의 위치를 애니메이션화합니다.
Stack(
children: [
AnimatedPositioned(
left: _left,
top: _top,
width: 100,
height: 100,
duration: Duration(milliseconds: 500),
curve: Curves.easeInOut,
child: GestureDetector(
onTap: () {
setState(() {
_left = _left == 0 ? 100 : 0;
_top = _top == 0 ? 100 : 0;
});
},
child: Container(
color: Colors.blue,
child: Center(
child: Text(
'탭하세요',
style: TextStyle(color: Colors.white),
),
),
),
),
),
],
)
이 예제는 사용자가 탭할 때마다 상자가 다른 위치로 부드럽게 이동합니다.
4. AnimatedPadding
패딩 값의 변화에 애니메이션을 적용합니다.
AnimatedPadding(
padding: _isExpanded
? EdgeInsets.all(20)
: EdgeInsets.all(5),
duration: Duration(milliseconds: 500),
curve: Curves.easeInOut,
child: Container(
color: Colors.green,
child: Text('패딩 애니메이션'),
),
)
5. AnimatedAlign
정렬 위치에 애니메이션을 적용합니다.
AnimatedAlign(
alignment: _isLeftAligned
? Alignment.centerLeft
: Alignment.centerRight,
duration: Duration(milliseconds: 500),
curve: Curves.easeInOut,
child: Container(
width: 100,
height: 100,
color: Colors.purple,
child: Center(
child: Text(
'좌우 이동',
style: TextStyle(color: Colors.white),
),
),
),
)
6. AnimatedCrossFade
두 위젯 사이를 크로스페이드 애니메이션으로 전환합니다.
AnimatedCrossFade(
firstChild: Container(
width: 200,
height: 200,
color: Colors.blue,
child: Center(child: Text('첫 번째 위젯')),
),
secondChild: Container(
width: 200,
height: 200,
color: Colors.green,
child: Center(child: Text('두 번째 위젯')),
),
crossFadeState: _showFirst
? CrossFadeState.showFirst
: CrossFadeState.showSecond,
duration: Duration(milliseconds: 500),
)
7. AnimatedSize
위젯의 크기 변화에 애니메이션을 적용합니다. 반드시 SingleTickerProviderStateMixin
을 사용해야 합니다.
class AnimatedSizeExample extends StatefulWidget {
@override
_AnimatedSizeExampleState createState() => _AnimatedSizeExampleState();
}
class _AnimatedSizeExampleState extends State<AnimatedSizeExample>
with SingleTickerProviderStateMixin {
bool _isExpanded = false;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
setState(() {
_isExpanded = !_isExpanded;
});
},
child: Center(
child: Container(
color: Colors.amber,
child: AnimatedSize(
duration: Duration(milliseconds: 500),
curve: Curves.easeInOut,
child: Container(
width: _isExpanded ? 200 : 100,
height: _isExpanded ? 200 : 100,
color: Colors.blue,
child: Center(
child: Text('크기 변경'),
),
),
),
),
),
);
}
}
8. AnimatedSwitcher
자식 위젯을 교체할 때 애니메이션을 적용합니다. key
를 사용하여 위젯이 변경되었음을 식별합니다.
AnimatedSwitcher(
duration: Duration(milliseconds: 500),
transitionBuilder: (Widget child, Animation<double> animation) {
return ScaleTransition(scale: animation, child: child);
},
child: _isShowingFirst
? Container(
key: ValueKey<int>(1),
width: 200,
height: 200,
color: Colors.blue,
child: Center(child: Text('첫 번째 내용')),
)
: Container(
key: ValueKey<int>(2),
width: 200,
height: 200,
color: Colors.green,
child: Center(child: Text('두 번째 내용')),
),
)
9. AnimatedDefaultTextStyle
텍스트 스타일 변경에 애니메이션을 적용합니다.
AnimatedDefaultTextStyle(
style: _isLarge
? TextStyle(
fontSize: 32,
color: Colors.blue,
fontWeight: FontWeight.bold,
)
: TextStyle(
fontSize: 16,
color: Colors.red,
fontWeight: FontWeight.normal,
),
duration: Duration(milliseconds: 500),
child: Text('텍스트 스타일 애니메이션'),
)
10. AnimatedPhysicalModel
물리적 모델(그림자, 모서리 등)에 애니메이션을 적용합니다.
AnimatedPhysicalModel(
duration: Duration(milliseconds: 500),
curve: Curves.fastOutSlowIn,
elevation: _elevated ? 20 : 0,
shape: BoxShape.rectangle,
shadowColor: Colors.black,
color: Colors.white,
borderRadius: BorderRadius.circular(_elevated ? 20 : 0),
child: Container(
height: 150,
width: 150,
child: Center(
child: Text('그림자 효과'),
),
),
)
복합 암시적 애니메이션 예제
여러 암시적 애니메이션 위젯을 함께 사용하여 더 복잡한 효과를 만들 수 있습니다:
class ComplexAnimationExample extends StatefulWidget {
@override
_ComplexAnimationExampleState createState() => _ComplexAnimationExampleState();
}
class _ComplexAnimationExampleState extends State<ComplexAnimationExample> {
bool _isExpanded = false;
bool _isVisible = true;
void _toggleState() {
setState(() {
_isExpanded = !_isExpanded;
// 확장 상태가 변경될 때마다 잠시 후 가시성도 토글
Future.delayed(Duration(milliseconds: 300), () {
setState(() {
_isVisible = !_isVisible;
});
});
});
}
@override
Widget build(BuildContext context) {
return Center(
child: GestureDetector(
onTap: _toggleState,
child: AnimatedContainer(
duration: Duration(milliseconds: 600),
curve: Curves.easeInOut,
width: _isExpanded ? 300 : 150,
height: _isExpanded ? 300 : 150,
decoration: BoxDecoration(
color: _isExpanded ? Colors.blue : Colors.green,
borderRadius: BorderRadius.circular(_isExpanded ? 30 : 10),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.3),
blurRadius: _isExpanded ? 20 : 5,
spreadRadius: _isExpanded ? 5 : 1,
),
],
),
child: Stack(
children: [
Center(
child: AnimatedOpacity(
opacity: _isVisible ? 1.0 : 0.0,
duration: Duration(milliseconds: 500),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AnimatedContainer(
duration: Duration(milliseconds: 600),
width: _isExpanded ? 100 : 50,
height: _isExpanded ? 100 : 50,
decoration: BoxDecoration(
color: Colors.white,
shape: _isExpanded ? BoxShape.rectangle : BoxShape.circle,
),
child: Icon(
_isExpanded ? Icons.close : Icons.add,
size: _isExpanded ? 50 : 30,
color: Colors.black87,
),
),
SizedBox(height: 20),
AnimatedDefaultTextStyle(
duration: Duration(milliseconds: 600),
style: TextStyle(
fontSize: _isExpanded ? 24 : 16,
color: Colors.white,
fontWeight: _isExpanded ? FontWeight.bold : FontWeight.normal,
),
child: Text('탭하여 확장'),
),
],
),
),
),
AnimatedPositioned(
duration: Duration(milliseconds: 800),
curve: Curves.elasticOut,
right: _isExpanded ? 20 : -80,
bottom: _isExpanded ? 20 : -80,
child: AnimatedOpacity(
opacity: _isExpanded ? 1.0 : 0.0,
duration: Duration(milliseconds: 500),
child: Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: Colors.amber,
shape: BoxShape.circle,
),
child: Icon(
Icons.star,
color: Colors.white,
),
),
),
),
],
),
),
),
);
}
}
이 예제는 여러 암시적 애니메이션 위젯을 함께 사용하여 복합적인 애니메이션 효과를 만듭니다.
TweenAnimationBuilder
Flutter 1.15 버전에서 도입된 TweenAnimationBuilder
는 간단한 사용자 정의 암시적 애니메이션을 만들 수 있는 위젯입니다. 특정 값의 변화에 애니메이션을 적용하고 싶을 때 유용합니다.
TweenAnimationBuilder<double>(
tween: Tween<double>(begin: 0, end: targetValue),
duration: Duration(milliseconds: 500),
builder: (BuildContext context, double value, Widget? child) {
return Transform.rotate(
angle: value,
child: child,
);
},
child: Container(
width: 100,
height: 100,
color: Colors.blue,
child: Center(
child: Text('회전'),
),
),
)
TweenAnimationBuilder
를 사용하면 기존 암시적 애니메이션 위젯에서 지원하지 않는 속성에도 애니메이션을 적용할 수 있습니다.
AnimatedBuilder vs 암시적 애니메이션 위젯
AnimatedBuilder
는 명시적 애니메이션 위젯으로, 애니메이션 컨트롤러를 사용하여 더 세밀한 제어가 가능하지만 설정이 더 복잡합니다.
반면, 암시적 애니메이션 위젯은 사용이 간편하지만 애니메이션의 시작, 정지, 재생 등의 세부적인 제어가 제한적입니다.
// 암시적 애니메이션 (간단함)
AnimatedContainer(
width: _width,
height: _height,
color: _color,
duration: Duration(milliseconds: 500),
)
// 명시적 애니메이션 (세밀한 제어)
AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Container(
width: _widthAnimation.value,
height: _heightAnimation.value,
color: _colorAnimation.value,
);
},
)
암시적 애니메이션의 장단점
장점:
- 간편한 사용: 애니메이션 컨트롤러 없이 쉽게 애니메이션 구현 가능
- 코드 간결성: 적은 코드로 효과적인 애니메이션 구현
- 상태 변화 감지: 위젯 속성 변경을 자동으로 감지하여 애니메이션 적용
단점:
- 제한된 제어: 애니메이션 진행 중 일시 정지, 속도 조절 등의 세밀한 제어가 어려움
- 제한된 커스터마이징: 복잡한 애니메이션 시퀀스 구현에 한계
- 지원되지 않는 속성: 일부 속성은 기본 암시적 애니메이션 위젯으로 지원되지 않음
성능 최적화 팁
- 최소한의 위젯 리빌드: 애니메이션이 필요한 위젯만
setState()
로 업데이트 - RepaintBoundary 사용: 복잡한 애니메이션은
RepaintBoundary
로 감싸서 렌더링 영역 제한 - 불필요한 애니메이션 피하기: 사용자에게 보이지 않는 요소에는 애니메이션 적용 자제
- 적절한 duration 설정: 너무 긴 애니메이션은 사용자 경험을 저하시킬 수 있음
결론
Flutter의 암시적 애니메이션 위젯은 코드 몇 줄로 멋진 애니메이션 효과를 쉽게 구현할 수 있는 강력한 도구입니다. 복잡한 애니메이션 컨트롤러 관리 없이도 UI 요소의 상태 변화에 자연스러운 전환 효과를 적용할 수 있습니다.
단순한 UI 전환과 상태 변화에는 암시적 애니메이션 위젯이 적합하며, 더 복잡하고 세밀한 제어가 필요한 애니메이션에는 명시적 애니메이션(AnimationController, Tween 등)을 고려하는 것이 좋습니다.
암시적 애니메이션 위젯을 효과적으로 사용하면 최소한의 코드로 사용자 경험을 크게 향상시킬 수 있으며, Flutter 앱에 세련된 느낌을 더할 수 있습니다.