[Flutter] 토스 스타일의 Scale-Bounce 인터렉션 만들기

윤달·2026년 2월 8일

Animation

목록 보기
2/3

들어가며

토스(Toss) 앱을 사용하면서, 탱탱볼처럼 살짝 커졌다가 제자리를 찾아가는 생동감있는 애니메이션을 보신 적이 있으신가요? 위에 보이는 gif는 토스 앱을 통해 혜택을 받을 수 있는 자몽다 서비스의 진입 화면인데요. 해당 애니메이션을 보고 구현을 해보고 싶다는 생각이 들어 구현해보게 되었습니다.

이번 포스팅에서는 토스 스타일의 Scale-Bounce 인터렉션을 플러터에서 구현하는 과정을 공유하려 합니다. 단순히 크기를 키우는 것을 넘어, TweenSequence를 활용한 정교한 바운스 효과와 Reduce Motion을 고려한 접근성 처리까지, 디테일한 구현 과정을 설명드리겠습니다.

💡 본 포스팅에서 소개하는 ScaleBounceIn 위젯을 쉽게 적용할 수 있도록 패키지를 개발하였습니다. 향후 더 많은 멋진 인터렉션을 해당 패키지에 추가할 에정이니 많은 관심 부탁드립니다!
package : cool_animation_flutter
github : https://github.com/yundal8755/cool_animations


1. 구현할 인터렉션 정의하기

저는 해당 인터렉션을 ScaleBounceIn 위젯이라고 정의했습니다. 핵심은 관성입니다. 물체가 등장할 때 목표 크기(1.0)에서 딱 멈추는 것이 아니라, 힘에 의해 살짝 더 커졌다가(Peak Scale) 다시 원래 크기로 돌아오는 움직임이 필요합니다. 코드상으로는 initialScale(0.3)에서 시작해 peakScale(1.15)까지 커진 뒤, 최종적으로 1.0으로 수렴하는 과정을 거칩니다. 이 과정에 투명도(Opacity) 변화를 함께 주어, 마치 화면 안쪽에서 튀어나오는 듯한 입체감을 주었습니다.


2. TweenSequence로 바운스 효과 제어하기

보통 단방향 애니메이션은 Tween 하나로 충분하지만, 커졌다가 작아지는 "왕복" 혹은 "단계별" 움직임은 TweenSequence를 사용하는 것이 훨씬 효율적입니다.

🤔 TweenSequence란?

하나의 AnimationController 시간 축 안에서 여러 개의 Tween을 순차적으로 실행할 수 있게 해주는 클래스입니다. 각 단계마다 가중치를 두어 시간 배분을 정교하게 조절할 수 있습니다. 이번 구현에서는 전체 애니메이션 시간을 100으로 봤을 때, 60%의 시간 동안은 커지고, 나머지 40%의 시간 동안 제라리를 찾아가도록 설계했습니다.


_scaleAnimation = TweenSequence<double>([
  // 1단계: 0.3 -> 1.15 (확 커지면서 등장)
  TweenSequenceItem(
    tween: Tween(begin: widget.initialScale, end: widget.peakScale)
        .chain(CurveTween(curve: Curves.easeOutCubic)),
    weight: 60,
  ),
  // 2단계: 1.15 -> 1.0 (살짝 줄어들며 안착)
  TweenSequenceItem(
    tween: Tween(begin: widget.peakScale, end: 1.0)
        .chain(CurveTween(curve: Curves.easeInOut)),
    weight: 40,
  ),
]).animate(_controller);

이렇게 TweenSequence를 활용하면 별도의 복잡한 상태 관리 없이도 쫀득한 바운스 효과를 하나의 컨트롤러로 깔끔하게 제어할 수 있습니다.


3. 스크롤을 하여 UI에 도달했을 때 보여주기

해당 위젯은 사용자가 스크롤을 내려 해당 요소가 화면에 보일 때 애니메이션이 시작되어야 효과적입니다. visibility_detector를 활용해 위젯이 화면에 10%(visibilityThreshold) 이상 노출되었을 때 애니메이션을 트리거하도록 구현했습니다.


void _onVisibilityChanged(VisibilityInfo info) {
  if (_didAnimateOnce || !widget.enabled || _isReduceMotionEnabled) return;

  final isNowVisible = info.visibleFraction >= widget.visibilityThreshold;
  
  // 화면에 등장하는 순간, 팝업!
  if (isNowVisible && !_isVisible) {
    _isVisible = true;
    _scheduleAutoPlay();
  }
}

마무리하며

이번 글에서는 토스나 최신 앱들에서 자주 보이는 Scale-Bounce 효과를 구현해 보았습니다. TweenSequence를 활용해 애니메이션의 단계를 나누고, Reduce Motion을 체크하여 접근성까지 고려한 견고한 위젯이 완성되었습니다. 강조하고 싶은 아이콘, 완료 체크 표시, 혹은 팝업 모달 등에 이 위젯을 감싸주기만 하면 앱의 생동감이 한층 살아날 것입니다. 해당 기능이 포함된 cool_animation_flutter 패키지에도 많은 관심 부탁드립니다.

긴 글 읽어주셔서 감사합니다!

profile
Mobile Developer

0개의 댓글