소개
Flutter에서 애니메이션을 구현하는 것은 사용자 인터페이스에 생동감을 더해주며, 더 나은 사용자 경험을 제공합니다.
이를 위해 알아야 할 몇 가지 중요한 개념과 예제 코드, 그리고 추가 참고 자료를 제공하겠습니다.

Animation 이란?
Flutter에서 애니메이션은 주로 Animation
클래스를 사용하여 구현됩니다.
Animation
은 상태와 함께 값(Generic type)으로 구성됩니다.
이 클래스는 시간에 따라 값이 어떻게 변화하는지를 정의하며, 다양한 종류의 값과 애니메이션 효과를 처리할 수 있습니다.
처음 위치부터 마지막 위치까지 Frame 단위로 표현하며 Flutter에서는 복잡한 계산식 없이 애니메이션을 구현할 수 있습니다.
Flutter Animation 장점
- 사용자 경험 개선:
- 애니메이션은 앱의 반응성을 높이고 사용자 경험을 향상시킵니다.
- 다양한 사용 가능:
- 다양한 유형의 애니메이션(페이드, 슬라이드, 회전 등)을 쉽게 구현할 수 있습니다.
- 높은 맞춤화 가능성:
- Flutter의 유연성 덕분에 개발자는 맞춤 애니메이션을 쉽게 만들 수 있습니다.
Animation Widgets
AnimatedContainer:
AnimatedContainer
는 시간이 지남에 따라 점진적으로 그 값을 변경하는 Container
의 애니메이션 버전입니다.
이 위젯은 제공된 곡선(curve
)과 지속 시간(duration
)을 사용하여 속성의 구값(old values)과 신값(new values) 사이를 자동으로 애니메이트합니다.
AnimatedAlign:
자식 위젯의 위치를 애니메이션으로 변경합니다.
AnimatedBuilder:
애니메이션을 더 세밀하게 제어할 수 있는 범용 위젯입니다.
AnimatedCrossFade:
두 자식 간의 cross-fade 애니메이션을 제공합니다.
AnimatedDefaultTextStyle:
텍스트 스타일을 애니메이션으로 변경합니다.
AnimatedList:
리스트에 아이템이 추가되거나 제거될 때 애니메이션을 제공합니다.
AnimatedOpacity:
위젯의 투명도(opacity)를 애니메이션으로 변경합니다.
AnimatedPadding:
패딩을 애니메이션으로 변경합니다.
AnimatedPhysicalModel:
그림자, 모양 등을 애니메이션으로 변경합니다.
AnimatedPositioned:
Stack
내에서 위젯의 위치를 애니메이션으로 변경합니다.
AnimatedSize:
위젯의 크기를 애니메이션으로 변경합니다.
AnimatedSwitcher:
자식 위젯 간의 전환을 애니메이션으로 구현합니다.
AnimatedTheme:
테마 데이터를 애니메이션으로 변경합니다.
Animation Widgets 예제
Animation 은 예제에 초점을 맞추어 진행하도록 하겠습니다.
이번 예제는 5개의 사람(Person
) 데이터를 추가하고, 체질량 지수(bmi)를 표시하는 예제를 통해
값이 변경될 때 애니메이션이 적용되도록 하는 예제를 구현해보겠습니다.
1. 모델 생성
이름, 키, 몸무게를 입력받아 bmi를 계산하는 모델을 정의합니다.
/lib/model/person.dart
class Person { String name; double height; double weight; double? bmi; People(this.name, this.height, this.weight) { bmi = weight / ((height / 100) * (height / 100)); } }
2. 메인에 DataSet (모델 List) 생성
5개의 Person을 생성하여 데이터(people
)로 사용합니다.
/lib/main.dart
class _AnimationAppState extends State<AnimationApp> { List<Person> people = List.empty(growable: true); int current = 0; @override void initState() { people.add(Person("John", 170, 60)); people.add(Person("Mary", 149, 88)); people.add(Person("Jane", 160, 50)); people.add(Person("James", 180, 20)); people.add(Person("Didi", 188, 30)); people.add(Person("Jenny", 150, 10)); super.initState(); }
3. 색상이 몸무게에 따라 다르게 적용되도록 설정합니다.
몸무게에 따라 _weightColor
값이 다르게 설정되도록 함수를 정의합니다.
/lib/sub/animation_app.dart
void _changeWeightColor(double weightValue) { if (weightValue < 40) { _weightColor = Colors.blueAccent; } else if (weightValue < 60) { _weightColor = Colors.indigo; } else if (weightValue < 80) { _weightColor = Colors.indigo; } else { _weightColor = Colors.red; } }
4. (AnimatedContainer를 이용한) Animation 적용
키, 몸무게, bmi를 높이로 나타내는 AnimatedContainer
를 배치시키고
curve
와 duration
속성값을 넣어 자연스럽게 Animation이 적용되어 색상과 높이가 변경되도록 Animation을 적용합니다.
/lib/sub/animation_app.dart
child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, crossAxisAlignment: CrossAxisAlignment.end, children: <Widget>[ SizedBox( width: 100, child: Text('name: ${people[current].name}'), ), AnimatedContainer( duration: const Duration(seconds: 2), curve: Curves.bounceIn, color: Colors.amber, width: 50, height: people[current].height, child: Text( 'height: ${people[current].height}', textAlign: TextAlign.center, ), ), AnimatedContainer( duration: const Duration(seconds: 2), curve: Curves.easeInCubic, color: _weightColor, width: 50, height: people[current].weight, child: Text( 'weight: ${people[current].weight}', textAlign: TextAlign.center, ), ), AnimatedContainer( duration: const Duration(seconds: 2), curve: Curves.linear, color: Colors.pinkAccent, width: 50, height: people[current].bmi, child: Text( 'bmi: ${people[current].bmi.toString().substring(0, 2)}', textAlign: TextAlign.center, ), ), ],
5. (AnimatedOpacity를 이용한) 불투명도 Animation 적용
/lib/sub/animation_app.dart
opacity에 대한 속성값을 정의합니다.
double _opacity = 1; // 1: visible, 0: invisible
버튼이 눌렸을 때, _opacity
값을 변경합니다.
ElevatedButton( onPressed: () { setState(() { _opacity == 1 ? _opacity = 0 : _opacity = 1; }); }, child: _opacity == 1 ? const Text("Disappear") : const Text("Appear"), ),
AnimatedOpacity
Widget의 opacity
와 duration
을 이용하여 Animation을 적용합니다.
AnimatedOpacity( opacity: _opacity, duration: const Duration(seconds: 2), child: SizedBox(
결과

Hero Widget
개요
Hero
위젯은 Flutter에서 두 화면 간의 전환 시 시각적인 연결을 제공하는 애니메이션 위젯입니다.
이를 통해 사용자는 앱 내에서 원활하고 직관적인 경험을 할 수 있습니다.
Hero
애니메이션은 주로 이미지나 위젯이 한 화면에서 다른 화면으로 이동할 때 사용됩니다.
주요 특징
- 연속성:
- 두 화면 사이에서 공유되는 요소(예: 이미지)가 연속적으로 보이며, 한 화면에서 다른 화면으로 자연스럽게 이동하는 것처럼 보입니다.
- 태그 사용:
Hero
위젯은tag
속성을 사용하여 두 화면 간의 연결을 정의합니다. 동- 일한
tag
를 가진Hero
위젯이 두 화면에 있으면, Flutter는 자동으로 두 위젯을 연결하여 애니메이션을 생성합니다.
- 커스터마이징:
- 애니메이션의 지속 시간, 곡선 등을 사용자 정의할 수 있습니다.
AnimationController
개요
애니메이션의 진행 상태를 관리하며, 애니메이션 값을 시간에 따라 변화시키는 역할을 합니다.
AnimationController
를 사용하면 애니메이션의 시작, 정지, 반대 방향 재생, 값 범위 설정 등을 할 수 있습니다.
https://api.flutter.dev/flutter/animation/AnimationController-class.html
주요 기능
- 시작 및 정지:
forward()
,reverse()
,stop()
메서드를 사용하여 애니메이션을 시작, 뒤로 재생, 중지할 수 있습니다.
- 상태 및 값 제어:
- 현재 애니메이션 값(
value
), 상태(status
), 진행 방향 등을 관리합니다.
- 현재 애니메이션 값(
- 지속 시간 설정:
- 애니메이션의 전체 길이를
Duration
객체로 설정합니다.
- 애니메이션의 전체 길이를
- 리스너 및 상태 콜백:
- 애니메이션의 변화에 따라 리스너를 추가하거나 상태 변경에 반응하는 콜백을 등록할 수 있습니다.
- 커스텀 애니메이션:
Tween
과 결합하여 다양한 값을 애니메이션할 수 있습니다.
Tween
Tween
은 Flutter에서 애니메이션 값의 범위를 정의하는 데 사용되는 클래스입니다.
Tween
은 “between”의 축약어로, 시작 값과 종료 값 사이의 중간 값을 생성하는 역할을 합니다.
이를 통해 개발자는 애니메이션 동안 어떤 값에서 어떤 값으로 변화할지를 정의할 수 있습니다.
Hero & AnimationController 예제
이 예제는 Flutter에서 AnimationController
와 다양한 Animation
객체들을 활용하여 복합적인 애니메이션을 구현하는 방법을 보여줍니다.
여기서는 회전, 스케일 조정, 그리고 이동 애니메이션을 결합하여 사용합니다.
1. Animation Controller 초기화
Animation과 Animation Controller를 다음과 같이 초기화 합니다.
_rotateAnimation
는 회전(rotation) 을
_scaleAnimation
는 크기변경(scale)을
_transAnimation
은 이동(trans) 에 대한 애니메이션을 관리합니다.
_animationController
는 위 Animation들을 5초 동안 지속하게 합니다.
@override void initState() { super.initState(); _animationController = AnimationController(duration: const Duration(seconds: 5), vsync: this); _rotateAnimation = Tween<double>(begin: 0, end: pi * 10).animate(_animationController!); _scaleAnimation = Tween<double>(begin: 1, end: 0).animate(_animationController!); _transAnimation = Tween<Offset>(begin: const Offset(0, 0), end: const Offset(200, 200)) .animate(_animationController!); }
2. Animation 작동
버튼을 누르면 위에서 말한 Animation이 Icons.cake
에 적용됩니다.
body: Container( child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ AnimatedBuilder( animation: _rotateAnimation!, builder: (context, widget) { return Transform.translate( offset: _transAnimation!.value, child: Transform.rotate( angle: _rotateAnimation!.value, child: Transform.scale( scale: _scaleAnimation!.value, child: widget, ), ), ); }, child: const Hero( tag: 'cake', child: Icon( Icons.cake, size: 300, ), ), ), ElevatedButton( onPressed: () { _animationController!.forward(); }, child: const Text('Rotate'), ), ], ), ), ),
결과
