피트니스 산업과 IT, 그리고 스타트업

IT

플러터, 애니메이션 구현하기

핏더스트리 2025. 5. 1. 15:24
728x90
반응형

플러터, 애니메이션 구현하기

Flutter로 앱을 만들다 보면 ‘디자인은 괜찮은데 뭔가 밋밋하다’는 생각이 들 때가 있어요. 바로 이런 순간, 애니메이션이 진가를 발휘해요. 사용자의 시선을 자연스럽게 이끌고, 앱에 생동감을 불어넣어주는 요소가 바로 애니메이션이죠.

 

이번 글에서는 Flutter에서 애니메이션을 구현하는 기본적인 방법들을 정리해보려 해요. 암시적 애니메이션(Implicit Animations)명시적 애니메이션(Explicit Animations)의 차이를 중심으로, 각각의 방식에 따라 어떤 위젯을 사용할 수 있고, 어떤 상황에서 적합한지 예제를 통해 살펴볼게요. 복잡해 보이지만, 직접 구현해보면 의외로 재밌고 강력한 기능이라는 걸 느낄 수 있어요.

 

 

암시적 애니메이션

암시적 애니메이션은 말 그대로 명령을 일일이 주지 않아도, 위젯의 속성이 변경되면 자동으로 애니메이션이 적용되는 방식이에요. 가장 큰 장점은 코드가 간단하고 직관적이라는 점이에요. Flutter에서는 AnimatedContainer, AnimatedAlign, AnimatedOpacity, AnimatedPositioned 등이 대표적인 암시적 애니메이션 위젯이에요.

 

이 위젯들의 공통점은, curve와 duration 속성을 필수로 설정한다는 점이에요.

  • curve는 애니메이션의 속도 곡선이에요. 예를 들어 Curves.easeIn은 느리게 시작해 빠르게 마무리되죠.
  • duration은 애니메이션이 진행되는 시간을 의미하며 Duration(seconds: 1)처럼 설정할 수 있어요.

각 위젯은 특정 속성이 변경될 때 애니메이션을 실행해요. 예를 들어,

  • AnimatedAlign은 alignment가 바뀔 때
  • AnimatedContainer는 size, color, padding 등이 바뀔 때
  • AnimatedOpacity는 opacity가 변경될 때
  • AnimatedPositioned는 left, top 등의 위치가 변경될 때

이런 위젯들은 사용하기 매우 간단하면서도, 사용자에게 자연스럽고 부드러운 전환 효과를 제공할 수 있어요. 예를 들어 버튼을 누르면 상자의 위치나 색이 바뀌는 인터랙션은 AnimatedContainer 하나로도 충분히 구현할 수 있죠. 암시적 애니메이션은 세밀한 제어가 필요 없는 상황, 즉 단순한 전환 효과나 상태 변경에 따른 움직임을 표현할 때 가장 효과적이에요. 코드도 간결하고, 유지보수도 쉬워서 Flutter 앱에서 가장 자주 쓰이는 애니메이션 방식이에요.

 

 

명시적 애니메이션

암시적 애니메이션이 간단하고 편리하다면, 명시적 애니메이션은 개발자가 세부 동작을 직접 설계할 수 있다는 점에서 훨씬 더 정밀한 제어가 가능해요. 예를 들어 애니메이션의 시작 타이밍, 속도 조절, 정지나 재시작 등 다양한 시나리오에 맞춰 세밀하게 동작을 설정할 수 있어요.

 

명시적 애니메이션을 구현하려면 반드시 알아야 할 핵심 요소들이 있어요.

  1. AnimationController
    애니메이션의 실행과 중지, 반복 등을 제어하는 객체예요. forward(), reverse(), stop(), reset() 같은 메서드를 통해 애니메이션의 흐름을 직접 다룰 수 있어요. 이 컨트롤러는 Flutter가 프레임을 갱신하는 타이밍과 동기화되어야 하기 때문에, 반드시 vsync 속성을 필요로 해요. 이를 위해 State 클래스에 TickerProviderStateMixin을 적용해야 해요.
  2. Tween
    애니메이션의 시작 값과 끝 값을 정의하는 객체예요. 예를 들어 Tween<double>(begin: 0, end: 300)은 0에서 300까지 값을 애니메이션하면서 자연스럽게 변화시켜줘요. 이 Tween은 animate() 메서드를 통해 AnimationController와 결합되며, 최종적으로 Animation 객체가 만들어져요.
  3. AnimatedBuilder
    이 위젯은 애니메이션의 값이 바뀔 때마다 자동으로 빌드 함수를 호출해 UI를 갱신해줘요. setState() 없이도 애니메이션의 상태 변화에 따라 UI를 업데이트할 수 있기 때문에 효율적이에요.

예를 들어, 사용자가 컨테이너를 길게 누르면 막대 게이지가 차오르고, 손을 떼면 멈추는 인터랙션을 구현한다고 할 때, 이 모든 흐름을 AnimationController로 정밀하게 조작할 수 있어요. 또한, 중간에 더블탭으로 애니메이션을 리셋하거나, 취소 이벤트를 감지해 애니메이션을 멈추는 등의 인터랙티브한 제어도 명시적 애니메이션에서만 가능한 영역이에요.

 

명시적 애니메이션은 처음엔 구조가 복잡해 보여도, 한 번 익숙해지면 애니메이션을 훨씬 유연하고 강력하게 다룰 수 있게 해주는 도구예요. 특히 사용자 행동에 따라 애니메이션 흐름이 바뀌거나, 복합적인 애니메이션이 필요한 상황에서 매우 유용하게 사용돼요.

 

 

Hero 애니메이션

Flutter에서 사용자 경험을 한 단계 끌어올리는 애니메이션 중 하나가 Hero 애니메이션이에요. 두 개의 화면 간에 같은 요소가 자연스럽게 연결되어 보이도록 만들어주는 방식으로, 특히 리스트 → 상세 페이지 전환에서 자주 사용돼요. 핵심은 Hero 위젯과 tag 속성이에요. 전환 전후의 두 화면에서 동일한 tag 값을 가진 Hero 위젯이 있을 때, Flutter는 자동으로 해당 요소를 부드럽게 연결해주는 애니메이션을 만들어줘요.

 

예를 들어, 상품 목록에서 이미지를 눌렀을 때 해당 이미지가 확대되며 상세 화면으로 전환되는 인터랙션을 구현해보면 아래와 같아요.

// 상품 리스트 화면
class HeroExample extends StatelessWidget {
  const HeroExample({super.key});

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        Navigator.push(
          context,
          MaterialPageRoute(builder: (_) => HeroDetailPage()),
        );
      },
      child: Container(
        padding: EdgeInsets.all(16),
        child: Row(
          children: [
            Hero(
              tag: 'product-image',
              child: ClipRRect(
                borderRadius: BorderRadius.circular(8),
                child: Image.network(
                  'https://picsum.photos/200/200',
                  width: 100,
                  height: 100,
                  fit: BoxFit.cover,
                ),
              ),
            ),
            SizedBox(width: 16),
            Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text('멋진 상품', style: TextStyle(fontSize: 18)),
                Text('₩200,000', style: TextStyle(color: Colors.grey)),
              ],
            ),
          ],
        ),
      ),
    );
  }
}
// 상세 페이지 화면
class HeroDetailPage extends StatelessWidget {
  const HeroDetailPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('상품 상세')),
      body: Column(
        children: [
          Hero(
            tag: 'product-image',
            child: AspectRatio(
              aspectRatio: 1,
              child: Image.network(
                'https://picsum.photos/200/200',
                fit: BoxFit.cover,
              ),
            ),
          ),
          SizedBox(height: 20),
          Text('이 상품은 정말 멋집니다. 다양한 설명이 들어갈 수 있어요.'),
        ],
      ),
    );
  }
}

이처럼 Hero 위젯으로 이미지를 감싸고, 두 화면 모두 동일한 tag를 설정해주면 화면 전환 시 자연스럽고 고급스러운 이동 효과가 적용돼요. 추가 설정 없이도 꽤 인상적인 전환을 구현할 수 있기 때문에, 앱의 완성도를 높이는 데 큰 역할을 해요.

 

단, 주의할 점은,

  • 두 위젯은 비슷한 형태와 위치를 갖고 있어야 자연스러워요.
  • tag는 반드시 고유하고 동일한 값이어야 해요.

Hero 애니메이션은 구현은 쉽지만, 사용자에게는 맥락이 이어지는 느낌을 주는 매우 강력한 도구라는 점에서 꼭 한 번 실습해볼 만한 기능이에요.

 

플러터, 애니메이션 구현하기

마치며...

Flutter에서 애니메이션을 구현하는 방법을 살펴보면서, 단순히 화면을 ‘움직이게’ 만드는 기술이 아니라 사용자의 흐름을 매끄럽게 이끄는 설계 도구라는 점을 다시 한 번 느끼게 되었어요. 암시적 애니메이션은 적은 코드로 빠르게 인터랙션을 넣을 수 있어서 자주 변하는 UI 구성에 매우 효과적이고, 명시적 애니메이션은 사용자의 행동에 따라 정밀하게 제어되는 복잡한 애니메이션 구현에 적합해요. Hero 애니메이션은 화면 간 전환의 이질감을 줄여주고, 앱 전체에 고급스러운 연결감을 부여해주죠.

 

결국 애니메이션은 기능적인 요소라기보다는 경험적인 요소에 가까워요. 사용자에게는 "왜 이게 이렇게 바뀌었는지", "지금 무슨 일이 일어나고 있는지"를 시각적으로 설명해주는 역할을 하기 때문이에요. Flutter는 이런 애니메이션을 구현하는 데 필요한 도구들을 풍부하게 제공하고, 그 사용법도 비교적 직관적인 편이라서 조금만 연습하면 바로 실무에 적용할 수 있어요.

 

앱의 완성도를 높이고 싶다면, 애니메이션은 선택이 아닌 필수적인 요소라고 생각해요. 기능은 잘 돌아가는데도 앱이 '어딘가 허전하다' 느껴질 때, 애니메이션을 한 스푼 넣어보세요. 사용자의 감정과 집중을 끌어당기는 힘이 분명히 생길 거예요!

 

728x90
반응형