Flutter에서 애니메이션은 어떻게 구현하나요?
질문
Flutter에서 애니메이션을 구현하는 다양한 방법에 대해 설명해주세요.
답변
Flutter는 매끄럽고 세련된 애니메이션을 쉽게 구현할 수 있는 다양한 방법을 제공합니다. 애니메이션은 사용자 경험을 향상시키고 앱에 생동감을 부여하는 중요한 요소입니다. Flutter의 애니메이션 시스템은 단순한 전환부터 복잡한 물리 기반 애니메이션까지 폭넓게 지원합니다.
1. 암시적 애니메이션 (Implicit Animations)
암시적 애니메이션은 Flutter에서 가장 간단한 애니메이션 구현 방법입니다. 이는 기존 위젯의 애니메이션 버전(AnimatedFoo
위젯)을 사용하여 속성 변경 시 자동으로 애니메이션이 적용되도록 합니다.
AnimatedContainer 예제
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: AnimatedContainer(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
width: _isExpanded ? 200.0 : 100.0,
height: _isExpanded ? 200.0 : 100.0,
color: _isExpanded ? Colors.blue : Colors.red,
child: Center(
child: Text('탭하세요'),
),
),
);
}
}
주요 암시적 애니메이션 위젯들
- AnimatedContainer: 크기, 색상, 패딩 등의 변화를 애니메이션화
- AnimatedOpacity: 투명도 변화를 애니메이션화
- AnimatedPositioned: 위치 변화를 애니메이션화 (Stack 내부에서 사용)
- AnimatedPadding: 패딩 변화를 애니메이션화
- AnimatedAlign: 정렬 변화를 애니메이션화
- AnimatedSwitcher: 자식 위젯 전환을 애니메이션화
- AnimatedCrossFade: 두 위젯 간의 교차 페이드 애니메이션
TweenAnimationBuilder
특정 속성에 대한 커스텀 애니메이션이 필요한 경우 TweenAnimationBuilder
를 사용할 수 있습니다:
TweenAnimationBuilder<double>(
tween: Tween<double>(begin: 0.0, end: 1.0),
duration: Duration(seconds: 1),
builder: (BuildContext context, double value, Widget? child) {
return Opacity(
opacity: value,
child: Container(
width: 200 * value,
height: 200 * value,
color: Colors.blue,
child: child,
),
);
},
child: Text('애니메이션'),
)
2. 명시적 애니메이션 (Explicit Animations)
명시적 애니메이션은 애니메이션 컨트롤러를 사용하여 직접 애니메이션의 진행 상태를 제어합니다. 이 방식은 더 복잡한 애니메이션이나 정밀한 제어가 필요할 때 유용합니다.
AnimationController와 Tween 사용하기
class RotatingSquare extends StatefulWidget {
@override
_RotatingSquareState createState() => _RotatingSquareState();
}
class _RotatingSquareState extends State<RotatingSquare> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _rotationAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(seconds: 2),
vsync: this,
);
_rotationAnimation = Tween<double>(
begin: 0.0,
end: 2 * 3.14159, // 2π (한 바퀴)
).animate(_controller);
// 애니메이션 반복
_controller.repeat();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Transform.rotate(
angle: _rotationAnimation.value,
child: Container(
width: 100,
height: 100,
color: Colors.blue,
),
);
},
);
}
}
애니메이션 컨트롤러 메서드
애니메이션 컨트롤러는 애니메이션 제어를 위한 다양한 메서드를 제공합니다:
// 애니메이션 시작
_controller.forward();
// 애니메이션 역방향 시작
_controller.reverse();
// 특정 위치로 이동
_controller.animateTo(0.5);
// 애니메이션 반복
_controller.repeat();
// 애니메이션 정지
_controller.stop();
// 애니메이션 초기화
_controller.reset();
복합 애니메이션
여러 애니메이션을 결합하여 복잡한 효과를 만들 수 있습니다:
// 연속 애니메이션
final Animation<double> _sizeAnimation = Tween<double>(
begin: 0.0,
end: 100.0,
).animate(
CurvedAnimation(
parent: _controller,
curve: Interval(0.0, 0.5, curve: Curves.elasticOut),
),
);
final Animation<double> _opacityAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(
CurvedAnimation(
parent: _controller,
curve: Interval(0.5, 1.0, curve: Curves.easeIn),
),
);
3. 특별한 애니메이션 위젯들
Hero 애니메이션
Hero
위젯을 사용하면 화면 전환 시 요소가 부드럽게 변형되는 효과를 만들 수 있습니다:
// 첫 번째 화면
Hero(
tag: 'imageHero',
child: Image.network('https://example.com/image.jpg'),
)
// 두 번째 화면
Hero(
tag: 'imageHero',
child: Image.network('https://example.com/image.jpg'),
)
동일한 태그를 가진 Hero 위젯이 다른 화면에 있으면, 화면 전환 시 자동으로 애니메이션이 적용됩니다.
SlideTransition
위젯의 위치 변화에 애니메이션을 적용합니다:
SlideTransition(
position: Tween<Offset>(
begin: Offset(-1.0, 0.0),
end: Offset.zero,
).animate(_controller),
child: Container(
width: 100,
height: 100,
color: Colors.blue,
),
)
FadeTransition
투명도 변화에 애니메이션을 적용합니다:
FadeTransition(
opacity: Tween<double>(
begin: 0.0,
end: 1.0,
).animate(_controller),
child: Container(
width: 100,
height: 100,
color: Colors.blue,
),
)
ScaleTransition
크기 변화에 애니메이션을 적용합니다:
ScaleTransition(
scale: Tween<double>(
begin: 0.0,
end: 1.0,
).animate(_controller),
child: Container(
width: 100,
height: 100,
color: Colors.blue,
),
)
RotationTransition
회전 애니메이션을 적용합니다:
RotationTransition(
turns: Tween<double>(
begin: 0.0,
end: 1.0, // 한 바퀴
).animate(_controller),
child: Container(
width: 100,
height: 100,
color: Colors.blue,
),
)
4. 물리 기반 애니메이션
Flutter는 물리학 법칙을 따르는 자연스러운 애니메이션을 위한 클래스를 제공합니다.
SpringSimulation
스프링 움직임을 시뮬레이션합니다:
final SpringDescription _spring = SpringDescription(
mass: 1.0,
stiffness: 500.0,
damping: 10.0,
);
final Simulation _simulation = SpringSimulation(
_spring,
0.0, // 시작 위치
1.0, // 목표 위치
0.0, // 초기 속도
);
_controller.animateWith(_simulation);
GravitySimulation
중력 효과를 시뮬레이션합니다:
final Simulation _simulation = GravitySimulation(
9.8, // 중력 가속도
0.0, // 시작 위치
100.0, // 끝 위치
0.0, // 초기 속도
);
_controller.animateWith(_simulation);
5. 애니메이션 커브
애니메이션의 진행 속도를 조절하는 커브는 애니메이션의 느낌을 크게 바꿀 수 있습니다:
// 선형 (일정한 속도)
curve: Curves.linear
// 이즈 인/아웃 (시작과 끝에서 느림)
curve: Curves.easeInOut
// 이즈 인 (시작에서 느림)
curve: Curves.easeIn
// 이즈 아웃 (끝에서 느림)
curve: Curves.easeOut
// 바운스 효과
curve: Curves.bounceOut
// 탄성 효과
curve: Curves.elasticIn
// 커스텀 커브
curve: Cubic(0.2, 0.0, 0.8, 1.0)
6. 애니메이션 상태 리스너
애니메이션 상태 변화를 감지하고 대응할 수 있습니다:
_controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
// 애니메이션 완료 시 실행할 코드
_controller.reverse();
} else if (status == AnimationStatus.dismissed) {
// 애니메이션 초기 상태로 돌아왔을 때 실행할 코드
_controller.forward();
}
});
7. Lottie 애니메이션
Adobe After Effects로 만든 애니메이션을 Flutter에서 직접 사용할 수 있는 Lottie 라이브러리:
// pubspec.yaml
// dependencies:
// lottie: ^2.6.0
import 'package:lottie/lottie.dart';
Lottie.asset(
'assets/animation.json',
width: 200,
height: 200,
fit: BoxFit.contain,
)
8. Rive 애니메이션
Rive(이전의 Flare)는 인터랙티브 애니메이션을 위한 도구로, Flutter에서 쉽게 통합됩니다:
// pubspec.yaml
// dependencies:
// rive: ^0.11.4
import 'package:rive/rive.dart';
RiveAnimation.asset(
'assets/animation.riv',
controllers: [_controller],
onInit: (_) => setState(() {}),
)
9. 애니메이션 최적화 팁
효율적인 애니메이션을 위한 몇 가지 팁:
- RepaintBoundary 사용: 애니메이션 위젯을
RepaintBoundary
로 감싸서 다시 그리는 영역을 제한합니다.
RepaintBoundary(
child: AnimatedWidget(),
)
필요할 때만 setState 호출: 애니메이션 상태 업데이트가 필요한 경우에만
setState
를 호출합니다.Ticker 대신 SingleTickerProviderStateMixin 사용: 단일 애니메이션 컨트롤러를 사용하는 경우
SingleTickerProviderStateMixin
을 사용합니다.복잡한 계산은 빌드 메서드 외부에서: 애니메이션 프레임마다 실행되는 빌드 메서드에서 복잡한 계산을 피합니다.
const 생성자 최대한 활용: 변경되지 않는 위젯에는
const
생성자를 사용합니다.
10. 실제 애니메이션 예제
페이지 전환 애니메이션
Navigator.of(context).push(
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.ease;
var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
return SlideTransition(
position: animation.drive(tween),
child: child,
);
},
),
);
시퀀스 애니메이션
여러 애니메이션을 순차적으로 실행:
class SequenceAnimationExample extends StatefulWidget {
@override
_SequenceAnimationExampleState createState() => _SequenceAnimationExampleState();
}
class _SequenceAnimationExampleState extends State<SequenceAnimationExample> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _scaleAnimation;
late Animation<double> _rotateAnimation;
late Animation<Color?> _colorAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(seconds: 3),
vsync: this,
);
_scaleAnimation = Tween<double>(begin: 1.0, end: 2.0).animate(
CurvedAnimation(
parent: _controller,
curve: Interval(0.0, 0.5, curve: Curves.easeOut),
),
);
_rotateAnimation = Tween<double>(begin: 0.0, end: 3.14).animate(
CurvedAnimation(
parent: _controller,
curve: Interval(0.5, 0.8, curve: Curves.easeInOut),
),
);
_colorAnimation = ColorTween(begin: Colors.blue, end: Colors.red).animate(
CurvedAnimation(
parent: _controller,
curve: Interval(0.8, 1.0, curve: Curves.easeIn),
),
);
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Transform.scale(
scale: _scaleAnimation.value,
child: Transform.rotate(
angle: _rotateAnimation.value,
child: Container(
width: 100,
height: 100,
color: _colorAnimation.value,
),
),
);
},
);
}
}
요약
Flutter에서 애니메이션을 구현하는 방법은 다양합니다:
- 암시적 애니메이션:
AnimatedContainer
,AnimatedOpacity
등의 위젯을 사용하여 간단하게 구현 - 명시적 애니메이션:
AnimationController
와Tween
을 사용하여 더 세밀하게 제어 - 전환 애니메이션:
Hero
,SlideTransition
등을 사용하여 화면 전환 시 애니메이션 적용 - 물리 기반 애니메이션: 자연스러운 움직임을 위한 물리 시뮬레이션 활용
- 서드파티 애니메이션: Lottie, Rive 등의 도구를 활용한 복잡한 애니메이션 구현
애니메이션을 선택할 때는 사용자 경험, 성능, 구현 복잡성 등을 고려해야 합니다. 간단한 애니메이션은 암시적 애니메이션으로 시작하고, 더 복잡한 제어가 필요할 경우 명시적 애니메이션으로 전환하는 것이 좋습니다.
적절한 애니메이션은 사용자 경험을 크게 향상시키지만, 과도한 애니메이션은 오히려 사용자를 혼란스럽게 하거나 앱 성능에 영향을 줄 수 있으므로, 균형 잡힌 접근이 중요합니다.