애니메이션: 움직임으로 완성되는 UI
애니메이션: 움직임으로 완성되는 UI
잘 만든 애니메이션은 UI를 더 직관적으로 느끼게 하고, 다듬어진 앱 특유의 감각을 만들어 주며, 전반적인 사용자 경험을 높여요. 플러터의 애니메이션 지원 덕분에 다양한 종류의 애니메이션을 비교적 쉽게 구현할 수 있어요. 특히 Material 위젯들은 디자인 스펙에 정의된 표준 동작 효과를 이미 갖고 있고, 그 효과를 직접 바꿀 수도 있어요.
어떤 방식을 쓸까: 접근 방법 고르기
플러터에서 애니메이션을 만드는 방법은 여러 가지예요. 어느 걸 써야 할지는 상황에 따라 갈리는데, 공식 문서는 결정 트리로 방향을 잡아 줘요. 크게 나누면 이래요.
- 미리 만들어진 암시적 애니메이션 — 가장 구현하기 쉬운 축이에요. 내장된 암시적 애니메이션으로 충분하다면 그것부터 쓰는 게 좋아요.
- 커스텀 암시적 애니메이션 —
TweenAnimationBuilder같은 것으로 직접 만들 수도 있어요. - 내장 명시적 애니메이션 — 애니메이션을 프레임워크에 맡기지 않고 직접 제어하려는 경우, 방향성 있는 내장 명시적 애니메이션 클래스를 쓸 수 있어요.
- 처음부터 만드는 명시적 애니메이션 —
AnimatedBuilder와AnimatedWidget으로 완전히 직접 만들 수도 있어요.
선택을 고민하고 있다면 공식 문서가 준비한 동영상을 보는 것도 큰 도움이 돼요.
애니메이션의 두 종류: 보간(tween)과 물리(physics)
플러터의 애니메이션은 크게 두 종류로 나뉘어요. 보간(tween) 기반과 물리(physics) 기반이죠.
Tween 애니메이션은 'in-betweening'의 줄임말이에요. 시작점과 끝점, 타임라인, 그리고 전환의 타이밍과 속도를 정하는 곡선(curve)을 정의하면, 프레임워크가 시작점에서 끝점으로 어떻게 전환할지 계산해요. 대부분의 애니메이션이 이 축에 속한다고 보면 돼요.
물리 기반 애니메이션은 움직임을 실제 세계의 물리 법칙처럼 모델링해요. 공을 던지면 던진 속도와 지면과의 거리에 따라 어디에 언제 떨어지는지가 정해지듯, 스프링에 매달린 공과 줄에 매달린 공은 떨어지고 튀는 방식이 달라져요. 이렇게 실제처럼 보이는 움직임이 필요할 때 물리 시뮬레이션을 써요.
자주 쓰는 애니메이션 패턴
UI·모션 디자이너들은 어떤 패턴을 반복해서 쓰게 돼요. 몇 가지 대표적인 걸 짚어볼게요.
- 리스트·그리드 애니메이션 — 목록이나 그리드에 요소가 추가·제거될 때 움직임을 넣는 패턴이에요.
- 히어로(Hero) 애니메이션 — 사용자가 페이지에서 요소(보통 이미지)를 선택하면 그 요소가 더 자세한 새 페이지로 날아가듯 전환되는 패턴이에요. 플러터에선
Hero위젯으로 화면(라우트) 사이의 공유 요소 전환을 쉽게 만들 수 있어요. - 스태거드(Staggered) 애니메이션 — 하나의 큰 애니메이션을 여러 작은 움직임으로 쪼개고 일부를 지연시키는 패턴이에요. 작은 애니메이션들이 순차적으로, 또는 일부 겹치듯 진행돼요.
필수 개념과 클래스
플러터의 애니메이션 시스템은 타입이 있는 Animation 객체를 기반으로 해요. 위젯은 build() 함수 안에서 이 애니메이션의 현재 값을 읽고 상태 변화를 들으며, 또는 더 정교한 애니메이션의 밑바탕으로 삼아 다른 위젯에 넘기기도 해요.
Animation<double> — Animation 객체는 화면에 무엇이 보이는지는 전혀 모르는 추상 클래스예요. 현재 값과 상태(완료됐는지 dismissed인지)만 알죠. 가장 흔히 쓰는 타입은 Animation<double>으로, 주어진 시간 동안 두 값 사이의 보간된 숫자를 순차적으로 생성해요. 출력은 선형일 수도, 곡선일 수도, 계단 함수일 수도 있어요. 제어 방식에 따라 역방향으로 돌거나 중간에 방향을 바꿀 수도 있죠. double 외에 Animation<Color>나 Animation<Size>처럼 다른 타입도 보간할 수 있어요. Animation 객체는 상태를 가지며, 현재 값은 항상 .value 멤버로 읽을 수 있어요.
CurvedAnimation — 애니메이션의 진행을 비선형 곡선으로 정의해요. animation = CurvedAnimation(parent: controller, curve: Curves.easeIn);처럼 써요. CurvedAnimation과 AnimationController는 모두 Animation<double> 타입이라 서로 바꿔 쓸 수 있어요. CurvedAnimation은 수정 대상 객체를 감싸는 방식이에요. 곡선은 Curves 클래스에 정의된 흔한 곡선을 쓰거나 직접 만들 수도 있고, Tween에 곡선을 적용하려면 CurveTween을 생각해 볼 수 있어요.
AnimationController — 하드웨어가 새 프레임을 준비할 때마다 새 값을 생성하는 특별한 Animation 객체예요. 기본적으로 주어진 시간 동안 0.0에서 1.0까지의 숫자를 선형 생성하는데, 이 생성은 화면 새로고침에 맞춰져서 보통 초당 60개 숫자를 만들어요. 예를 들어 2초짜리 컨트롤러는 이렇게 만들고, .forward()로 시작해요.
controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
AnimationController를 만들 때 vsync 인자를 넘기는데, 이 값 덕분에 화면 밖(offscreen) 애니메이션이 불필요한 자원을 소모하지 않아요. 상태 있는 객체를 vsync로 쓰려면 클래스 정의에 SingleTickerProviderStateMixin을 추가하면 돼요.
Tween — 시작·끝 값의 범위를 바꾸거나 다른 타입으로 애니메이션하기 위해 애니메이션을 감싸는 Animatable 계열이에요. 예를 들어 컨트롤러·곡선·Tween을 조합해 IntTween(begin: 0, end: 255).animate(curve)처럼 정수 값 애니메이션을 만들 수 있어요.
애니메이션 알림 — Animation 객체는 addListener()와 addStatusListener()로 정의하는 Listener와 StatusListener를 가질 수 있어요. Listener는 값이 바뀔 때마다 불리고, 가장 흔한 동작은 setState()를 불러 다시 빌드하는 거예요. StatusListener는 애니메이션이 시작·종료·전진·역진할 때(AnimationStatus로 정의) 불려요.
더 알아보기
- 애니메이션 튜토리얼: Animations tutorial
- 애니메이션 개요: Animations: overview
- Hero 애니메이션: Hero animations
- 스태거드 애니메이션: Staggered animations
- 애니메이션 위젯 카탈로그: Animation and motion widgets
- 애니메이션 레시피: Animation recipes