본문 바로가기
Flutter/Flutter 필수개념

[Flutter] Animation 이해하기

by 붕어사랑 티스토리 2021. 11. 26.
반응형

0. Animation의 대표적인 컨셉

 

한마디로 요약하면

 

위젯의 특정 속성 값을 계속 변화시키며 rebuild 하는것!

 

애니메이션은 value change를 가진다. 즉 어떤 값이 start에서 end로 변화한다.

이 값의 변화를 위젯의 특정속성에다가 부여하면 플러터는 계속해서 이를 rebuild 하게 된다.

그러므로 움직이는것 처럼 보이게 되는 것이다.

 

이러한 값 변화는 impicit에서는 Tween을 이용하고 explicit에선 AnimationController를 이용하게 된다.

1. implicit, explicit

애니메이션의 종류에는 크게 두가지가 있다. implicit, explicit

implicit animation은 속성값을 변화시키면 자기가 알아서 애니메이션을 만들어준다.

 

만약 애니메이션을 클릭해서 stop이나 repeat를 하고 싶다면 explicit을 사용하면 된다.

explicit을 사용할 때 animationController가 필요하다

 

 

 

2. Built In Animation

flutter에서 기본적으로 주어지는 Animation 위젯들이 있다.

Animated땡떙땡 이렇게 시작하는것은 implicit built in animation이다.

AnimatedContatiner처럼.

 

땡땡땡Transition은 explicit Built in animation이다.

FadeTransition을 사용하면 화면에 위젯을 투명하게 만들어주는 애니메이션을 넣을 수 있다.

 

 

 

3. Custom Animation

built-in 애니메이션에서 내가 원하는게 없을 때 사용한다.

 

Implicit Custom Animation을 사용하려면 TweenAnimationBuilder를  사용해야 한다.

TweenAnimatinoBuilder를 사용할 때 기억할 점은, 이 위젯은 항상 endValue를 향해 위젯을 변경한다

가령 위젯 로드와 동시에 start와 end값이 다르면 바로 애니메이션이 시작되버림.

 

Explicit Custon Animation을 만들려면 AnimatedBuilder을 사용한다.

 

builder 함수임으로, 빌더안에 애니메이션을 줄 위젯트리를 넣고, animation 값을 읽어오면서 애니메이션을 만드는 방식이다.

AnimiatedBuilder는 AnimatedWidget이라는 위젯으로 코드 리팩토링을 할 수 있다.

 

 

 

 

4. 애니메이션과 관련된 클래스들

 

Animation

이 클래스는 abstract class이다. 현재 애니메이션의 값과 상태를 알고 있는 클래스이다. 가장 흔한 케이스는 Animation<double> 이다.

 

 

AnimationController

controller = AnimationController(duration: const Duration(seconds: 2), vsync: this);

0에서 1사이를 linear하게 interpolate하는 Animation 구현체이다. animation클래스에 추가적으로 애니메이션을 컨트롤 할 수 있는 . forward stop repeat같은 메소드들이 주어진다.

 

한가지 중요한 개념으로 vsync라는 파라미터를 넘겨줘야 하는데, 이는 수직동기화로, 불필요한 애니메이션 연산을 막아준다.

(수직동기화란, 애니메이션 프레임 생성 타이밍을 화면에 출력되는 프레임 타이밍에 맞추는 기술)

 

 

CurvedAnimation

animation = CurvedAnimation(parent: controller, curve: Curves.easeIn);

linear하지 않은 애니메이션을 만들 때 사용된다.

 

CurevedAnimation과 AnimationController는 사실 Animation<double> 타입이다. 즉, Animation<double> 위치에 쓰일 수 있다.

 

 

Tween

범위를 지정하는 클래스이다. 0과 1 보다  더 넓은 범위를 지정할 때 사용한다.

tween = Tween<double>(begin: -200, end: 0);
colorTween = ColorTween(begin: Colors.transparent, end: Colors.black54);

tween은 stateless이다. 즉, 상태값을 저장하지 않고 오직 범위만을 지정하는 역할을 한다.

 

 

Tween.Animate()

AnimationController의 범위를 바꾸고 싶을 때 사용

Tween.Animate()에다가 컨트롤러를 넘겨주면 Animation이 나온다.

 

Animatino과 AnimationController 이렇게 이름을 구분짓는 이유는, 범위의 변경을 나타내기 위한거다! 즉 0~1 사이라는 범위가 커스텀되었는지를 나타낸다 (아래쪽에 좀더 자세한설명이 있음)

 

이 Animation이 범위가 바뀐 AnimationController라고 이해하면 됨

AnimationController controller = AnimationController(
    duration: const Duration(milliseconds: 500), vsync: this);
Animation<int> alpha = IntTween(begin: 0, end: 255).animate(controller);

아래처럼 curve와 조합이 가능하다.

AnimationController controller = AnimationController(
    duration: const Duration(milliseconds: 500), vsync: this);
final Animation<double> curve =
    CurvedAnimation(parent: controller, curve: Curves.easeOut);
Animation<int> alpha = IntTween(begin: 0, end: 255).animate(curve);

 

 

 

Q. Explicit Animation에는 AnimationController를 사용하라고 했는데 위에 예제들을 보면 Curved나 Tween.animate를 쓰면 animation객체들이 리턴되고 있어. 도대체 저것들의 정체는 뭐야?

처음에 배운 내용을 돌아보자. Animation은 현재의 값을 저장하는 abstract라고 했다. Curved와 Tween을 이용해 Animation을 생성할 때 controller를 넘겨주고 있다. 즉 controller는 그대로 제 기능을 하고, explicit widget에 내가 커브나 범위를 변경해서 넣어주고 싶으면, curved나 Tween.animate로 나온 애니메이션을 explicit widget에 넣어주면, 범위나 커브도 내맘대로 커스텀하고 조작도 할 수 있다.

 

 

Q. 그래 Animation 객체가 범위가 변경된 애들인건 알겠어. 근데 왜 AnimationController와 Animation 이렇게 이름을 구분하는거야?

처음에 나도 이해가 안됐는데 개발을 하다 보니 알았다. 애니메이션을 만들다 보면 0에서 1이라는 범위가 아주 기본적이고 자주사용된다

즉 AnimationController와 Animation 이름을 이렇게 두가지로 구분하는 이유는, AnimationController의 범위가 변경이 되었나 안되었나를 나타내주는 것이다.

 

 

5. 리스너

Animation 클래스는 listener와 statusListener를 달아 줄 수 있다. 각각 addListener와 addStatusListener를 통해 이루어진다.

리스너를 달아 줄 때 가장 흔하게 하는 작업은 setState()를 호출하는 것 이다.

 

아래는 공식홈페이지에 나온 예제이다. Controller를 만들고, Tween을 이용해 범위가 0에서 300사이인 Animation을 만들었다.

이후 controller를 이용해 forward를 하여 애니메이션을 실행시킨다. 여기서 리스너에서는 setState를 호출하여 위젯을 리빌드하게 만든다.

 

그러면 Container에서는 아까 Controller와 Tween을 이용해 만든 0~300사이의 애니메이션에서 값을 읽어오면서 애니메이션 효과를 준다.

State<LogoApp> createState() => _LogoAppState();            
                 }            
    -            class _LogoAppState extends State<LogoApp> {            
    +            class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {            
    +              late Animation<double> animation;            
    +              late AnimationController controller;            
    +                        
    +              @override            
    +              void initState() {            
    +                super.initState();            
    +                controller =            
    +                    AnimationController(duration: const Duration(seconds: 2), vsync: this);            
    +                animation = Tween<double>(begin: 0, end: 300).animate(controller)            
    +                  ..addListener(() {            
    +                    setState(() {            
    +                      // The state that has changed here is the animation object's value.            
    +                    });            
    +                  });            
    +                controller.forward();            
    +              }            
    +                        
                   @override            
                   Widget build(BuildContext context) {            
                     return Center(            
                       child: Container(            
                         margin: const EdgeInsets.symmetric(vertical: 10),            
    -                    height: 300,            
    -                    width: 300,            
    +                    height: animation.value,            
    +                    width: animation.value,            
                         child: const FlutterLogo(),            
                       ),            
                     );            
                   }            
    +                        
    +              @override            
    +              void dispose() {            
    +                controller.dispose();            
    +                super.dispose();            
    +              }            
                 }

 

 

 

6. AnimatedWidget

굳이 state를 보관할 필요가 없다. 즉 StatefulWidget을 사용하지말고 아래처럼 AnimatedWidget을 이용하여 리팩토링이 가능하다.

class AnimatedLogo extends AnimatedWidget {
  const AnimatedLogo({super.key, required Animation<double> animation})
      : super(listenable: animation);

  @override
  Widget build(BuildContext context) {
    final animation = listenable as Animation<double>;
    return Center(
      child: Container(
        margin: const EdgeInsets.symmetric(vertical: 10),
        height: animation.value,
        width: animation.value,
        child: const FlutterLogo(),
      ),
    );
  }
}

 

7. AnimationBuilder

플러터에서 빌더라 함은, stateful 위젯에서 리빌드를 할 때 전체를 리빌드 하는것이 아닌, 특정 부분만 빌드를 하고 싶을 때 사용된다.

애니메이션 또한 마찬가지이다. 특정 애니메이션이 빌드함수 전체를 빌드 시킨다면, 이를 AnimationBuilder를 이용하여 애니메이션 되는 부분만 애니메이션이 돌 때 빌드되록 쪼갤 수 있다.

 

반응형

댓글