Flutter 애니메이션 정리 (1) - 큰 그림, 구성요소와 명시적 애니메이션

vhv3y8·2025년 5월 18일
0

Flutter 애니메이션

목록 보기
1/1

이 시리즈는 LLM의 도움을 받아 공부한 내용을 바탕으로 정리한 글들이므로, 잘못된 내용이 있을 수 있습니다.

UI에서 애니메이션은, 있어보이게 표현하자면 뭔가가 다른 상태로 바뀔 때 이산적이 아니라 연속적으로 변화하는거라고 생각해볼 수 있다.

시작값끝값, 지속될 시간, easing 함수 등을 표현하는 방법이 제공되고 그에 맞게 작성하면 엔진이 이펙트를 구현해주는 식이다.

웹에서의 CSS, 안드로이드의 Jetpack Compose, 그리고 Flutter 등등은 저런 값들을 받는다는 점이 비슷하다.

Flutter에는 애니메이션을 표현할 수 있는 다양한 방식과 아주 많은 위젯들이 존재하는데, 우선 거의 모든 위젯을 리스트로 한눈에 모아보고, 이후 이들을 모두 이해하기 위해 알아야 할 구성요소에 대해 생각해보자.

1. 큰 그림

📌 기본 구분

1. Implicit animation (자동) - 상태 변경만 하면 됨
2. Explicit animation (수동) - 컨트롤러 직접 조작 필요
3. Custom animation - 내가 직접 Path, 시간 등 정해야 함
4. Gesture/Scroll 연동 - 사용자의 동작에 따른 반응

간단한 이펙트만 주려면 앞쪽만 알면 되고, 뒤쪽으로 갈수록 표현할 수 있는 범위가 넓어진다고 한다.

제스처나 스크롤 입력 같은 경우는 말그대로 입력을 받게 해주는거고, 표현을 어떻게 하는지를 잘 알면 생각보다 어렵지 않을 수 있을듯하다.

✅ 위젯/기술 전체 리스트 (카테고리별)

🔹 Implicit Animation (쉽고 자주 씀)

  • AnimatedContainer
  • AnimatedOpacity
  • AnimatedAlign
  • AnimatedPadding
  • AnimatedPositioned
  • AnimatedCrossFade
  • AnimatedSwitcher
  • AnimatedList
  • AnimatedDefaultTextStyle
  • TweenAnimationBuilder

🔹 Explicit Animation (컨트롤러 필요)

  • AnimationController
  • TickerProviderStateMixin
  • Tween, TweenSequence
  • CurvedAnimation
  • AnimatedBuilder
  • FadeTransition, ScaleTransition, SlideTransition, RotationTransition
  • SizeTransition, PositionedTransition
  • AnimatedWidget, AnimatedWidgetBaseState

🔹 Gesture 연동 / Custom

  • GestureDetector
  • onPanUpdate, onScaleUpdate, onLongPress
  • Draggable, DragTarget
  • Dismissible
  • Transform (scale/rotate/translate)
  • InteractiveViewer

🔹 Scroll 기반 연동

  • ScrollController
  • NotificationListener<ScrollNotification>
  • SliverAppBar, FlexibleSpaceBar
  • SliverAnimatedList, SliverPersistentHeader
  • ListWheelScrollView

🔹 Hero & 전환

  • Hero
  • CustomRectTween
  • PageRouteBuilder, PageTransitionsBuilder
  • go_routertransitionsBuilder, CustomTransitionPage

🔹 Staggered / Group Animation

  • Interval, Future.delayed
  • Multiple AnimationController
  • Staggered Animation
  • AnimationGroup, AnimatedSequence

🔹 Custom Drawing

  • CustomPainter
  • Canvas, Path, Paint, draw*()
  • AnimationController + CustomPaint 연동
  • ex. progress bar, ripple effect, waveform 등

2. 애니메이션의 구성요소

UI 애니메이션을 프로그래밍적으로 표현하려면 다음과 같은 요소들이 필요할거라고 생각해볼 수 있다 :

  • 위젯의 어떤 프로퍼티가 바뀔 것인지 (e.g. 배경색, opacity, 크기)
  • 시작값과 끝값
  • 지속될 시간 (몇 초에 걸쳐 이펙트가 표현될건지)
  • easing 함수

AnimatedContainer 같은 큰 그림 1번의 암시적 애니메이션 위젯들은, 결국 위젯이 많은 일들을 처리해주고 사용자인 나는 이런 값들만 딱딱 적어주거나 이 값들 중 일부조차 위젯이 처리해주는 식으로 되어있다고 이해할 수 있고,

뒤로 갈수록 모든 값을 직접 적어줘야 하고 프로그래밍적으로 이들이 합쳐지는 과정까지 사용자인 내가 적어주게 되어있다라고 이해할 수 있을듯하다.

그런 관점에서 2번 명시적 애니메이션에 대해 이해하면 이 모든 과정을 캐치할 수 있을듯하다.

명시적 애니메이션이 바로 flutter에서 위 요소들을 작성하는 방법이며, 1번 암시적 애니메이션 위젯 등등 다른 애니메이션 위젯들이 이 위에 올려져 동작한다고 생각할 수 있기 때문이다.

명시적 애니메이션

명시적 애니메이션은 flutter에서 애니메이션의 구성요소를 표현하는 방법이다 :

  • Tween :
    • 시작값끝값을 들고 있음
  • AnimationController :
    • 지속될 시간 값을 갖고, 시작 메서드, 정지 메서드, 현재 시간 등을 가짐
    • 시간을 움직이는 주체
  • Animation :
    • 현재 값을 들고 있음
    • 얘가 상태값이 됨
  • CurvedAnimation (Animation에서 확장됨)
    • easing 함수(curve)를 가지는 Animation

이들은 모두 위젯이 아니며, StatefulWidget 안에서 값으로 서로 엉켜있고 결국 상태값의 변화를 통해 애니메이션이 눈에 보이게 된다.

1) 그래프

이들이 왜 필요한지는 시간이 x축이고 변화할 프로퍼티 값이 y축인 그래프를 떠올려보면 이해할 수 있다.

지속될 시간(duration)은 x축 기준으로 시간이 0에서부터 몇까지 지속될거냐를 정하게 해준다.

시작값끝값은 y축에서 이동할 범위를 정해준다. 예를 들어 배경색이 흰색에서 검은색으로 변할수도 있고, 투명도가 0.2에서 0.8로 변할 수도 있다.

이때 시간이 0일 때 시작값에서 시작을 하게 될거고 (= (0, 시작값)), 시간이 일 때 끝값에서 끝난다는 건 고정이다. (= (끝, 끝값))

그 사이를 잇는 선이 어떻게 생겨먹었냐가 easing 함수라고 볼 수 있다.

이 그래프가 중요한 이유는, 애니메이션이 시작된 이후 특정 시간(x값)일 때 변하고 있는 프로퍼티값(y값)이 몇이냐를 특정할 수 있기 때문이다.

예를 들어 3초에 걸쳐 크기가 1에서 1.5배로 변한다면, 1.35초일 때 크기가 몇인지 명확히 알 수 있게 된다.

그리고 이 값이 Stateful 위젯에게 넘겨주고 프로퍼티에 반영되는 값이기 때문에 알아야 한다. (y값)

명시적 애니메이션에서는 값을 직접 넘겨주게 되는거고, 다른 애니메이션 위젯들도 내부적으로 이들을 통해 동작한다고 한다.

2) 예제

그래서 어떻게 사용한다는건지 예제를 하나 보자 :

class MyFade extends StatefulWidget {
  
  _MyFadeState createState() => _MyFadeState();
}

class _MyFadeState extends State<MyFade> with SingleTickerProviderStateMixin {
  late final AnimationController _controller; // 컨트롤러를 상태로 저장
  late final Animation<double> _animation; // Animation 값을 상태로 저장

  // State 생성할 때
  
  void initState() {
    super.initState();

    // AnimationController 생성
    _controller = AnimationController(
      vsync: this,
      duration: Duration(seconds: 1), // 애니메이션이 지속될 시간을 받음
    );

    // Tween().animate(CurvedAnimation(컨트롤러))의 결과값은 Animation이다.
    // 이걸 통해 모두가 연결되는 것이다 :
    _animation = Tween(begin: 0.0, end: 1.0).animate( // Tween : 시작값과 끝값
      CurvedAnimation(parent: _controller, curve: Curves.easeIn), // CurvedAnimation : easing 함수
    );

    _controller.forward(); // 컨트롤러의 시작 메서드
  }

  // State가 돌려주는 위젯
  
  Widget build(BuildContext context) {
    return Center(
      child: FadeTransition( // 일단은 상태값을 바로 반영해주는 위젯이라고 생각하고 넘어가자. 실제로도 복잡한 건 아님
        opacity: _animation, // Animation 상태값을 그대로 넘겨준다
        child: Text('Hello!', style: TextStyle(fontSize: 32)),
      ),
    );
  }

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

우선 당연하게도 값이 변하며 그게 UI에 반영되어야 하므로 StatefulWidget + State를 사용해야한다.

여기서 명시적 애니메이션을 통해 반영되는 변화할 프로퍼티의 값을 담고 있는 그 상태값은 바로 _animation, 즉 Animation이다.

build()에서 보면 opacity: _animation이라고 넘겨주고 있는데, 이제 컨트롤러를 통해 애니메이션이 시작이 되면 이 값이 1초에 여러 번 덮어씌워지면서 _MyFadeState(State)가 다시 그려지게 된다.

여기에서 캐치해야하는 건 다음과 같다 :

  • Animation, 즉 그래프의 y값이 StatefulWidget + State의 상태값이 되며 프로퍼티에 넘겨주면서 적용이 된다
  • 컨트롤러(AnimationController)도 같은 StatefulWidget + State에 상태값으로 저장되어 있고, Tween, CurvedAnimation과 엉켜져있는 형태로 존재한다 (Tween().animate())

어쨌든 위에서 언급한 애니메이션의 구성요소들은 모두 하나의 StatefulWidget + State 상에서 표현가능하다는 걸 알게 되었다.

그러면 애니메이션 구성요소의 작성으로부터 실제 위젯으로 눈에 보이기까지의 과정은 다음처럼 구성된다고 이해해볼 수 있다 :

  • Tween으로 시작값과 끝값, AnimationController로 지속될 시간, Animation으로 현재값, CurvedAnimation으로 easing 함수를 작성할 수 있다
  • 이들은 StatefulWidget + State 위에 작성해줘야 한다
  • 애니메이션이 눈에 보이게 되는 원리는 변화할 값(Animation)을 상태값으로 저장해두고 시간이 흐름에 따라 1초에 수십번 setState()를 해주는 것이다
  • 애니메이션의 시작/정지/역재생 등은 컨트롤러(AnimationController)의 메서드를 통해 조절 가능하다.

이 코드가 전부는 아니지만, 여기에 있는 걸 이해한다면 Flutter에서 애니메이션을 표현하는 모든 방법의 바탕을 이해했다고 볼 수 있을듯하다.

여기에 나온 모든 게 Flutter 소스코드 관점에서 정확히 어떻게 이루어져있고 어떻게 연결되는건지, 그리고 가장 로우레벨 명시적 애니메이션으로부터 암시적 애니메이션까지 점진적으로 어떻게 사용하고 내부적으로 어떻게 이루어져있는지 등을 다음 글에서 알아보도록 하자.

profile
개발 기록, 미래의 나에게 설명하기

0개의 댓글