애니메이션 컨트롤러에 대해 설명해주세요.

질문

Flutter에서 애니메이션 컨트롤러에 대해 설명해주세요.

답변

Flutter에서 AnimationController는 애니메이션의 진행을 제어하는 핵심 클래스입니다. 모든 명시적(explicit) 애니메이션은 AnimationController를 사용하여 제어됩니다. 이는 애니메이션의 시작, 정지, 반복, 방향 전환 등을 다룹니다.

AnimationController의 기본 개념

AnimationController는 0.0에서 1.0 사이의 값을 생성하는 특수한 Animation<double> 객체입니다. 이 값은 시간에 따라 선형적으로 증가하며, 애니메이션의 진행 상태를 나타냅니다:

  • 0.0: 애니메이션의 시작
  • 1.0: 애니메이션의 끝

이 값은 다른 애니메이션 객체(Tween과 결합된)의 입력으로 사용되어 실제 애니메이션 효과를 만듭니다.

AnimationController 생성하기

class MyAnimationWidget extends StatefulWidget {
  @override
  _MyAnimationWidgetState createState() => _MyAnimationWidgetState();
}

class _MyAnimationWidgetState extends State<MyAnimationWidget>
    with SingleTickerProviderStateMixin {  // 필수 믹스인

  late AnimationController _controller;

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

    // 컨트롤러 초기화
    _controller = AnimationController(
      duration: Duration(seconds: 2),  // 애니메이션 지속 시간
      vsync: this,  // 틱 제공자
    );
  }

  @override
  void dispose() {
    _controller.dispose();  // 리소스 해제
    super.dispose();
  }

  // 위젯 빌드...
}

TickerProvider의 역할

vsync 매개변수는 TickerProvider를 필요로 합니다. 이는 애니메이션이 화면에 표시될 때만 틱(tick)을 생성하게 하여, 배터리와 CPU 리소스를 절약합니다. 일반적으로 애니메이션을 포함하는 State 클래스에 다음 믹스인 중 하나를 추가합니다:

  • SingleTickerProviderStateMixin: 단일 애니메이션 컨트롤러를 사용할 때
  • TickerProviderStateMixin: 여러 애니메이션 컨트롤러를 사용할 때

AnimationController의 주요 속성

  1. value: 현재 애니메이션 값(0.0~1.0)
  2. duration: 애니메이션의 지속 시간
  3. status: 현재 애니메이션 상태(forward, reverse, completed, dismissed)
  4. lowerBound: 애니메이션의 최소값(기본값 0.0)
  5. upperBound: 애니메이션의 최대값(기본값 1.0)
  6. reverseDuration: 역방향 애니메이션의 지속 시간(별도 설정 가능)

사용자 정의 범위

기본적으로 AnimationController는 0.0에서 1.0 사이의 값을 생성하지만, 다른 범위를 지정할 수도 있습니다:

AnimationController(
  duration: Duration(seconds: 2),
  vsync: this,
  lowerBound: -1.0,  // 최소값
  upperBound: 2.0,   // 최대값
);

AnimationController의 주요 메서드

1. 애니메이션 제어 메서드

// 애니메이션 시작 (0.0 -> 1.0)
_controller.forward();

// 애니메이션 역방향 실행 (1.0 -> 0.0)
_controller.reverse();

// 애니메이션 정지
_controller.stop();

// 애니메이션 초기화 (값을 lowerBound로 설정)
_controller.reset();

// 애니메이션 반복 (무한 반복)
_controller.repeat();

// 애니메이션 반복 (역방향 포함)
_controller.repeat(reverse: true);

// 특정 값으로 애니메이션 진행
_controller.animateTo(0.5);

// 특정 값으로 역방향 애니메이션 진행
_controller.animateBack(0.2);

2. 상태 확인 메서드

// 애니메이션이 진행 중인지 확인
if (_controller.isAnimating) {
  // 애니메이션 진행 중 로직
}

// 애니메이션 상태 확인
if (_controller.status == AnimationStatus.completed) {
  // 애니메이션 완료 시 로직
}

// 현재 값 확인
double currentValue = _controller.value;

애니메이션 상태 리스닝

AnimationController의 상태 변화를 감지하고 대응하기 위해 리스너를 추가할 수 있습니다:

1. 값 변경 리스너

_controller.addListener(() {
  // 컨트롤러 값이 변경될 때마다 호출됨
  print('현재 애니메이션 값: ${_controller.value}');
  setState(() {
    // 필요한 경우 UI 업데이트
  });
});

2. 상태 변경 리스너

_controller.addStatusListener((status) {
  if (status == AnimationStatus.completed) {
    // 애니메이션 완료 시
    print('애니메이션 완료');
  } else if (status == AnimationStatus.dismissed) {
    // 애니메이션이 초기 상태로 돌아왔을 때
    print('애니메이션 초기화');
  } else if (status == AnimationStatus.forward) {
    // 정방향 애니메이션 중
    print('정방향 애니메이션 중');
  } else if (status == AnimationStatus.reverse) {
    // 역방향 애니메이션 중
    print('역방향 애니메이션 중');
  }
});

AnimationController 사용 사례

1. 기본 애니메이션 구현

class BasicAnimation extends StatefulWidget {
  @override
  _BasicAnimationState createState() => _BasicAnimationState();
}

class _BasicAnimationState extends State<BasicAnimation>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

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

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

    // Tween과 결합하여 실제 애니메이션 생성
    _animation = Tween<double>(begin: 0, end: 200).animate(_controller);

    // 애니메이션 시작
    _controller.forward();
  }

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

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _controller,
      builder: (context, child) {
        return Container(
          width: _animation.value,
          height: _animation.value,
          color: Colors.blue,
        );
      },
    );
  }
}

2. 무한 반복 애니메이션

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

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

  _animation = Tween<double>(begin: 0, end: 2 * pi).animate(_controller);

  // 무한 반복
  _controller.repeat();
}

@override
Widget build(BuildContext context) {
  return AnimatedBuilder(
    animation: _controller,
    builder: (context, child) {
      return Transform.rotate(
        angle: _animation.value,
        child: FlutterLogo(size: 100),
      );
    },
  );
}

3. 애니메이션 체이닝 (연속 애니메이션)

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

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

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

  // 애니메이션 완료 시 역방향 실행
  _controller.addStatusListener((status) {
    if (status == AnimationStatus.completed) {
      _controller.reverse();
    } else if (status == AnimationStatus.dismissed) {
      _controller.forward();
    }
  });

  _controller.forward();
}

4. 애니메이션 시작/정지 제어

FloatingActionButton(
  onPressed: () {
    if (_controller.isAnimating) {
      _controller.stop();
    } else {
      if (_controller.status == AnimationStatus.forward ||
          _controller.status == AnimationStatus.completed) {
        _controller.reverse();
      } else {
        _controller.forward();
      }
    }
  },
  child: Icon(
    _controller.isAnimating ? Icons.pause : Icons.play_arrow,
  ),
)

5. 복수 애니메이션 제어하기

class MultipleAnimations extends StatefulWidget {
  @override
  _MultipleAnimationsState createState() => _MultipleAnimationsState();
}

class _MultipleAnimationsState extends State<MultipleAnimations>
    with TickerProviderStateMixin {  // 여러 컨트롤러용 믹스인

  late AnimationController _sizeController;
  late AnimationController _rotationController;
  late Animation<double> _sizeAnimation;
  late Animation<double> _rotationAnimation;

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

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

    _rotationController = AnimationController(
      duration: Duration(milliseconds: 500),
      vsync: this,
    );

    _sizeAnimation = Tween<double>(begin: 50, end: 200).animate(_sizeController);
    _rotationAnimation = Tween<double>(begin: 0, end: 2 * pi).animate(_rotationController);

    _sizeController.forward();
    _rotationController.repeat();
  }

  @override
  void dispose() {
    _sizeController.dispose();
    _rotationController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: Listenable.merge([_sizeController, _rotationController]),
      builder: (context, child) {
        return Transform.rotate(
          angle: _rotationAnimation.value,
          child: Container(
            width: _sizeAnimation.value,
            height: _sizeAnimation.value,
            color: Colors.blue,
          ),
        );
      },
    );
  }
}

애니메이션 커브 적용하기

애니메이션의 진행 방식을 제어하기 위해 CurvedAnimation을 사용할 수 있습니다:

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

// 커브 적용
final curvedAnimation = CurvedAnimation(
  parent: _controller,
  curve: Curves.elasticOut,  // 바운스 효과
  reverseCurve: Curves.easeIn,  // 역방향에 대한 별도 커브
);

// Tween과 결합
_animation = Tween<double>(begin: 0, end: 200).animate(curvedAnimation);

주요 커브 종류:

  • Curves.linear: 일정한 속도
  • Curves.easeIn: 느리게 시작해서 빨라짐
  • Curves.easeOut: 빠르게 시작해서 느려짐
  • Curves.easeInOut: 느리게 시작하고 빨라졌다가 다시 느려짐
  • Curves.elasticIn, Curves.elasticOut: 탄성 효과
  • Curves.bounceIn, Curves.bounceOut: 통통 튀는 효과
  • Curves.decelerate: 감속 효과

애니메이션 구간 제어하기

Interval 클래스를 사용하면 애니메이션의 특정 구간에서만 실행되도록 할 수 있습니다:

// 애니메이션의 20%에서 80% 구간에서만 실행
final intervalAnimation = CurvedAnimation(
  parent: _controller,
  curve: Interval(0.2, 0.8, curve: Curves.easeInOut),
);

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

커스텀 애니메이션 컨트롤러

특정 요구사항에 맞게 애니메이션 컨트롤러를 확장할 수도 있습니다:

class CustomAnimationController extends AnimationController {
  CustomAnimationController({
    required Duration duration,
    required TickerProvider vsync,
  }) : super(duration: duration, vsync: vsync);

  // 사용자 정의 메서드
  void playWithDelay({required Duration delay}) {
    Future.delayed(delay, () {
      this.forward();
    });
  }

  // 특정 구간만 실행
  void animateSegment(double start, double end) {
    assert(start >= 0.0 && start <= 1.0);
    assert(end >= 0.0 && end <= 1.0);
    assert(start <= end);

    this.value = start;
    this.animateTo(end);
  }
}

고급 애니메이션 제어 기술

1. 시뮬레이션 기반 애니메이션

물리적 동작을 시뮬레이션하기 위해 AnimationController.animateWith()를 사용할 수 있습니다:

// 스프링 효과
final SpringSimulation simulation = SpringSimulation(
  SpringDescription(
    mass: 1.0,
    stiffness: 500.0,
    damping: 10.0,
  ),
  _controller.value,  // 시작 위치
  1.0,                // 목표 위치
  0.0,                // 시작 속도
);

_controller.animateWith(simulation);

// 마찰 효과
final FrictionSimulation frictionSimulation = FrictionSimulation(
  0.5,                // 마찰 계수
  _controller.value,  // 시작 위치
  2.0,                // 시작 속도
);

_controller.animateWith(frictionSimulation);

2. 사용자 제스처로 애니메이션 제어

사용자의 드래그 제스처에 따라 애니메이션 값을 직접 제어할 수 있습니다:

GestureDetector(
  onHorizontalDragUpdate: (details) {
    // 드래그 거리에 따라 애니메이션 값 설정
    _controller.value += details.primaryDelta! / screenWidth;
  },
  onHorizontalDragEnd: (details) {
    // 속도에 따라 애니메이션 완료 또는 초기화
    if (details.primaryVelocity! > 0) {
      _controller.forward();
    } else {
      _controller.reverse();
    }
  },
  child: AnimatedBuilder(
    animation: _controller,
    builder: (context, child) {
      // 애니메이션 위젯 빌드
    },
  ),
)

성능 최적화 팁

  1. 애니메이션 컨트롤러 재사용: 가능한 경우 여러 애니메이션에 대해 하나의 컨트롤러를 재사용합니다.

  2. 리소스 해제: dispose() 메서드에서 반드시 컨트롤러를 해제하여 메모리 누수를 방지합니다.

  3. 상태 리스너 관리: 필요 없는 리스너는 제거합니다.

    // 리스너 참조 저장
    void _handleAnimationChanged() {
      setState(() {});
    }
    
    _controller.addListener(_handleAnimationChanged);
    
    // 나중에 리스너 제거
    _controller.removeListener(_handleAnimationChanged);
    
  4. AnimatedBuilder 최적화: 자주 변경되지 않는 위젯은 builder 외부에 배치합니다.

  5. 이중 애니메이션 방지: 불필요하게 여러 개의 애니메이션이 동시에 실행되지 않도록 합니다.

결론

AnimationController는 Flutter에서 명시적 애니메이션을 제어하는 핵심 클래스입니다. 애니메이션의 시작, 정지, 반복, 방향 전환 및 진행 상태 감지 등 다양한 기능을 제공합니다. Tween, CurvedAnimation, Interval 등과 결합하여 복잡하고 세련된 애니메이션 효과를 만들 수 있습니다.

적절한 애니메이션 컨트롤러 사용은 사용자 경험을 향상시키는 부드럽고 반응성 있는 인터페이스를 만드는 데 필수적입니다. 또한 리소스 관리와 성능 최적화에 주의하여 효율적인 애니메이션을 구현하는 것이 중요합니다.

results matching ""

    No results matching ""