인문주의 사피엔스

Flutter 애니메이션의 이해와 구현 본문

프로그래밍/Flutter

Flutter 애니메이션의 이해와 구현

인문주 2021. 11. 22. 04:35
반응형

Flutter는 애니메이션의 구현을 위한 다양한 도구를 제공합니다. 종류가 다양한 만큼 각 도구의 특징을 이해해야 용도와 목적에 맞는 도구를 제대로 선택할 수 있습니다. 따라서 애니메이션을 종합적으로 이해하고 전체 그림을 파악하는 것이 중요합니다. 하나의 애니메이션을 다양한 방법으로 구현해보면 다양한 애니메이션 구현 방법 간의 차이를 명확하게 이해할 수 있습니다.

 

예제 앱

아래 그림은 예제 앱의 화면입니다.

 

예제 앱 화면

 

예제 앱에는 동그라미가 그려진 여러 개의 버튼이 있습니다. 각각의 버튼을 누르면 각각의 애니메이션이 실행됩니다. 애니메이션의 내용은 모두 동일합니다. 동그라미의 크기가 줄어들었다 다시 원래 크기로 복원됩니다. 대신 애니메이션을 구현하는 방법은 서로 다릅니다. 비교를 위해 애니메이션을 적용하지 않은 경우가 맨 위의 검은색 동그라미에 구현되었습니다. 다음은 검은색 동그라미의 소스코드입니다.

 

 

검은색 동그라미를 눌러 ListTile의 onTap을 통해 toggle 함수가 호출될 때마다 scale이 0.5 또는 1.0으로 변경됩니다. 동시에 setState가 실행되어 build 함수가 다시 호출됩니다. build 함수의 Transform.scale 함수는 scale의 값으로 MyCircle의 크기를 조절합니다. MyCircle은 동그라미를 구현하는 클래스입니다. 다음은 MyCircle의 소스코드입니다.

 

 

애니메이션의 의미

나머지 동그라미들은 모두 애니메이션으로 구현됩니다. 구현을 위해서는 먼저 의미를 이해해야 합니다. 결론부터 말하면 애니메이션은 변수의 값을 일정한 시간마다 변경하는 것입니다. 여기서 변수라는 것은 늘어나는 직선의 길이, 바뀌는 색깔의 RGB 값, 이동하는 도형의 위치 좌표 등을 예로 들 수 있습니다. 예제 앱의 변수는 일정한 시간마다 변경되는 동그라미의 반지름입니다. 애니메이션은 값과 시간의 문제입니다.

 

애니메이션의 값 - Animation<T>

Animation<T>는 애니메이션의 값을 저장하는 추상 클래스입니다. 값의 형식을 나타내는 T는 int, double, Color, Size 등 클래스를 포함해 모든 형식이 허용됩니다. 구체적으로 말해 애니메이션의 값은 value라는 이름의 변수로 Animation에 정의되어 있습니다. 애니메이션은 그 value의 값을 일정한 시간마다 변경해서 UI에 반영하는 것입니다.

 

애니메이션의 시간 - AnimationController

AnimationController는 애니메이션의 시간을 담당하는 클래스입니다. AnimationController의 생성자는 다음과 같이 애니메이션의 실행 시간을 입력받습니다.

 

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

 

AnimationController는 Animation<double>을 상속합니다. 그 자체에 이미 value 변수가 있다는 뜻입니다. value의 형식은 double입니다. value의 범위는 기본적으로 0.0에서 1.0까지입니다. value의 값을 일정 시간마다 변경시키는 것이 AnimationController의 역할입니다. value가 변경되는 크기는 일정할 수도 있고 일정하지 않을 수도 있습니다. 일정한 값의 궤적을 일반적으로 선형적이라고 표현합니다. 반대로 일정하지 않은 궤적은 비선형적이라고 표현합니다. 값의 궤적은 애니메이션의 순간 속도입니다. 선형적인 애니메이션의 속도는 실행하는 동안 일정하고 비선형적인 애니메이션의 속도는 실행하는 동안 바뀝니다.

 

요약하면 AnimationController는 애니메이션의 값을 0.0에서 1.0까지의 범위에서 선형적으로 변경시킵니다. 값의 형식과 범위, 변경 궤적을 바꾸려면 추가적인 처리가 필요합니다.

 

애니메이션 속도의 변화 - CurvedAnimation

CurvedAnimation는 애니메이션 값의 변경 궤적, 즉 속도를 비선형적으로 만들 때 사용됩니다. 따라서 선형적인 애니메이션의 속도를 원한다면 사용할 필요가 없습니다. CurvedAnimation은 다음과 같이 AnimationController를 입력받아 Animation<double> 형식의 객체를 생성합니다.

 

final Animation<double> curve = CurvedAnimation(
    parent: controller,
    curve: Curves.ease,
);

 

이 과정을 통해 AnimationController의 선형 궤적이 비선형 궤적으로 바뀝니다. 즉 일정하던 애니메이션의 속도에 변화가 생겨 스프링의 움직임과 같은 비선형 효과가 발생됩니다. 비선형 궤적의 형태는 Curves 클래스에 다양한 상수로 정의되어 있습니다.

 

애니메이션 값의 형식과 범위 - Tween<T>

Tween<T>는 AnimationController와 CurvedAnimation의 출력인 Animation<double>의 형식과 범위를 바꾸는 데 사용하는 도구입니다. double 형식과 0.0에서 1.0까지의 값의 범위는 임의의 형식 T와 임의의 범위로 변경됩니다. Tween<T>은 다음과 같이 정의되고 사용됩니다.

 

final Animation<Offset> animation = Tween<Offset>(
    begin: const Offset(100.0, 100.0),
    end: const Offset(200.0, 300.0),
).animate(curve);

 

여기서 임의의 형식 T는 Offset으로 정의되었습니다. Offset은 위치 좌표를 나타냅니다. 따라서 위의 Tween<Offset>은 시작 좌표와 끝 좌표를 잇는 직선 경로를 의미합니다. 그리고 curve가 animate 함수에 입력되어 Animation<Offset>으로 생성되었습니다. 즉 0.0에서 1.0까지의 범위가 Offset(100.0, 100.0)에서 Offset(200.0, 300.0)까지의 범위로 변환되었습니다.

 

Color, Int, Size 등의 특정 T에 대해서는 Tween<T>를 상속하는 별도의 API 클래스들를 사용할 수도 있습니다. Flutter는 다음과 같은 API 클래스들을 제공합니다.

 

AlignmentGeometryTween, AlignmentTween, BorderRadiusTween, BorderTween, BoxConstraintsTween, ColorTween, ConstantTween, DecorationTween, EdgeInsetsGeometryTween, EdgeInsetsTween, FractionalOffsetTween, IntTween, MaterialPointArcTween, Matrix4Tween, RectTween, RelativeRectTween, ReverseTween, ShapeBorderTween, SizeTween, StepTween, TextStyleTween, ThemeDataTween

 

애니메이션 실행하기

AnimationController의 forward 함수를 호출하면 애니메이션이 실행됩니다. 애니메이션이 실행된다는 것은 Animation<T>의 value가 변경되기 시작한다는 의미입니다. value가 변경될 때마다 AnimationController의 addListener를 통해 콜백 이벤트가 발생됩니다. 이벤트가 발생될 때마다 setState를 호출함으로써 변경된 value의 값이 UI에 반영됩니다.

 

애니메이션 구현 방법의 종류

지금까지 설명한 내용은 애니메이션을 구현하기 위한 기본적이고 전체적인 과정입니다. 그 과정에서 애니메이션을 제어하는 핵심 요소는 AnimationController입니다. AnimationController를 프로그래머가 직접 제어하는 것을 명시적 애니메이션(explicit animation)이라고 합니다. 그와 달리 AnimationController를 API 클래스가 대신 제어하는 것을 내포된 애니메이션(implicit animation)이라고 합니다. 애니메이션의 상태를 세부적으로 파악하고 제어할 필요가 있다면 명시적 애니메이션을 사용해야 합니다. 반면 비교적 간단한 애니메이션을 단순히 보여줄 목적이라면 내포적 애니메이션으로 충분합니다.


반응형

 

명시적 애니메이션

 

Flutter 애니메이션은 구현 방법에 따라 명시적 애니메이션과 내포된 애니메이션으로 구분됩니다. 명시적 애니메이션은 AnimationController를 프로그래머가 직접 제어합니다. 반면 내포된 애니메이션은 AnimationController를 API 클래스가 대신 제어합니다. 명시적 애니메이션은 addListener와 setState의 사용 여부에 따라 다시 몇 가지 종류로 나누어집니다.

 

기본적인 구현 방법

애니메이션의 기본적인 구현 방법은 AnimationController, addListener, setState 등 애니메이션에 관련된 모든 API를 직접 사용합니다. 다음은 기본적인 방법으로 구현한 소스코드입니다.

 

 

 

위의 소스코드가 애니메이션을 사용하지 않는 처음의 소스코드와 다른 부분은 scale 변수를 대체하여 선언된 controller와 animation입니다. 그것은 scale을 대체하여 animation.value가 사용된다는 의미입니다. 애니메이션의 실행되면 animation.value의 값이 바뀌기 시작하고 그 결과로 동그라미의 크기가 변경됩니다.

 

initState 함수에서는 애니메이션의 시간과 값이 설정되었습니다. 애니메이션 시간은 1초로 설정되어 AnimationController의 생성자에 입력되었습니다. 애니메이션 값은 1.0에서 0.5까지의 범위로 정의되어 Tween의 생성자에 입력되었습니다. 마지막으로 addListener를 통해 등록된 콜백에서 setState가 실행되어 UI가 갱신되도록 설정되었습니다. build 함수에서는 scale을 대체하여 animation.value가 사용되었고 MyCircle은 빨간색으로 설정되었습니다. 이제 버튼을 누르면 빨간색 동그라미의 크기가 0.5배로 줄어들었다 다시 1.0배로 늘어나게 됩니다.

 

낮은 단계 애니메이션 (low-level animation)

낮은 단계 애니메이션에서 사용되는 CustomPaint, CustomPainter, Canvas는 정확히 말해 애니메이션을 구현하는 도구가 아닙니다. 그 클래스들은 애니메이션과 무관하게 정지된 그림을 세부적으로 그릴 때 사용하는 도구입니다. 세부적인 그림은 다음과 같은 Canvas의 함수들을 사용하여 그리게 됩니다.

 

drawArc, drawAtlas, drawCircle, drawColor, drawDRRect, drawImage, drawImageNine, drawLine, drawOval, drawPaint, drawParagraph, drawPath, drawPicture, drawPoints, drawRawAtlas, drawRawPoints, drawRect, drawRRect, drawShadow, drawVertices

 

위의 함수들을 사용하여 점, 선, 원, 호 등의 다양한 도형을 세부적으로 그릴 수 있습니다. 다음은 drawCircle을 사용하여 동그라미를 그리는 소스코드입니다.

 

 

CustomPainter는 이름 그대로 ‘화가’입니다. 화가는 Canvas에 그림을 그립니다. Canvas의 drawCircle를 호출하면 동그라미가 그려집니다. 위의 소스코드는 Offset.zero의 위치에 radius의 반지름을 갖는 동그라미를 그립니다. 화가가 그린 그림은 CustomPaint를 통해 위젯으로 변환됩니다. 다음은 동그라미를 위젯으로 변환하는 소스코드입니다.

 

 

Paint는 이름 그대로 ‘물감’입니다. 위에서는 4.0 두께의 오렌지색 획으로 정의되었습니다. 동그라미의 반지름은 50에 scale을 곱한 값으로 정의되었습니다. 다음은 MyPaintedCircle을 생성하고 사용하는 소스코드입니다.

 

 

참고로 말해 위의 소스코드는 낮은 단계 애니메이션에 꼭 맞는 예제는 아닙니다. 왜냐하면 동그라미가 한번에 통째로 그려지기 때문입니다. 통째로 그려지는 동그라미는 다른 방법으로 더 간단하게 구현할 수 있기 때문입니다. 낮은 단계 애니메이션을 반드시 사용해야 하는 경우는 예를 들어 궤적을 따라 선을 그리며 동그라미를 완성하는 애니메이션입니다.

 

AnimationController를 프로그래머가 직접 제어하는 명시적 애니메이션에서 addListener와 setState의 역할을 대체하는 API 클래스가 AnimatedWidget 및 AnimatedBuilder입니다. 이는 변경된 값으로 UI를 갱신하는 작업을 API 클래스가 대신 수행한다는 의미입니다.

 

AnimatedWidget

AnimatedWidget은 StatefulWidget을 상속합니다. 내부에서 addListener와 setState를 호출합니다. AnimatedWidget을 사용하면 애니메이션을 별도의 위젯으로 분리할 수 있습니다. 다음은 AnimatedWidget을 구현하는 소스코드입니다.

 

 

위의 과정을 통해 자식 위젯의 크기를 애니메이션하는 부분이 MyAnimatedScale이라는 위젯으로 분리되었습니다. 다음은 MyAnimatedScale을 사용하여 노란색 동그라미의 크기를 애니메이션하는 소스코드입니다.

 

 

위의 내용은 애니메이션의 기본 구현 방식과 거의 비슷해 보입니다. 하지만 initState 함수에서 addListener와 setState를 호출하는 코드가 없다는 것을 확인할 수 있습니다.

 

FooTransition

ScaleTransition은 AnimatedWidget을 상속한 API 클래스입니다. ScaleTransition은 앞에 나온 MyAnimatedScale과 같은 방식으로 구현되었습니다. Flutter는 자주 사용하는 특정 형태의 애니메이션에 대해 FooTransition 같은 형태의 이름을 갖는 API 클래스들을 아래와 같이 제공합니다.

 

AlignTransition, DecoratedBoxTransition, DefaultTextStyleTransition, PositionedTransition, RelativePositionedTransition, RotationTransition, ScaleTransition, SizeTransition, SlideTransition, FadeTransition

 

다음은 ScaleTransition을 사용하는 소스코드입니다.

 

 

AnimatedBuilder

AnimatedBuilder는 AnimatedWidget을 상속한 클래스입니다. AnimatedBuilder는 어떤 위젯의 build 함수에서 애니메이션을 한 부분으로 포함하고 싶은 경우에 유용하게 사용할 수 있습니다. 다음은 AnimatedBuilder를 사용한 소스코드입니다.

 

 

MyScaleTransition은 다음과 같이 사용됩니다.

 

 


내포된 애니메이션

 

내포된 애니메이션을 사용하면 소스코드가 많이 간결해집니다. AnimationController를 제어하는 부분이 API 클래스로 옮겨지기 때문입니다. 소스코드가 간결해지는 장점도 있지만 애니메이션을 세부적으로 제어할 수 없는 단점도 있습니다. 실행을 제어할 수 없기 때문에 내포된 애니메이션은 클래스 생성과 동시에 자동으로 시작됩니다. 내포된 애니메이션의 API 클래스들은 모두 ImplicitlyAnimatedWidget을 상속받습니다. 비교적 단순한 애니메이션을 간단하게 구현하는 경우라면 내포된 애니메이션이 정답입니다.

 

TweenAnimationBuilder

TweenAnimationBuilder는 애니메이션의 시간과 값을 Duration과 Tween의 형식으로 입력받습니다. 다음은 TweenAnimationBuilder을 사용하는 소스코드입니다.

 

 

 

AnimationController를 제어하는 내용이 없기 때문에 소스코드가 많이 간결해지는 것을 확인할 수 있습니다. 버튼을 누를 때마다 toggle 함수에서 begin과 end의 값이 교체되고 setState가 실행되면 build 함수에서 TweenAnimationBuilder가 생성되면서 애니메이션이 실행됩니다.

 

AnimatedFoo

애니메이션의 시간과 목표 값만 입력받는 AnimatedScale을 사용하면 소스코드가 더 간결해집니다.

 

 

Flutter는 이와 같이 특정한 형태의 애니메이션에 대해서 AnimatedFoo 형태의 이름을 갖는 다음과 같은 API 클래스들을 제공합니다.

 

AnimatedAlign, AnimatedContainer, AnimatedCrossFade, AnimatedDefaultTextStyle, AnimatedOpacity, AnimatedPadding, AnimatedPhysicalModel, AnimatedPositioned, AnimatedPositionedDirection, AnimatedRotation, AnimatedScale, AnimatedSize, AnimatedSlide, AnimatedSwitcher, AnimatedTheme

 


 

다음은 예제 앱의 전체 소스코드입니다.

 

 

 

 

 

반응형
Comments