[Flutter] Animation Controller, AnimatedBuilder

keemeesuu·2023년 2월 21일
0

Flutter

목록 보기
4/5
post-custom-banner

애니메이션 위젯은 기본적으로 정해진 애니메이션을 제공해준다.(AnimatedOpacity 같은)
하지만 위젯 크기나 움직이는 애니메이션을 넣으려면 커스텀 애니메이션을 사용해야 한다.
그러기 위해 AnimationController가 필요하다.
AnimationController 는 애니메이션의 시간을 담당하는 클래스이며 애니메이션의 정보를 가지고 있다고 보면 된다.

아래 예제는 기존 위젯이 아닌 근본적으로 Animation을 컨트롤링 하는 방법이다.

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

class TestPage extends StatefulWidget {
  const TestPage({super.key});

  
  State<TestPage> createState() => _TestPageState();
}

class _TestPageState extends State<TestPage>
    with SingleTickerProviderStateMixin {
  // 2-1. Mixin 추가

  // 1. 변수 생성
  late final AnimationController _animationController;

  bool isSelected = false;

  void _onPressed() {
    // 3. 애니메이션 값 구하기
    if (isSelected) {
      isSelected = false;
      _animationController.reverse();
    } else {
      isSelected = true;
      _animationController.forward();
    }
  }

  
  void initState() {
    // 2-2. 초기화
    _animationController = AnimationController(
      vsync: this,
      lowerBound: 1,
      upperBound: 1.5,
      value: 1,
      duration: const Duration(milliseconds: 300),
    );

    // 4. Event Listener 추가해서 값이 변경될때 setState
    _animationController.addListener(() {
      print(_animationController.value);
      setState(() {});
    });

    super.initState();
  }

  
  void dispose() {
    _animationController.dispose();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          children: [
            const SizedBox(height: 150),
            CupertinoButton.filled(
              onPressed: _onPressed,
              child: const Text("Button"),
            ),
            const SizedBox(height: 100),
            Transform.scale(
              scale: _animationController.value,
              child: AnimatedOpacity(
                opacity: isSelected ? 1 : 0,
                duration: const Duration(milliseconds: 300),
                child: Container(
                  color: Colors.red,
                  width: 100,
                  height: 100,
                ),
              ),
            )
          ],
        ),
      ),
    );
  }
}

초기화 및 필수 속성인 vsync를 사용하려면 with SingleTickerProviderStateMixin 사용해야함.
vsync는 애니메이션 최적화 및 언제 재생할지 시간을 세어주는 등의 기능을 위해 필요하다.

addListener로 값이 바뀔때 setState를 실행시켜준다. 일종의 Trigger.

vsync: SingleTickerProviderStateMixin

what is vsync?

offscreen 애니메이션의 불필요한 리소스를 막는다고 한다. (공식문서 내용)
즉, 위젯이 안 보일 때는 애니메이션이 작동하지 않도록 하는것이다.

what is SingleTickerProviderStateMixin?

current tree가 활성화된 동안만 tick 하는 단일 ticker를 제공한다.
current tree가 활성화된 동안 : 위젯이 화면에 보일 때만
Ticker는 시계라고 생각하면 이해하기 쉽다. 매 초도, 매 밀리초도 아니고 애니메이션의 프레임마다 callback을 호출해준다.

vsync에 ticker
애니메이션이 ticker를 잡을 거고 애니메이션이 재생되어야 할 때가 오면 Ticker가 그걸 애니메이션에게 알려준다. Ticker는 그걸 매 프레임마다 할 것이다.

정리.

vsync는 애니메이션 재생을 도와주며 위젯이 위젯_트리에 있을 때만 Ticker를 유지해 준다는 것이다.
그리고 Ticker를 만들기 위해서는 SingleTickerProviderStateMixin 사용.

제일 중요한 두가지

  1. 애니메이션은 Ticker가 필요하다. 매 프레임마다 재생되어야 하니까 말이다.
  2. Ticer가 항상 활성화 상태이면 안되니까 위젯이 화면에 보일 때만 활성화 되도록 한다.


AnimatedBuilder

위의 소스와 다른점과 장점은
Event Listener와 setState를 사용하지 않아도 된다는 점이다.
animate하고 싶은것과 animation을 분리 할 수 있다.
기능적으로는 큰 차이는 없지만 AnimatedBuilder를 사용하는게 더 좋아보인다.

// 4. Event Listener 추가해서 값이 변경될때 setState
_animationController.addListener(() {
  print(_animationController.value);
  setState(() {});
});

이부분은 삭제한다.

Transform.scale(
  scale: _animationController.value,
  child: AnimatedOpacity(
    opacity: isSelected ? 1 : 0,
    duration: const Duration(milliseconds: 300),
    child: Container(
      color: Colors.red,
      width: 100,
      height: 100,
    ),
  ),
),

위의 build 소스코드는 아래와 같이 수정

AnimatedBuilder(
  animation: _animationController,
  builder: (context, child) {
    return Transform.scale(
      scale: _animationController.value,
      child: child,
    );
  },
  child: AnimatedOpacity(
    opacity: isSelected ? 1 : 0,
    duration: const Duration(milliseconds: 300),
    child: Container(
      color: Colors.red,
      width: 100,
      height: 100,
    ),
  ),
),
profile
개발관련 정리하고 기록하는 블로그 🦦
post-custom-banner

0개의 댓글