Tween 애니메이션이란 무엇이며 어떻게 사용되나요?
질문
Flutter에서 Tween 애니메이션이란 무엇이며 어떻게 사용되나요?
답변
Tween(Tweening의 줄임말, 'in-betweening'에서 유래)은 Flutter에서 두 값 사이의 보간(interpolation)을 정의하는 클래스입니다. 애니메이션에서 Tween은 시작 값과 끝 값을 정의하고, 애니메이션이 진행됨에 따라 두 값 사이의 중간 값을 계산합니다.
Tween의 기본 개념
Tween
클래스는 다음과 같은 기능을 합니다:
- 시작 값(begin)과 끝 값(end)을 정의합니다.
- 0.0에서 1.0 사이의 진행 값(t)을 입력받아 그에 해당하는 중간 값을 계산합니다.
- 다양한 데이터 유형(숫자, 색상, 위치 등)에 대한 보간을 지원합니다.
기본 Tween 사용법
// 가장 기본적인 형태의 Tween 생성
final tween = Tween<double>(begin: 0.0, end: 100.0);
// 50% 지점의 값 얻기
double value = tween.transform(0.5); // 결과: 50.0
Flutter 애니메이션에서 Tween 사용하기
Tween은 일반적으로 AnimationController
와 함께 사용되어 명시적(explicit) 애니메이션을 구현합니다:
class TweenAnimationExample extends StatefulWidget {
@override
_TweenAnimationExampleState createState() => _TweenAnimationExampleState();
}
class _TweenAnimationExampleState extends State<TweenAnimationExample>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _sizeAnimation;
@override
void initState() {
super.initState();
// 애니메이션 컨트롤러 생성
_controller = AnimationController(
duration: Duration(seconds: 2),
vsync: this,
);
// Tween과 컨트롤러 연결
_sizeAnimation = Tween<double>(
begin: 50.0,
end: 200.0,
).animate(_controller);
// 애니메이션 시작
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Center(
child: AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Container(
width: _sizeAnimation.value,
height: _sizeAnimation.value,
color: Colors.blue,
);
},
),
);
}
}
다양한 Tween 유형
Flutter는 다양한 데이터 유형을 애니메이션화할 수 있는 여러 Tween 하위 클래스를 제공합니다:
1. Tween\
숫자 값을 보간하는 가장 기본적인 Tween입니다:
final sizeTween = Tween<double>(begin: 50.0, end: 200.0);
final opacityTween = Tween<double>(begin: 0.0, end: 1.0);
2. ColorTween
색상 간 보간을 수행합니다:
final colorTween = ColorTween(
begin: Colors.blue,
end: Colors.red,
);
// 사용 예시
Container(
color: colorTween.evaluate(_controller),
width: 100,
height: 100,
)
3. IntTween
정수 값 사이의 보간을 수행합니다:
final countTween = IntTween(begin: 0, end: 100);
// 사용 예시
Text(
'${countTween.evaluate(_controller)}%',
style: TextStyle(fontSize: 24),
)
4. RectTween
사각형(Rect) 객체 간의 보간을 수행합니다:
final rectTween = RectTween(
begin: Rect.fromLTWH(0, 0, 50, 50),
end: Rect.fromLTWH(200, 200, 100, 100),
);
5. BorderTween
테두리(Border) 속성을 보간합니다:
final borderTween = BorderTween(
begin: Border.all(width: 1, color: Colors.red),
end: Border.all(width: 5, color: Colors.blue),
);
6. BorderRadiusTween
모서리 반경(BorderRadius) 간 보간을 수행합니다:
final radiusTween = BorderRadiusTween(
begin: BorderRadius.circular(0),
end: BorderRadius.circular(25),
);
7. EdgeInsetsTween
패딩/마진(EdgeInsets) 값 간 보간을 수행합니다:
final paddingTween = EdgeInsetsTween(
begin: EdgeInsets.all(0),
end: EdgeInsets.all(16),
);
8. TextStyleTween
텍스트 스타일 간 보간을 수행합니다:
final textStyleTween = TextStyleTween(
begin: TextStyle(fontSize: 12, color: Colors.black),
end: TextStyle(fontSize: 24, color: Colors.blue),
);
복합 애니메이션을 위한 Tween 조합
여러 Tween을 조합하여 복잡한 애니메이션을 만들 수 있습니다:
class ComplexAnimationExample extends StatefulWidget {
@override
_ComplexAnimationExampleState createState() => _ComplexAnimationExampleState();
}
class _ComplexAnimationExampleState extends State<ComplexAnimationExample>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _sizeAnimation;
late Animation<Color?> _colorAnimation;
late Animation<double> _opacityAnimation;
late Animation<Offset> _slideAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(seconds: 3),
vsync: this,
);
_sizeAnimation = Tween<double>(
begin: 50.0,
end: 200.0,
).animate(_controller);
_colorAnimation = ColorTween(
begin: Colors.blue,
end: Colors.red,
).animate(_controller);
_opacityAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(_controller);
_slideAnimation = Tween<Offset>(
begin: Offset(-1.0, 0.0),
end: Offset(0.0, 0.0),
).animate(_controller);
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Center(
child: AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return SlideTransition(
position: _slideAnimation,
child: Opacity(
opacity: _opacityAnimation.value,
child: Container(
width: _sizeAnimation.value,
height: _sizeAnimation.value,
color: _colorAnimation.value,
),
),
);
},
),
);
}
}
Tween과 커브(Curve) 결합하기
Tween은 CurvedAnimation
과 결합하여 비선형 애니메이션을 만들 수 있습니다:
// 기본 컨트롤러 설정
final controller = AnimationController(
duration: Duration(seconds: 2),
vsync: this,
);
// 커브 애니메이션 생성
final curvedAnimation = CurvedAnimation(
parent: controller,
curve: Curves.easeInOut,
);
// Tween과 커브 애니메이션 결합
final sizeAnimation = Tween<double>(
begin: 50.0,
end: 200.0,
).animate(curvedAnimation);
순차 애니메이션에서 Tween 사용하기
Interval
커브를 사용하여 Tween이 특정 시간 범위에서만 동작하도록 만들 수 있습니다:
// 크기 애니메이션 (0%-50% 구간)
final sizeAnimation = Tween<double>(
begin: 50.0,
end: 200.0,
).animate(
CurvedAnimation(
parent: controller,
curve: Interval(0.0, 0.5, curve: Curves.easeOut),
),
);
// 색상 애니메이션 (50%-100% 구간)
final colorAnimation = ColorTween(
begin: Colors.blue,
end: Colors.red,
).animate(
CurvedAnimation(
parent: controller,
curve: Interval(0.5, 1.0, curve: Curves.easeIn),
),
);
사용자 정의 Tween 만들기
사용자 정의 클래스를 애니메이션화하려면 Tween
을 상속하여 자신만의 Tween을 만들 수 있습니다:
// 사용자 정의 클래스
class MyCustomObject {
final double width;
final double height;
final Color color;
MyCustomObject({
required this.width,
required this.height,
required this.color,
});
}
// 사용자 정의 Tween
class MyCustomTween extends Tween<MyCustomObject> {
MyCustomTween({
required MyCustomObject begin,
required MyCustomObject end,
}) : super(begin: begin, end: end);
@override
MyCustomObject lerp(double t) {
return MyCustomObject(
width: lerpDouble(begin!.width, end!.width, t)!,
height: lerpDouble(begin!.height, end!.height, t)!,
color: Color.lerp(begin!.color, end!.color, t)!,
);
}
}
// 사용 예시
final customTween = MyCustomTween(
begin: MyCustomObject(width: 50, height: 50, color: Colors.blue),
end: MyCustomObject(width: 200, height: 100, color: Colors.red),
);
final animation = customTween.animate(controller);
TweenSequence 사용하기
하나의 애니메이션에서 여러 Tween을 순차적으로 실행하려면 TweenSequence
를 사용할 수 있습니다:
final Animation<Color?> colorAnimation = TweenSequence<Color?>(
[
// 0% - 33%: 파란색에서 초록색으로
TweenSequenceItem(
tween: ColorTween(begin: Colors.blue, end: Colors.green),
weight: 1,
),
// 33% - 66%: 초록색에서 노란색으로
TweenSequenceItem(
tween: ColorTween(begin: Colors.green, end: Colors.yellow),
weight: 1,
),
// 66% - 100%: 노란색에서 빨간색으로
TweenSequenceItem(
tween: ColorTween(begin: Colors.yellow, end: Colors.red),
weight: 1,
),
],
).animate(controller);
각 TweenSequenceItem
의 weight는 전체 애니메이션에서 해당 Tween이 차지하는 비율을 나타냅니다. 모든 weight의 합은 애니메이션의 전체 시간을 나타냅니다.
실제 예시: 로딩 버튼 애니메이션
Tween을 활용한 실제 예시로, 버튼이 누르면 로딩 상태로 애니메이션되는 예제를 살펴보겠습니다:
class AnimatedLoadingButton extends StatefulWidget {
final VoidCallback onPressed;
AnimatedLoadingButton({required this.onPressed});
@override
_AnimatedLoadingButtonState createState() => _AnimatedLoadingButtonState();
}
class _AnimatedLoadingButtonState extends State<AnimatedLoadingButton>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _widthAnimation;
late Animation<double> _borderRadiusAnimation;
late Animation<Color?> _colorAnimation;
bool _isLoading = false;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(milliseconds: 300),
vsync: this,
);
_widthAnimation = Tween<double>(
begin: 200.0,
end: 50.0,
).animate(_controller);
_borderRadiusAnimation = Tween<double>(
begin: 4.0,
end: 25.0,
).animate(_controller);
_colorAnimation = ColorTween(
begin: Colors.blue,
end: Colors.green,
).animate(_controller);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _handleTap() async {
if (_isLoading) return;
setState(() {
_isLoading = true;
});
await _controller.forward();
widget.onPressed();
// 작업 완료 후 원래 상태로 복귀
Future.delayed(Duration(seconds: 2), () {
_controller.reverse().then((_) {
setState(() {
_isLoading = false;
});
});
});
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return GestureDetector(
onTap: _handleTap,
child: Container(
width: _widthAnimation.value,
height: 50,
decoration: BoxDecoration(
color: _colorAnimation.value,
borderRadius: BorderRadius.circular(_borderRadiusAnimation.value),
),
child: Center(
child: _isLoading
? SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
color: Colors.white,
strokeWidth: 2,
),
)
: Text(
'버튼',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
),
);
},
);
}
}
결론
Tween은 Flutter 애니메이션 시스템의 핵심 구성 요소로, 시작 값과 끝 값 사이의 보간을 정의합니다. 다양한 데이터 유형에 대한 보간을 지원하고, 커브와 결합하여 정교한 애니메이션을 만들 수 있습니다. 또한 TweenSequence
를 통해 복잡한 다단계 애니메이션을 구현할 수 있으며, 필요에 따라 사용자 정의 Tween을 만들어 특별한 애니메이션 효과를 구현할 수 있습니다.
Tween을 효과적으로 사용하면 부드럽고 매력적인 UI 트랜지션을 만들어 사용자 경험을 크게 향상시킬 수 있습니다.