애니메이션 심화 — AnimationController, Tween, Curve
애니메이션 심화 — AnimationController, Tween, Curve
명시적 애니메이션은 AnimationController를 직접 제어하여 반복, 역방향, 시퀀스 등 세밀한 애니메이션을 구현합니다.
AnimationController 기본
class PulseAnimation extends StatefulWidget {
const PulseAnimation({super.key});
@override
State<PulseAnimation> createState() => _PulseAnimationState();
}
class _PulseAnimationState extends State<PulseAnimation>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 1),
vsync: this, // TickerProvider (프레임 동기화)
);
// 애니메이션 시작
_controller.repeat(reverse: true); // 반복 + 역방향
}
@override
void dispose() {
_controller.dispose(); // 반드시 해제!
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Transform.scale(
scale: 1.0 + (_controller.value * 0.2), // 1.0 ~ 1.2
child: child,
);
},
child: Container(
width: 100,
height: 100,
decoration: const BoxDecoration(
color: Colors.blue,
shape: BoxShape.circle,
),
),
);
}
}
SingleTickerProviderStateMixin vs TickerProviderStateMixin
| Mixin | 용도 |
|---|---|
SingleTickerProviderStateMixin | AnimationController 1개일 때 |
TickerProviderStateMixin | AnimationController 여러 개일 때 |
Tween — 값 범위 매핑
AnimationController는 0.0~1.0 사이 값을 생성합니다. Tween으로 원하는 범위로 변환합니다.
class TweenExample extends StatefulWidget {
const TweenExample({super.key});
@override
State<TweenExample> createState() => _TweenExampleState();
}
class _TweenExampleState extends State<TweenExample>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
late final Animation<double> _sizeAnimation;
late final Animation<Color?> _colorAnimation;
late final Animation<double> _rotationAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
// 크기: 50 → 200
_sizeAnimation = Tween<double>(
begin: 50,
end: 200,
).animate(CurvedAnimation(
parent: _controller,
curve: Curves.elasticOut,
));
// 색상: 빨강 → 파랑
_colorAnimation = ColorTween(
begin: Colors.red,
end: Colors.blue,
).animate(_controller);
// 회전: 0 → 2π (360도)
_rotationAnimation = Tween<double>(
begin: 0,
end: 2 * 3.14159,
).animate(CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
));
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Transform.rotate(
angle: _rotationAnimation.value,
child: Container(
width: _sizeAnimation.value,
height: _sizeAnimation.value,
color: _colorAnimation.value,
),
);
},
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
if (_controller.isCompleted) {
_controller.reverse();
} else {
_controller.forward();
}
},
child: const Text('애니메이션 토글'),
),
],
);
}
}
AnimationController 제어 메서드
_controller.forward(); // 정방향 재생
_controller.reverse(); // 역방향 재생
_controller.repeat(); // 반복
_controller.repeat(reverse: true); // 왕복 반복
_controller.stop(); // 정지
_controller.reset(); // 초기값으로 리셋
_controller.animateTo(0.5); // 특정 값까지 애니메이션
// 상태 확인
_controller.isAnimating; // 실행 중인지
_controller.isCompleted; // 완료 상태인지
_controller.isDismissed; // 초기 상태인지
_controller.value; // 현재 값 (0.0 ~ 1.0)
상태 리스너
_controller.addStatusListener((status) {
switch (status) {
case AnimationStatus.forward:
print('정방향 재생 중');
case AnimationStatus.completed:
print('완료');
case AnimationStatus.reverse:
print('역방향 재생 중');
case AnimationStatus.dismissed:
print('초기 상태');
}
});
시퀀스 애니메이션 (Staggered)
여러 애니메이션을 순차적으로 실행합니다.
class StaggeredAnimation extends StatefulWidget {
const StaggeredAnimation({super.key});
@override
State<StaggeredAnimation> createState() => _StaggeredAnimationState();
}
class _StaggeredAnimationState extends State<StaggeredAnimation>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
late final Animation<double> _opacity;
late final Animation<double> _width;
late final Animation<double> _height;
late final Animation<EdgeInsets> _padding;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
// 0% ~ 30%: 투명도
_opacity = Tween(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.0, 0.3, curve: Curves.ease),
),
);
// 30% ~ 60%: 너비
_width = Tween(begin: 50.0, end: 200.0).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.3, 0.6, curve: Curves.ease),
),
);
// 60% ~ 100%: 높이
_height = Tween(begin: 50.0, end: 200.0).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.6, 1.0, curve: Curves.ease),
),
);
// 0% ~ 100%: 패딩
_padding = EdgeInsetsTween(
begin: const EdgeInsets.only(bottom: 16),
end: const EdgeInsets.only(bottom: 75),
).animate(
CurvedAnimation(parent: _controller, curve: Curves.ease),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Opacity(
opacity: _opacity.value,
child: Container(
width: _width.value,
height: _height.value,
padding: _padding.value,
color: Colors.blue,
),
);
},
);
}
}
TweenAnimationBuilder — 암시적 + 명시적 혼합
AnimationController 없이 Tween 애니메이션을 사용할 수 있습니다.
TweenAnimationBuilder<double>(
tween: Tween(begin: 0, end: _targetValue),
duration: const Duration(milliseconds: 500),
curve: Curves.easeOut,
builder: (context, value, child) {
return Transform.scale(
scale: value,
child: child,
);
},
child: const Icon(Icons.star, size: 50, color: Colors.yellow),
)
실전: 로딩 스피너
class CustomSpinner extends StatefulWidget {
const CustomSpinner({super.key});
@override
State<CustomSpinner> createState() => _CustomSpinnerState();
}
class _CustomSpinnerState extends State<CustomSpinner>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 1),
vsync: this,
)..repeat(); // 계속 반복
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Transform.rotate(
angle: _controller.value * 2 * 3.14159,
child: child,
);
},
child: const Icon(Icons.refresh, size: 48),
);
}
}
정리
AnimationController로 애니메이션 시작, 정지, 반복, 역방향을 제어합니다Tween으로 0.0~1.0 값을 원하는 범위로 변환합니다CurvedAnimation으로 Curve(이징)를 적용합니다Interval을 사용해 시퀀스 애니메이션을 구현할 수 있습니다AnimatedBuilder가addListener+setState보다 성능이 좋습니다dispose()에서 반드시 controller를 해제하세요
댓글 로딩 중...