Flutter에서 애니메이션을 어떻게 구현하나요?
질문
Flutter에서 다양한 애니메이션을 구현하는 방법과 종류에 대해 설명해주세요.
답변
Flutter는 사용자 경험을 향상시킬 수 있는 다양한 애니메이션 기법을 제공합니다. 크게 암묵적 애니메이션과 명시적 애니메이션으로 나눌 수 있습니다.
1. 암묵적 애니메이션 (Implicit Animation)
속성 변화를 자동으로 애니메이션화하는 간단한 방법으로, 'Animated'로 시작하는 위젯들이 여기에 해당합니다.
import 'package:flutter/material.dart';
class ImplicitAnimationExample extends StatefulWidget {
@override
_ImplicitAnimationExampleState createState() => _ImplicitAnimationExampleState();
}
class _ImplicitAnimationExampleState extends State<ImplicitAnimationExample> {
bool _isExpanded = false;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AnimatedContainer(
duration: Duration(milliseconds: 500),
curve: Curves.easeInOut,
width: _isExpanded ? 200 : 100,
height: _isExpanded ? 200 : 100,
color: _isExpanded ? Colors.blue : Colors.red,
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
setState(() {
_isExpanded = !_isExpanded;
});
},
child: Text(_isExpanded ? '축소하기' : '확장하기'),
),
],
),
),
);
}
}
주요 암묵적 애니메이션 위젯:
- AnimatedContainer: 크기, 색상, 패딩 등 변화
- AnimatedOpacity: 투명도 변화
- AnimatedPositioned: Stack 내 위치 변화
- AnimatedCrossFade: 두 위젯 간 크로스페이드
- AnimatedSwitcher: 위젯 전환 애니메이션
- AnimatedAlign: 정렬 변화
- AnimatedPadding: 패딩 변화
- AnimatedSize: 크기 변화
2. 명시적 애니메이션 (Explicit Animation)
애니메이션 컨트롤러를 통해 직접 제어하는 방식으로, 더 복잡하지만 세밀한 제어가 가능합니다.
import 'package:flutter/material.dart';
class ExplicitAnimationExample extends StatefulWidget {
@override
_ExplicitAnimationExampleState createState() => _ExplicitAnimationExampleState();
}
class _ExplicitAnimationExampleState extends State<ExplicitAnimationExample>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(seconds: 2),
vsync: this,
);
_animation = CurvedAnimation(
parent: _controller,
curve: Curves.elasticOut,
);
_animation = Tween<double>(begin: 0, end: 200).animate(_animation);
_animation.addListener(() {
setState(() {});
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: _animation.value,
height: _animation.value,
color: Colors.blue,
),
SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () => _controller.forward(),
child: Text('시작'),
),
SizedBox(width: 10),
ElevatedButton(
onPressed: () => _controller.reverse(),
child: Text('역방향'),
),
],
),
],
),
),
);
}
}
AnimatedBuilder 사용하기
리빌드를 최적화하기 위해 AnimatedBuilder
를 사용할 수 있습니다:
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Transform.rotate(
angle: _controller.value * 2 * 3.14159,
child: Container(
width: 100 + (_controller.value * 100),
height: 100 + (_controller.value * 100),
color: Colors.blue,
child: child, // 변경되지 않는 부분
),
);
},
child: Center(
child: Text(
'회전',
style: TextStyle(color: Colors.white),
),
),
),
),
);
}
3. Hero 애니메이션
화면 간 전환 시 위젯을 부드럽게 이동시키는 애니메이션입니다.
// 첫 번째 화면
Hero(
tag: 'logo', // 동일한 태그로 연결
child: Image.asset('assets/logo.png', width: 100),
)
// 두 번째 화면
Hero(
tag: 'logo', // 동일한 태그로 연결
child: Image.asset('assets/logo.png', width: 300),
)
4. TweenAnimationBuilder
상태 없이도 애니메이션을 구현할 수 있는 편리한 위젯입니다:
TweenAnimationBuilder<double>(
tween: Tween<double>(begin: 0, end: 200),
duration: Duration(seconds: 2),
builder: (context, value, child) {
return Container(
width: value,
height: value,
color: Colors.blue,
child: child,
);
},
child: Center(child: Text('크기 변화', style: TextStyle(color: Colors.white))),
)
5. 페이지 전환 애니메이션
커스텀 페이지 전환 애니메이션을 구현할 수 있습니다:
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) => SecondPage(),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
var begin = Offset(1.0, 0.0);
var end = Offset.zero;
var curve = Curves.easeInOut;
var tween = Tween(begin: begin, end: end)
.chain(CurveTween(curve: curve));
return SlideTransition(
position: animation.drive(tween),
child: child,
);
},
transitionDuration: Duration(milliseconds: 500),
),
);
6. Lottie 애니메이션
Adobe After Effects로 만든 고급 애니메이션을 사용할 수 있습니다:
// pubspec.yaml에 의존성 추가
// dependencies:
// lottie: ^2.3.0
import 'package:lottie/lottie.dart';
Lottie.asset(
'assets/animations/animation.json',
width: 200,
height: 200,
fit: BoxFit.contain,
)
7. 애니메이션 성능 최적화 팁
RepaintBoundary 사용: 애니메이션 위젯을 감싸 불필요한 리페인팅 방지
RepaintBoundary(child: AnimatedWidget())
setState 대신 AnimatedBuilder 사용: 전체 위젯 트리 리빌드 방지
불필요한 애니메이션 피하기: 화면 밖 위젯의 애니메이션은 비활성화
DevTools로 성능 모니터링:
flutter run --profile
로 프로파일링
8. AnimationStatusListener 활용
애니메이션의 상태 변화를 감지하여 다른 동작 연결:
_animation.addStatusListener((status) {
if (status == AnimationStatus.completed) {
// 애니메이션 완료 시 동작
} else if (status == AnimationStatus.dismissed) {
// 애니메이션 초기화 시 동작
}
});
결론
Flutter는 다양한 애니메이션 기법을 제공하며, 상황에 따라 적절한 방식을 선택할 수 있습니다:
- 간단한 애니메이션: 암묵적 애니메이션 위젯 사용
- 복잡한 제어가 필요한 경우: 명시적 애니메이션과 컨트롤러 사용
- 화면 전환: Hero 애니메이션 또는 PageRouteBuilder 활용
- 고급 애니메이션: Lottie 또는 CustomPainter 사용
애니메이션은 앱의 사용자 경험을 향상시키는 중요한 요소이지만, 적절한 사용이 중요합니다. 사용자 인터랙션에 자연스럽게 반응하고 상태 변화를 부드럽게 전달하는 데 초점을 맞추면 좋습니다.