버튼을 눌렀을 때 크기나 색만 부드럽게 바꾸고 싶은데 controller부터 만들면 코드가 커진다. 반대로 복잡한 진행 상태를 AnimatedContainer로 억지로 표현하면 제어가 사라진다. 애니메이션의 제어 필요 수준으로 나누면 선택이 쉬워진다.
값 변화만 부드럽게 연결하면 암시적 애니메이션이 알맞다
도식의 왼쪽처럼 시작값과 끝값만 연결하면 되는 변화에는 위젯이 진행을 맡는 암시적 방식이 간단하다. 크기와 색이 함께 바뀌는 최소 예제로 동작을 보자.
class SizeButton extends StatefulWidget {
const SizeButton({super.key});
@override
State<SizeButton> createState() => _SizeButtonState();
}
class _SizeButtonState extends State<SizeButton> {
bool selected = false;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => setState(() => selected = !selected),
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
width: selected ? 160 : 120,
height: 48,
color: selected ? Colors.blue : Colors.grey,
),
);
}
}SizeButton을 화면에 놓고 누르면 너비와 색이 200밀리초 동안 바뀐다. 다시 누르면 원래 값으로 돌아간다. 값 변화가 명확하고 중간 정지나 여러 애니메이션의 동기화가 필요 없으면 이 방식이 읽기 쉽다. 화면에 보이는 탭 영역이 변하므로 실제 버튼에서는 접근성 이름과 터치 영역도 따로 확인해야 한다.
시간축을 직접 다뤄야 하면 controller를 쓴다
사용자 드래그에 따라 진행도를 바꾸거나, 여러 효과를 정확히 조합하고, 반복·정지·역재생을 제어해야 한다면 AnimationController가 필요하다. controller는 State가 소유하고 dispose에서 해제해야 한다. 그렇지 않으면 ticker가 화면 수명 밖에서도 남을 수 있다.
먼저 위 예제를 여러 번 빠르게 눌러도 최종 상태가 selected 값과 일치하는지 확인한다. 진행도를 외부 입력에 맞춰 멈추거나 역재생해야 하는 요구가 생겼을 때 controller로 바꾸는 편이 낫다. controller로 옮긴 뒤에는 화면을 닫았다가 다시 열어도 애니메이션 관련 오류가 없는지 살핀다.
움직임은 정보 전달을 방해하지 않아야 한다
애니메이션이 중요한 버튼 위치를 계속 바꾸거나 사용자의 동작을 늦추면 장식이 기능을 이긴다. 짧은 시간, 예측 가능한 변화, 접근성의 모션 감소 설정을 고려하자. 성능 문제가 있으면 매 프레임 무거운 계산이나 큰 위젯 트리 재구성이 없는지도 확인한다.
핵심 요약
단순한 속성 변화에는 암시적 애니메이션을, 시간축을 세밀하게 제어해야 할 때는 AnimationController 기반 명시적 애니메이션을 쓴다. controller는 반드시 dispose하고, 움직임이 상태 변화를 돕는지 확인하자. 애니메이션의 복잡도는 요구한 상호작용만큼만 늘리는 것이 좋다.

