🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

Flutter Animation 고급 패턴 - 슬라이드 1/15
A

AI Generated

2025. 10. 30. · 63 Views

Flutter Animation 고급 패턴

Flutter 애니메이션의 고급 패턴, 성능 최적화, 물리 시뮬레이션을 완벽하게 마스터합니다


카테고리:Flutter
언어:Dart
메인 태그:#Flutter
서브 태그:
#Animation#Advanced#Performance#Physics

들어가며

이 글에서는 Flutter Animation 고급 패턴에 대해 상세히 알아보겠습니다. 총 14가지 주요 개념을 다루며, 각각의 개념에 대한 설명과 실제 코드 예제를 함께 제공합니다.

목차

  1. 복수_Controller_동기화_패턴
  2. ValueNotifier_성능_최적화
  3. CustomClipper_Reveal_애니메이션
  4. AnimateTo_정밀_제어_패턴
  5. Ticker_프레임_단위_제어
  6. 드래그_제스처_인터랙션
  7. PageRouteBuilder_커스텀_전환
  8. Matrix4_3D_Transform
  9. RepaintBoundary_격리_최적화
  10. Interval_타이밍_분할_패턴
  11. SpringSimulation_물리_시뮬레이션
  12. GravitySimulation_중력_물리
  13. Lerp_커스텀_보간_함수
  14. PathMetrics_경로_애니메이션

1. 복수 Controller 동기화 패턴

개요

여러 AnimationController를 동기화하여 복잡한 순차 애니메이션을 구현합니다.

코드 예제

class SyncedAnimations with TickerProviderStateMixin {
  late AnimationController first;
  late AnimationController second;
  late AnimationController third;

  void initControllers(TickerProvider vsync) {
    first = AnimationController(
      vsync: vsync,
      duration: Duration(seconds: 1),
    );
    second = AnimationController(
      vsync: vsync,
      duration: Duration(milliseconds: 800),
    );
    third = AnimationController(
      vsync: vsync,
      duration: Duration(milliseconds: 600),
    );
  }

  Future<void> playSequence() async {
    await first.forward();
    await Future.wait([
      second.forward(),
      third.forward(),
    ]);
    await first.reverse();
  }
}

설명

await으로 순차 실행, Future.wait으로 병렬 실행을 조합하여 정교한 애니메이션 시퀀스를 만듭니다.


2. ValueNotifier 성능 최적화

개요

ValueNotifier와 ValueListenableBuilder로 필요한 부분만 리빌드하여 성능을 극대화합니다.

코드 예제

class PerformantWidget extends StatefulWidget {
  @override
  _PerformantWidgetState createState() =>
    _PerformantWidgetState();
}

class _PerformantWidgetState
    extends State<PerformantWidget>
    with SingleTickerProviderStateMixin {
  late AnimationController _ctrl;

  @override
  void initState() {
    super.initState();
    _ctrl = AnimationController(
      vsync: this,
      duration: Duration(seconds: 3),
    )..repeat(reverse: true);
  }

  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder<double>(
      valueListenable: _ctrl,
      builder: (_, value, child) {
        return Opacity(
          opacity: value,
          child: child,
        );
      },
      child: ExpensiveStaticWidget(),
    );
  }
}

설명

child는 한 번만 빌드되고 value만 변경되어 성능이 크게 개선됩니다.


3. CustomClipper Reveal 애니메이션

개요

CustomClipper를 활용하여 복잡한 Reveal 효과를 구현합니다.

코드 예제

class RevealClipper extends CustomClipper<Path> {
  final double revealPercent;

  RevealClipper({required this.revealPercent});

  @override
  Path getClip(Size size) {
    final path = Path();
    final revealHeight = size.height * revealPercent;

    path.addRect(Rect.fromLTWH(
      0,
      size.height - revealHeight,
      size.width,
      revealHeight,
    ));

    return path;
  }

  @override
  bool shouldReclip(RevealClipper old) =>
    old.revealPercent != revealPercent;
}

// 사용
AnimatedBuilder(
  animation: controller,
  builder: (_, child) {
    return ClipPath(
      clipper: RevealClipper(
        revealPercent: controller.value,
      ),
      child: Image.asset('assets/image.png'),
    );
  },
)

설명

아래에서 위로 이미지가 서서히 드러나는 Reveal 효과를 만듭니다.


4. AnimateTo 정밀 제어 패턴

개요

animateTo 메서드로 애니메이션의 목표값과 속도를 동적으로 제어합니다.

코드 예제

late AnimationController _ctrl;

void _animateToTarget(double target) {
  _ctrl.animateTo(
    target,
    duration: Duration(milliseconds: 800),
    curve: Curves.easeInOutCubic,
  );
}

// 상태 리스너로 자동 반복
_ctrl.addStatusListener((status) {
  if (status == AnimationStatus.completed) {
    _ctrl.animateTo(
      0.0,
      duration: Duration(seconds: 1),
      curve: Curves.easeOut,
    );
  } else if (status == AnimationStatus.dismissed) {
    _ctrl.animateTo(
      1.0,
      duration: Duration(seconds: 1),
      curve: Curves.easeIn,
    );
  }
});

설명

forward/reverse 대신 animateTo로 특정 값까지만 애니메이션하거나 방향을 동적으로 변경할 수 있습니다.


5. Ticker 프레임 단위 제어

개요

Ticker를 직접 사용하여 프레임 단위로 애니메이션을 제어합니다.

코드 예제

class TickerBasedWidget extends StatefulWidget {
  @override
  _TickerBasedWidgetState createState() =>
    _TickerBasedWidgetState();
}

class _TickerBasedWidgetState
    extends State<TickerBasedWidget>
    with SingleTickerProviderStateMixin {
  late Ticker _ticker;
  double _angle = 0.0;

  @override
  void initState() {
    super.initState();
    _ticker = createTicker((elapsed) {
      setState(() {
        _angle = (elapsed.inMilliseconds / 1000) * pi;
      });
    });
    _ticker.start();
  }

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

  @override
  Widget build(BuildContext context) {
    return Transform.rotate(
      angle: _angle,
      child: FlutterLogo(size: 100),
    );
  }
}

설명

Ticker는 프레임마다 elapsed 시간을 제공하여 시간 기반의 정밀한 애니메이션이 가능합니다.


6. 드래그 제스처 인터랙션

개요

사용자의 드래그 제스처에 반응하는 인터랙티브 애니메이션을 구현합니다.

코드 예제

class DraggableBox extends StatefulWidget {
  @override
  _DraggableBoxState createState() =>
    _DraggableBoxState();
}

class _DraggableBoxState extends State<DraggableBox>
    with SingleTickerProviderStateMixin {
  Offset _offset = Offset.zero;
  late AnimationController _snapCtrl;

  @override
  void initState() {
    super.initState();
    _snapCtrl = AnimationController(
      vsync: this,
      duration: Duration(milliseconds: 400),
    );
  }

  void _onPanEnd(DragEndDetails details) {
    final animation = Tween<Offset>(
      begin: _offset,
      end: Offset.zero,
    ).animate(CurvedAnimation(
      parent: _snapCtrl,
      curve: Curves.elasticOut,
    ));

    _snapCtrl.forward(from: 0).then((_) {
      setState(() => _offset = Offset.zero);
    });
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onPanUpdate: (details) {
        setState(() {
          _offset += details.delta;
        });
      },
      onPanEnd: _onPanEnd,
      child: Transform.translate(
        offset: _offset,
        child: Container(
          width: 100,
          height: 100,
          color: Colors.blue,
        ),
      ),
    );
  }
}

설명

드래그 중에는 즉시 반응하고, 놓으면 elasticOut으로 원점으로 돌아갑니다.


7. PageRouteBuilder 커스텀 전환

개요

PageRouteBuilder로 페이지 전환 애니메이션을 완전히 커스터마이징합니다.

코드 예제

class FadeSlidePageRoute extends PageRouteBuilder {
  final Widget page;

  FadeSlidePageRoute({required this.page})
    : super(
        pageBuilder: (context, animation, secondaryAnimation)
          => page,
        transitionsBuilder: (
          context,
          animation,
          secondaryAnimation,
          child,
        ) {
          const begin = Offset(0.0, 0.3);
          const end = Offset.zero;
          final slideTween = Tween(begin: begin, end: end);
          final fadeTween = Tween<double>(begin: 0.0, end: 1.0);

          final slideAnimation = animation.drive(
            slideTween.chain(
              CurveTween(curve: Curves.easeOut),
            ),
          );
          final fadeAnimation = animation.drive(fadeTween);

          return SlideTransition(
            position: slideAnimation,
            child: FadeTransition(
              opacity: fadeAnimation,
              child: child,
            ),
          );
        },
      );
}

// 사용
Navigator.push(
  context,
  FadeSlidePageRoute(page: NextPage()),
);

설명

슬라이드와 페이드를 결합한 부드러운 페이지 전환을 만듭니다.


8. Matrix4 3D Transform

개요

Matrix4로 3D 회전과 원근 효과를 구현합니다.

코드 예제

AnimatedBuilder(
  animation: controller,
  builder: (context, child) {
    final angle = controller.value * pi * 2;
    return Transform(
      transform: Matrix4.identity()
        ..setEntry(3, 2, 0.002)
        ..rotateX(angle * 0.3)
        ..rotateY(angle),
      alignment: Alignment.center,
      child: Container(
        width: 200,
        height: 300,
        decoration: BoxDecoration(
          gradient: LinearGradient(
            colors: [Colors.blue, Colors.purple],
          ),
          borderRadius: BorderRadius.circular(20),
          boxShadow: [
            BoxShadow(
              color: Colors.black38,
              blurRadius: 20,
              offset: Offset(0, 10),
            ),
          ],
        ),
      ),
    );
  },
)

설명

setEntry(3, 2, 0.002)로 원근감을 주고 X, Y축 동시 회전으로 3D 카드 효과를 만듭니다.


9. RepaintBoundary 격리 최적화

개요

RepaintBoundary로 리페인트 영역을 격리하여 성능을 최적화합니다.

코드 예제

Column(
  children: [
    RepaintBoundary(
      child: AnimatedBuilder(
        animation: controller,
        builder: (context, child) {
          return Transform.rotate(
            angle: controller.value * 2 * pi,
            child: FlutterLogo(size: 100),
          );
        },
      ),
    ),
    SizedBox(height: 20),
    ExpensiveStaticList(),
  ],
)

설명

애니메이션 위젯만 격리하여 나머지 UI는 리페인트되지 않아 성능이 향상됩니다.


10. Interval 타이밍 분할 패턴

개요

Interval로 하나의 controller를 시간대별로 분할하여 순차 애니메이션을 만듭니다.

코드 예제

final firstHalf = CurvedAnimation(
  parent: controller,
  curve: Interval(0.0, 0.5, curve: Curves.easeIn),
);

final secondHalf = CurvedAnimation(
  parent: controller,
  curve: Interval(0.5, 1.0, curve: Curves.easeOut),
);

Column(
  children: [
    ScaleTransition(
      scale: firstHalf,
      child: Box(color: Colors.red),
    ),
    SizedBox(height: 20),
    SlideTransition(
      position: Tween<Offset>(
        begin: Offset(1, 0),
        end: Offset.zero,
      ).animate(secondHalf),
      child: Box(color: Colors.blue),
    ),
  ],
)

설명

첫 번째는 00.5초, 두 번째는 0.51초에 애니메이션되어 순차적으로 실행됩니다.


11. SpringSimulation 물리 시뮬레이션

개요

스프링 물리 법칙을 시뮬레이션하여 자연스러운 애니메이션을 만듭니다.

코드 예제

void _runSpringAnimation() {
  final spring = SpringSimulation(
    SpringDescription(
      mass: 1.0,
      stiffness: 200.0,
      damping: 15.0,
    ),
    controller.value,
    1.0,
    0.0,
  );

  controller.animateWith(spring);
}

// 또는 내장 Curve 사용
final springCurve = SpringCurve();
final animation = CurvedAnimation(
  parent: controller,
  curve: springCurve,
);

설명

mass(질량), stiffness(강성), damping(감쇠)로 스프링의 물리적 특성을 조절합니다.


12. GravitySimulation 중력 물리

개요

중력 시뮬레이션으로 자유낙하 효과를 구현합니다.

코드 예제

void _dropWithGravity() {
  final gravity = GravitySimulation(
    9.8,
    controller.value,
    1.0,
    0.0,
  );

  controller.animateWith(gravity);
}

// 바운스 효과 추가
controller.addStatusListener((status) {
  if (status == AnimationStatus.completed) {
    final bounce = SpringSimulation(
      SpringDescription.withDampingRatio(
        mass: 1.0,
        stiffness: 300.0,
      ),
      1.0,
      0.8,
      -5.0,
    );
    controller.animateWith(bounce);
  }
});

설명

GravitySimulation으로 낙하하고, 바닥에 닿으면 SpringSimulation으로 튕겨 올라갑니다.


13. Lerp 커스텀 보간 함수

개요

커스텀 lerp 함수로 복잡한 값의 보간을 구현합니다.

코드 예제

class GradientTween extends Tween<Gradient> {
  GradientTween({Gradient? begin, Gradient? end})
    : super(begin: begin, end: end);

  @override
  Gradient lerp(double t) {
    if (begin is LinearGradient && end is LinearGradient) {
      final b = begin as LinearGradient;
      final e = end as LinearGradient;

      return LinearGradient(
        colors: List.generate(
          b.colors.length,
          (i) => Color.lerp(
            b.colors[i],
            e.colors[i],
            t,
          )!,
        ),
      );
    }
    return end!;
  }
}

// 사용
final gradientAnimation = GradientTween(
  begin: LinearGradient(
    colors: [Colors.blue, Colors.purple],
  ),
  end: LinearGradient(
    colors: [Colors.red, Colors.orange],
  ),
).animate(controller);

설명

Gradient처럼 복잡한 타입도 lerp 함수를 구현하여 부드럽게 보간할 수 있습니다.


14. PathMetrics 경로 애니메이션

개요

PathMetrics로 복잡한 경로를 따라 움직이는 애니메이션을 구현합니다.

코드 예제

class PathAnimationPainter extends CustomPainter {
  final double progress;
  final Path path;

  PathAnimationPainter({
    required this.progress,
    required this.path,
  });

  @override
  void paint(Canvas canvas, Size size) {
    final metrics = path.computeMetrics().first;
    final distance = metrics.length * progress;
    final tangent = metrics.getTangentForOffset(distance);

    if (tangent != null) {
      canvas.drawCircle(
        tangent.position,
        10,
        Paint()..color = Colors.red,
      );
    }
  }

  @override
  bool shouldRepaint(PathAnimationPainter old) =>
    old.progress != progress;
}

설명

computeMetrics로 경로의 길이를 계산하고, getTangentForOffset으로 특정 지점의 위치를 얻어 경로를 따라 움직입니다.


마치며

이번 글에서는 Flutter Animation 고급 패턴에 대해 알아보았습니다. 총 14가지 개념을 다루었으며, 각각의 사용법과 예제를 살펴보았습니다.

관련 태그

#Flutter #Animation #Advanced #Performance #Physics

#Flutter#Animation#Advanced#Performance#Physics

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.
이전
1/2다음