애니메이션 컨트롤러에 대해 설명해주세요.
질문
Flutter에서 애니메이션 컨트롤러에 대해 설명해주세요.
답변
Flutter에서 AnimationController
는 애니메이션의 진행을 제어하는 핵심 클래스입니다. 모든 명시적(explicit) 애니메이션은 AnimationController
를 사용하여 제어됩니다. 이는 애니메이션의 시작, 정지, 반복, 방향 전환 등을 다룹니다.
AnimationController의 기본 개념
AnimationController
는 0.0에서 1.0 사이의 값을 생성하는 특수한 Animation<double>
객체입니다. 이 값은 시간에 따라 선형적으로 증가하며, 애니메이션의 진행 상태를 나타냅니다:
- 0.0: 애니메이션의 시작
- 1.0: 애니메이션의 끝
이 값은 다른 애니메이션 객체(Tween과 결합된)의 입력으로 사용되어 실제 애니메이션 효과를 만듭니다.
AnimationController 생성하기
class MyAnimationWidget extends StatefulWidget {
@override
_MyAnimationWidgetState createState() => _MyAnimationWidgetState();
}
class _MyAnimationWidgetState extends State<MyAnimationWidget>
with SingleTickerProviderStateMixin { // 필수 믹스인
late AnimationController _controller;
@override
void initState() {
super.initState();
// 컨트롤러 초기화
_controller = AnimationController(
duration: Duration(seconds: 2), // 애니메이션 지속 시간
vsync: this, // 틱 제공자
);
}
@override
void dispose() {
_controller.dispose(); // 리소스 해제
super.dispose();
}
// 위젯 빌드...
}
TickerProvider의 역할
vsync
매개변수는 TickerProvider
를 필요로 합니다. 이는 애니메이션이 화면에 표시될 때만 틱(tick)을 생성하게 하여, 배터리와 CPU 리소스를 절약합니다. 일반적으로 애니메이션을 포함하는 State 클래스에 다음 믹스인 중 하나를 추가합니다:
SingleTickerProviderStateMixin
: 단일 애니메이션 컨트롤러를 사용할 때TickerProviderStateMixin
: 여러 애니메이션 컨트롤러를 사용할 때
AnimationController의 주요 속성
- value: 현재 애니메이션 값(0.0~1.0)
- duration: 애니메이션의 지속 시간
- status: 현재 애니메이션 상태(forward, reverse, completed, dismissed)
- lowerBound: 애니메이션의 최소값(기본값 0.0)
- upperBound: 애니메이션의 최대값(기본값 1.0)
- reverseDuration: 역방향 애니메이션의 지속 시간(별도 설정 가능)
사용자 정의 범위
기본적으로 AnimationController
는 0.0에서 1.0 사이의 값을 생성하지만, 다른 범위를 지정할 수도 있습니다:
AnimationController(
duration: Duration(seconds: 2),
vsync: this,
lowerBound: -1.0, // 최소값
upperBound: 2.0, // 최대값
);
AnimationController의 주요 메서드
1. 애니메이션 제어 메서드
// 애니메이션 시작 (0.0 -> 1.0)
_controller.forward();
// 애니메이션 역방향 실행 (1.0 -> 0.0)
_controller.reverse();
// 애니메이션 정지
_controller.stop();
// 애니메이션 초기화 (값을 lowerBound로 설정)
_controller.reset();
// 애니메이션 반복 (무한 반복)
_controller.repeat();
// 애니메이션 반복 (역방향 포함)
_controller.repeat(reverse: true);
// 특정 값으로 애니메이션 진행
_controller.animateTo(0.5);
// 특정 값으로 역방향 애니메이션 진행
_controller.animateBack(0.2);
2. 상태 확인 메서드
// 애니메이션이 진행 중인지 확인
if (_controller.isAnimating) {
// 애니메이션 진행 중 로직
}
// 애니메이션 상태 확인
if (_controller.status == AnimationStatus.completed) {
// 애니메이션 완료 시 로직
}
// 현재 값 확인
double currentValue = _controller.value;
애니메이션 상태 리스닝
AnimationController의 상태 변화를 감지하고 대응하기 위해 리스너를 추가할 수 있습니다:
1. 값 변경 리스너
_controller.addListener(() {
// 컨트롤러 값이 변경될 때마다 호출됨
print('현재 애니메이션 값: ${_controller.value}');
setState(() {
// 필요한 경우 UI 업데이트
});
});
2. 상태 변경 리스너
_controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
// 애니메이션 완료 시
print('애니메이션 완료');
} else if (status == AnimationStatus.dismissed) {
// 애니메이션이 초기 상태로 돌아왔을 때
print('애니메이션 초기화');
} else if (status == AnimationStatus.forward) {
// 정방향 애니메이션 중
print('정방향 애니메이션 중');
} else if (status == AnimationStatus.reverse) {
// 역방향 애니메이션 중
print('역방향 애니메이션 중');
}
});
AnimationController 사용 사례
1. 기본 애니메이션 구현
class BasicAnimation extends StatefulWidget {
@override
_BasicAnimationState createState() => _BasicAnimationState();
}
class _BasicAnimationState extends State<BasicAnimation>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(seconds: 2),
vsync: this,
);
// Tween과 결합하여 실제 애니메이션 생성
_animation = Tween<double>(begin: 0, end: 200).animate(_controller);
// 애니메이션 시작
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Container(
width: _animation.value,
height: _animation.value,
color: Colors.blue,
);
},
);
}
}
2. 무한 반복 애니메이션
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(seconds: 1),
vsync: this,
);
_animation = Tween<double>(begin: 0, end: 2 * pi).animate(_controller);
// 무한 반복
_controller.repeat();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Transform.rotate(
angle: _animation.value,
child: FlutterLogo(size: 100),
);
},
);
}
3. 애니메이션 체이닝 (연속 애니메이션)
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(seconds: 2),
vsync: this,
);
_animation = Tween<double>(begin: 0, end: 200).animate(_controller);
// 애니메이션 완료 시 역방향 실행
_controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controller.reverse();
} else if (status == AnimationStatus.dismissed) {
_controller.forward();
}
});
_controller.forward();
}
4. 애니메이션 시작/정지 제어
FloatingActionButton(
onPressed: () {
if (_controller.isAnimating) {
_controller.stop();
} else {
if (_controller.status == AnimationStatus.forward ||
_controller.status == AnimationStatus.completed) {
_controller.reverse();
} else {
_controller.forward();
}
}
},
child: Icon(
_controller.isAnimating ? Icons.pause : Icons.play_arrow,
),
)
5. 복수 애니메이션 제어하기
class MultipleAnimations extends StatefulWidget {
@override
_MultipleAnimationsState createState() => _MultipleAnimationsState();
}
class _MultipleAnimationsState extends State<MultipleAnimations>
with TickerProviderStateMixin { // 여러 컨트롤러용 믹스인
late AnimationController _sizeController;
late AnimationController _rotationController;
late Animation<double> _sizeAnimation;
late Animation<double> _rotationAnimation;
@override
void initState() {
super.initState();
_sizeController = AnimationController(
duration: Duration(seconds: 2),
vsync: this,
);
_rotationController = AnimationController(
duration: Duration(milliseconds: 500),
vsync: this,
);
_sizeAnimation = Tween<double>(begin: 50, end: 200).animate(_sizeController);
_rotationAnimation = Tween<double>(begin: 0, end: 2 * pi).animate(_rotationController);
_sizeController.forward();
_rotationController.repeat();
}
@override
void dispose() {
_sizeController.dispose();
_rotationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: Listenable.merge([_sizeController, _rotationController]),
builder: (context, child) {
return Transform.rotate(
angle: _rotationAnimation.value,
child: Container(
width: _sizeAnimation.value,
height: _sizeAnimation.value,
color: Colors.blue,
),
);
},
);
}
}
애니메이션 커브 적용하기
애니메이션의 진행 방식을 제어하기 위해 CurvedAnimation
을 사용할 수 있습니다:
_controller = AnimationController(
duration: Duration(seconds: 2),
vsync: this,
);
// 커브 적용
final curvedAnimation = CurvedAnimation(
parent: _controller,
curve: Curves.elasticOut, // 바운스 효과
reverseCurve: Curves.easeIn, // 역방향에 대한 별도 커브
);
// Tween과 결합
_animation = Tween<double>(begin: 0, end: 200).animate(curvedAnimation);
주요 커브 종류:
Curves.linear
: 일정한 속도Curves.easeIn
: 느리게 시작해서 빨라짐Curves.easeOut
: 빠르게 시작해서 느려짐Curves.easeInOut
: 느리게 시작하고 빨라졌다가 다시 느려짐Curves.elasticIn
,Curves.elasticOut
: 탄성 효과Curves.bounceIn
,Curves.bounceOut
: 통통 튀는 효과Curves.decelerate
: 감속 효과
애니메이션 구간 제어하기
Interval
클래스를 사용하면 애니메이션의 특정 구간에서만 실행되도록 할 수 있습니다:
// 애니메이션의 20%에서 80% 구간에서만 실행
final intervalAnimation = CurvedAnimation(
parent: _controller,
curve: Interval(0.2, 0.8, curve: Curves.easeInOut),
);
_animation = Tween<double>(begin: 0, end: 200).animate(intervalAnimation);
커스텀 애니메이션 컨트롤러
특정 요구사항에 맞게 애니메이션 컨트롤러를 확장할 수도 있습니다:
class CustomAnimationController extends AnimationController {
CustomAnimationController({
required Duration duration,
required TickerProvider vsync,
}) : super(duration: duration, vsync: vsync);
// 사용자 정의 메서드
void playWithDelay({required Duration delay}) {
Future.delayed(delay, () {
this.forward();
});
}
// 특정 구간만 실행
void animateSegment(double start, double end) {
assert(start >= 0.0 && start <= 1.0);
assert(end >= 0.0 && end <= 1.0);
assert(start <= end);
this.value = start;
this.animateTo(end);
}
}
고급 애니메이션 제어 기술
1. 시뮬레이션 기반 애니메이션
물리적 동작을 시뮬레이션하기 위해 AnimationController.animateWith()
를 사용할 수 있습니다:
// 스프링 효과
final SpringSimulation simulation = SpringSimulation(
SpringDescription(
mass: 1.0,
stiffness: 500.0,
damping: 10.0,
),
_controller.value, // 시작 위치
1.0, // 목표 위치
0.0, // 시작 속도
);
_controller.animateWith(simulation);
// 마찰 효과
final FrictionSimulation frictionSimulation = FrictionSimulation(
0.5, // 마찰 계수
_controller.value, // 시작 위치
2.0, // 시작 속도
);
_controller.animateWith(frictionSimulation);
2. 사용자 제스처로 애니메이션 제어
사용자의 드래그 제스처에 따라 애니메이션 값을 직접 제어할 수 있습니다:
GestureDetector(
onHorizontalDragUpdate: (details) {
// 드래그 거리에 따라 애니메이션 값 설정
_controller.value += details.primaryDelta! / screenWidth;
},
onHorizontalDragEnd: (details) {
// 속도에 따라 애니메이션 완료 또는 초기화
if (details.primaryVelocity! > 0) {
_controller.forward();
} else {
_controller.reverse();
}
},
child: AnimatedBuilder(
animation: _controller,
builder: (context, child) {
// 애니메이션 위젯 빌드
},
),
)
성능 최적화 팁
애니메이션 컨트롤러 재사용: 가능한 경우 여러 애니메이션에 대해 하나의 컨트롤러를 재사용합니다.
리소스 해제:
dispose()
메서드에서 반드시 컨트롤러를 해제하여 메모리 누수를 방지합니다.상태 리스너 관리: 필요 없는 리스너는 제거합니다.
// 리스너 참조 저장 void _handleAnimationChanged() { setState(() {}); } _controller.addListener(_handleAnimationChanged); // 나중에 리스너 제거 _controller.removeListener(_handleAnimationChanged);
AnimatedBuilder 최적화: 자주 변경되지 않는 위젯은 builder 외부에 배치합니다.
이중 애니메이션 방지: 불필요하게 여러 개의 애니메이션이 동시에 실행되지 않도록 합니다.
결론
AnimationController
는 Flutter에서 명시적 애니메이션을 제어하는 핵심 클래스입니다. 애니메이션의 시작, 정지, 반복, 방향 전환 및 진행 상태 감지 등 다양한 기능을 제공합니다. Tween
, CurvedAnimation
, Interval
등과 결합하여 복잡하고 세련된 애니메이션 효과를 만들 수 있습니다.
적절한 애니메이션 컨트롤러 사용은 사용자 경험을 향상시키는 부드럽고 반응성 있는 인터페이스를 만드는 데 필수적입니다. 또한 리소스 관리와 성능 최적화에 주의하여 효율적인 애니메이션을 구현하는 것이 중요합니다.