(Flutter-기초 강의) 16. Animation 사용하기

소개

Flutter에서 애니메이션을 구현하는 것은 사용자 인터페이스에 생동감을 더해주며, 더 나은 사용자 경험을 제공합니다.

이를 위해 알아야 할 몇 가지 중요한 개념과 예제 코드, 그리고 추가 참고 자료를 제공하겠습니다.

animation-fade-in

Animation 이란?

Flutter에서 애니메이션은 주로 Animation 클래스를 사용하여 구현됩니다.

Animation은 상태와 함께 값(Generic type)으로 구성됩니다.

이 클래스는 시간에 따라 값이 어떻게 변화하는지를 정의하며, 다양한 종류의 값과 애니메이션 효과를 처리할 수 있습니다.

처음 위치부터 마지막 위치까지 Frame 단위로 표현하며 Flutter에서는 복잡한 계산식 없이 애니메이션을 구현할 수 있습니다.

Flutter Animation 장점

  1. 사용자 경험 개선:
    • 애니메이션은 앱의 반응성을 높이고 사용자 경험을 향상시킵니다.
  2. 다양한 사용 가능:
    • 다양한 유형의 애니메이션(페이드, 슬라이드, 회전 등)을 쉽게 구현할 수 있습니다.
  3. 높은 맞춤화 가능성:
    • 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를 배치시키고

curveduration 속성값을 넣어 자연스럽게 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의 opacityduration을 이용하여 Animation을 적용합니다.

              AnimatedOpacity(
                opacity: _opacity,
                duration: const Duration(seconds: 2),
                child: SizedBox(

결과

Animation Widget Example

Hero Widget

개요

Hero 위젯은 Flutter에서 두 화면 간의 전환 시 시각적인 연결을 제공하는 애니메이션 위젯입니다.

이를 통해 사용자는 앱 내에서 원활하고 직관적인 경험을 할 수 있습니다.

Hero 애니메이션은 주로 이미지나 위젯이 한 화면에서 다른 화면으로 이동할 때 사용됩니다.

주요 특징

  1. 연속성:
    • 두 화면 사이에서 공유되는 요소(예: 이미지)가 연속적으로 보이며, 한 화면에서 다른 화면으로 자연스럽게 이동하는 것처럼 보입니다.
  2. 태그 사용:
    • Hero 위젯은 tag 속성을 사용하여 두 화면 간의 연결을 정의합니다. 동
    • 일한 tag를 가진 Hero 위젯이 두 화면에 있으면, Flutter는 자동으로 두 위젯을 연결하여 애니메이션을 생성합니다.
  3. 커스터마이징:
    • 애니메이션의 지속 시간, 곡선 등을 사용자 정의할 수 있습니다.

AnimationController

개요

애니메이션의 진행 상태를 관리하며, 애니메이션 값을 시간에 따라 변화시키는 역할을 합니다.

AnimationController를 사용하면 애니메이션의 시작, 정지, 반대 방향 재생, 값 범위 설정 등을 할 수 있습니다.

https://api.flutter.dev/flutter/animation/AnimationController-class.html

주요 기능

  1. 시작 및 정지:
    • forward(), reverse(), stop() 메서드를 사용하여 애니메이션을 시작, 뒤로 재생, 중지할 수 있습니다.
  2. 상태 및 값 제어:
    • 현재 애니메이션 값(value), 상태(status), 진행 방향 등을 관리합니다.
  3. 지속 시간 설정:
    • 애니메이션의 전체 길이를 Duration 객체로 설정합니다.
  4. 리스너 및 상태 콜백:
    • 애니메이션의 변화에 따라 리스너를 추가하거나 상태 변경에 반응하는 콜백을 등록할 수 있습니다.
  5. 커스텀 애니메이션:
    • 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'),
              ),
            ],
          ),
        ),
      ),

결과

참고 링크

  1. 내 Github 저장소-devitworld-flutter-basic (_5_animation)
  2. Flutter 공식 문서의 애니메이션 튜토리얼
  3. Flutter 공식 문서의 애니메이션 소개
  4. LogRocket 블로그의 고급 Flutter 애니메이션 가이드
  5. Flutter 공식 문서의 애니메이션 섹션

Leave a Comment