Flutter에서 애니메이션을 어떻게 구현하나요?

질문

Flutter에서 다양한 애니메이션을 구현하는 방법과 종류에 대해 설명해주세요.

답변

Flutter는 사용자 경험을 향상시킬 수 있는 다양한 애니메이션 기법을 제공합니다. 크게 암묵적 애니메이션과 명시적 애니메이션으로 나눌 수 있습니다.

1. 암묵적 애니메이션 (Implicit Animation)

속성 변화를 자동으로 애니메이션화하는 간단한 방법으로, 'Animated'로 시작하는 위젯들이 여기에 해당합니다.

import 'package:flutter/material.dart';

class ImplicitAnimationExample extends StatefulWidget {
  @override
  _ImplicitAnimationExampleState createState() => _ImplicitAnimationExampleState();
}

class _ImplicitAnimationExampleState extends State<ImplicitAnimationExample> {
  bool _isExpanded = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            AnimatedContainer(
              duration: Duration(milliseconds: 500),
              curve: Curves.easeInOut,
              width: _isExpanded ? 200 : 100,
              height: _isExpanded ? 200 : 100,
              color: _isExpanded ? Colors.blue : Colors.red,
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                setState(() {
                  _isExpanded = !_isExpanded;
                });
              },
              child: Text(_isExpanded ? '축소하기' : '확장하기'),
            ),
          ],
        ),
      ),
    );
  }
}

주요 암묵적 애니메이션 위젯:

  • AnimatedContainer: 크기, 색상, 패딩 등 변화
  • AnimatedOpacity: 투명도 변화
  • AnimatedPositioned: Stack 내 위치 변화
  • AnimatedCrossFade: 두 위젯 간 크로스페이드
  • AnimatedSwitcher: 위젯 전환 애니메이션
  • AnimatedAlign: 정렬 변화
  • AnimatedPadding: 패딩 변화
  • AnimatedSize: 크기 변화

2. 명시적 애니메이션 (Explicit Animation)

애니메이션 컨트롤러를 통해 직접 제어하는 방식으로, 더 복잡하지만 세밀한 제어가 가능합니다.

import 'package:flutter/material.dart';

class ExplicitAnimationExample extends StatefulWidget {
  @override
  _ExplicitAnimationExampleState createState() => _ExplicitAnimationExampleState();
}

class _ExplicitAnimationExampleState extends State<ExplicitAnimationExample>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

  @override
  void initState() {
    super.initState();

    _controller = AnimationController(
      duration: Duration(seconds: 2),
      vsync: this,
    );

    _animation = CurvedAnimation(
      parent: _controller,
      curve: Curves.elasticOut,
    );

    _animation = Tween<double>(begin: 0, end: 200).animate(_animation);

    _animation.addListener(() {
      setState(() {});
    });
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Container(
              width: _animation.value,
              height: _animation.value,
              color: Colors.blue,
            ),
            SizedBox(height: 20),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                ElevatedButton(
                  onPressed: () => _controller.forward(),
                  child: Text('시작'),
                ),
                SizedBox(width: 10),
                ElevatedButton(
                  onPressed: () => _controller.reverse(),
                  child: Text('역방향'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

AnimatedBuilder 사용하기

리빌드를 최적화하기 위해 AnimatedBuilder를 사용할 수 있습니다:

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Center(
      child: AnimatedBuilder(
        animation: _controller,
        builder: (context, child) {
          return Transform.rotate(
            angle: _controller.value * 2 * 3.14159,
            child: Container(
              width: 100 + (_controller.value * 100),
              height: 100 + (_controller.value * 100),
              color: Colors.blue,
              child: child, // 변경되지 않는 부분
            ),
          );
        },
        child: Center(
          child: Text(
            '회전',
            style: TextStyle(color: Colors.white),
          ),
        ),
      ),
    ),
  );
}

3. Hero 애니메이션

화면 간 전환 시 위젯을 부드럽게 이동시키는 애니메이션입니다.

// 첫 번째 화면
Hero(
  tag: 'logo', // 동일한 태그로 연결
  child: Image.asset('assets/logo.png', width: 100),
)

// 두 번째 화면
Hero(
  tag: 'logo', // 동일한 태그로 연결
  child: Image.asset('assets/logo.png', width: 300),
)

4. TweenAnimationBuilder

상태 없이도 애니메이션을 구현할 수 있는 편리한 위젯입니다:

TweenAnimationBuilder<double>(
  tween: Tween<double>(begin: 0, end: 200),
  duration: Duration(seconds: 2),
  builder: (context, value, child) {
    return Container(
      width: value,
      height: value,
      color: Colors.blue,
      child: child,
    );
  },
  child: Center(child: Text('크기 변화', style: TextStyle(color: Colors.white))),
)

5. 페이지 전환 애니메이션

커스텀 페이지 전환 애니메이션을 구현할 수 있습니다:

Navigator.push(
  context,
  PageRouteBuilder(
    pageBuilder: (context, animation, secondaryAnimation) => SecondPage(),
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      var begin = Offset(1.0, 0.0);
      var end = Offset.zero;
      var curve = Curves.easeInOut;

      var tween = Tween(begin: begin, end: end)
          .chain(CurveTween(curve: curve));

      return SlideTransition(
        position: animation.drive(tween),
        child: child,
      );
    },
    transitionDuration: Duration(milliseconds: 500),
  ),
);

6. Lottie 애니메이션

Adobe After Effects로 만든 고급 애니메이션을 사용할 수 있습니다:

// pubspec.yaml에 의존성 추가
// dependencies:
//   lottie: ^2.3.0

import 'package:lottie/lottie.dart';

Lottie.asset(
  'assets/animations/animation.json',
  width: 200,
  height: 200,
  fit: BoxFit.contain,
)

7. 애니메이션 성능 최적화 팁

  1. RepaintBoundary 사용: 애니메이션 위젯을 감싸 불필요한 리페인팅 방지

    RepaintBoundary(child: AnimatedWidget())
    
  2. setState 대신 AnimatedBuilder 사용: 전체 위젯 트리 리빌드 방지

  3. 불필요한 애니메이션 피하기: 화면 밖 위젯의 애니메이션은 비활성화

  4. DevTools로 성능 모니터링: flutter run --profile로 프로파일링

8. AnimationStatusListener 활용

애니메이션의 상태 변화를 감지하여 다른 동작 연결:

_animation.addStatusListener((status) {
  if (status == AnimationStatus.completed) {
    // 애니메이션 완료 시 동작
  } else if (status == AnimationStatus.dismissed) {
    // 애니메이션 초기화 시 동작
  }
});

결론

Flutter는 다양한 애니메이션 기법을 제공하며, 상황에 따라 적절한 방식을 선택할 수 있습니다:

  • 간단한 애니메이션: 암묵적 애니메이션 위젯 사용
  • 복잡한 제어가 필요한 경우: 명시적 애니메이션과 컨트롤러 사용
  • 화면 전환: Hero 애니메이션 또는 PageRouteBuilder 활용
  • 고급 애니메이션: Lottie 또는 CustomPainter 사용

애니메이션은 앱의 사용자 경험을 향상시키는 중요한 요소이지만, 적절한 사용이 중요합니다. 사용자 인터랙션에 자연스럽게 반응하고 상태 변화를 부드럽게 전달하는 데 초점을 맞추면 좋습니다.

results matching ""

    No results matching ""