[Flutter] 플러터 애니메이션 전체보기

Broccolism·2022년 1월 10일
5
post-thumbnail

플러터의 2가지 애니메이션

플러터에서는 2가지 방법으로 애니메이션을 구현할 수 있다. 바로 암묵적 애니메이션(Implicit animation)과 명시적 애니메이션(explicit animation)이다.

암묵적 애니메이션은 미리 정의된 스타일과 애니메이션을 사용하기 때문에 구현이 아주 간단하다. 만약 미리 정의된게 아니라 커스터마이징해서 애니메이션을 직접 만들고 싶다면 명시적 애니메이션을 쓰면 된다. 대신 적어야 하는 코드가 조금 늘어난다.

플러터에는 다음 2가지 애니메이션이 있다: 간단한 암묵적 애니메이션, 커스터마이징 할 수 있는 명시적 애니메이션.

그러면 각각의 특징을 조금 더 알아보자.

1. 암묵적 애니메이션

위젯 내부에서 자체적으로 애니메이션이 일어난다.

  • 애니메이션이 이미 프로그래밍 되어있거나, 스타일이 입혀져있다.
    • 다른 종류의 애니메이션 위젯을 사용하기 위해 적어야 하는 리스너(listener)나 티커(ticker)를 추가하는 코드가 필요없다.
  • 애니메이션의 트리거, 즉 애니메이션을 시작 타이밍을 지정할 필요가 없다. 값이 변함에 따라 자동으로 애니메이션이 일어난다.
    • 예) 투명도가 변하는 애니메이션: 투명도를 의미하는 변수의 값이 50에서 100으로 변하는 순간, 자동으로 애니메이션이 시작된다.

2. 명시적 애니메이션

암묵적 애니메이션 위젯에서는 제공하지 않는, 애니메이션 커스터마이징을 할 수 있다.

  • 애니메이션을 컨트롤하기 위해 AnimationController를 등록해야 한다.
  • AnimationBuilder를 사용해서 내 입맛대로 애니메이션을 구현할 수 있다.
    • 그래서 리스너, 티커 등 커스터마이징을 위한 다른 요소를 등록한다.
  • 반드시 애니메이션의 트리거를 지정해줘야한다.

암묵적 애니메이션(Implicit Animation)

명시적 애니메이션(Explicit Animation)

명시적 애니메이션을 구현하는 방식에도 종류가 있다.

  1. 빌트인 위젯을 사용하는 방법
    ~~Transition으로 끝나는 위젯을 사용하면 된다.
  2. AnimatedBuilder를 사용하는 방법
    이 방식을 사용하면 원하는 위젯을 AnimatedBuilder로 감싸는 식으로 구현하게 된다. 좀 더 커스터마이징을 많이 할 수 있다.

이번 글에서는 방법 1에 해당하는 ~~Transition 위젯 사용법을 소개하고, 다음 글에서 AnimatedBuilder에 대해 적으려고 한다.

필수 셋팅

명시적 애니메이션을 사용하기 위해서 만들어야 할게 2개 있다. 컨트롤러와 애니메이션 객체다.

1. 컨트롤러: AnimationController

  • 애니메이션을 시작하거나/거꾸로 재생하거나/멈춘다.

  • 주어진 value로 애니메이션을 조절한다.

  • 애니메이션의 lowerBound와 upperBound, 즉 설정한 애니메이션의 몇 %까지 진행하고 초기값은 몇 %에서 시작할 것인지를 결정한다.

    • 예를 들어, 0%부터 100%까지 변하는 투명도에 대한 애니메이션이라고 할 때 lowerBound가 0.1이라면 실제로 투명도는 10%인 상태에서 시작한다. 애니메이션이 시작되기 전에도 투명도는 10%가 된다.
  • 필수 파라미터

이름정체넘겨준 객체
vsyncTickerProvider. Ticker를 제공한다. 1프레임마다 주어진 콜백 함수를 실행한다.this (위젯의 state에 TickerProviderStateMixin를 사용했기 때문에 자기 자신을 넘겨도 된다.)

2. 애니메이션: Animation

  • 컨트롤러와의 관계: 컨트롤러가 애니메이션의 상태를 조절한다. 즉, 상태는 애니메이션 객체가 들고 다니고 그 값을 컨트롤러가 변경할 수 있다.
  • Animation 클래스는 abstract class이다. Tween, CurvedAnimation, TrainHoppingAnimation 등을 사용해 객체를 만들 수 있다.
  • 공식 문서 예제에서는 CurvedAnimation를 사용했다. 이 클래스의 필수 파라미터는...
이름정체넘겨준 객체
parentAnimationController. 여기에 addStatusListener를 달아서 변화를 감지한다.위에서 만들어준 animationController 객체.
curve애니메이션이 어떤 흐름으로 진행될지 정한다. 직접 보는게 훨씬 이해가 빠르다. 여기를 보자.원하는 Curve 객체.

예제 코드

연습삼아 총 2가지 위젯을 구현했다. 무한 반복 애니메이션과 끝난 뒤 역재생되는 애니메이션이다.

예제 코드 1: 무한 반복 애니메이션

class _TransitionAnimationScreenState extends State<TransitionAnimationScreen>
    with TickerProviderStateMixin {
  late final AnimationController _controller = AnimationController(
    duration: const Duration(seconds: 1),
    vsync: this,
  )..repeat(reverse: true);
  
  late final Animation<double> _animation = CurvedAnimation(
    parent: _controller,
    curve: Curves.easeInToLinear,
  );

  
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
...

이렇게 셋팅을 한 다음, 애니메이션을 사용할 위젯에서는 이렇게 써먹으면 된다. 아래는 컨테이너의 투명도를 조절하는 예제다.

FadeTransition(
  opacity: _animation,
  child: Container(
    color: Colors.green,
    width: 300,
    height: 100,
  ),
),
  • 원래는 애니메이션을 실행시키는 코드도 직접 적어야하지만, _controller를 만들 때 repeat 설정을 해줬기 때문에 애니메이션이 무한반복된다. 따라서 이 화면에 들어가기만 해도 바로 애니메이션이 실행된다.

    • reverse: true 옵션을 주면 애니메이션이 이런 순서로 반복된다: -시작-끝-시작-끝.
    • reverse: false 라면 이렇게 된다: 시작-시작-시작-시작
  • 애니메이션 컨트롤러는 프레임의 변화를 감지하는 ticker를 사용한다. 따로 dispose 해주지 않으면 리소스를 계속해서 잡아먹게 된다.

예제 코드 2: 끝난 뒤 역재생되는 애니메이션

class _TransitionAnimationScreenState extends State<TransitionAnimationScreen>
    with TickerProviderStateMixin {
  late final AnimationController _rotationController = AnimationController(
    duration: const Duration(seconds: 1),
    vsync: this,
  );
  
  late final Animation<double> _rotationAnimation =
      CurvedAnimation(parent: _rotationController, curve: Curves.bounceIn);

  
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
...

첫번째 예시 코드와 다른 점은 애니메이션 컨트롤러를 만들 때 repeat 함수를 호출하지 않았다는 점이다. 앞의 예제에서는 repeat 함수를 사용하면 애니메이션이 자동으로 시작되었기 때문에 애니메이션 트리거를 지정해주지 않았다. 트랜지션 애니메이션은 트리거를 직접 지정해줘야하기 때문에 repeat 함수를 쓰지 않은 이번 예시에서는 애니메이션이 언제 시작할 것인지 우리가 정해줘야 한다.

RotationTransition(
  turns: _rotationAnimation,
  child: GestureDetector(
  onTap: () {
    _rotationController.forward();
  },
  child: Container(
    margin: EdgeInsets.all(90),
    color: Colors.lightGreen,
    width: 300,
    height: 100,
    child: Text("탭하면 애니메이션 시작"),
  ),
);

애니메이션 관리는 컨트롤러가 한다. 즉, 컨트롤러에게 애니메이션을 시작할 수 있는 함수가 들어있다. forward 함수를 사용하면 애니메이션이 정방향으로 실행된다. GestureDetector.onTap에 해당 코드를 넣었다.

한번 재생하기만 하면 재미없으니까 애니메이션이 완료된 다음 역재생하게 만들었다. reverse 함수로 역재생할 수 있다. 이 때 주의할 점은 이렇게 적으면 안된다는거다.

_rotationController.forward();
_rotationController.reverse();

언뜻 보기에는 정방향으로 한번 재생한 다음 역재생하는 코드 같다. 나도 처음에는 이렇게 적었다. 하지만 이렇게 하면 눈으로 보기에 아무 변화도 일어나지 않는다. 1 프레임 내에서 forward, reverse 함수를 모두 실행해버리기 때문이다. 그래서 가만히 있는 것처럼 보인다. 제대로 하려면 아래 코드처럼 적어야 한다.

switch (_rotationController.status) {
  case AnimationStatus.dismissed:
    _rotationController.forward();
    break;
  case AnimationStatus.completed:
    _rotationController.reverse();
    break;
  default:
}

명시적 애니메이션 정리

  1. '명시적 애니메이션을 사용한다'는 것은 결국 사용자가 정의한 값을 특정 위젯의 속성에 넘겨주는 것이다. 마치 가로/세로 값을 정하는 것처럼!
  2. 그런데 애니메이션이기 때문에 그 값이 계속해서 변하고, 값이 변함에 따라 모양이 변하는 것 뿐이다.
  3. 애니메이션 컨트롤러에서 애니메이션의 상태를 조절한다.
profile
코드도 적고 그림도 그리고 글도 씁니다. 넓고 얕은 경험을 쌓고 있습니다.

0개의 댓글