Flutter 애니메이션

devkwon·2023년 2월 22일
0

Implicit Animation

공식문서
flutter의 애니메이션 라이브러리를 사용하는 방법. 보간법(interpolate)를 통해 old value와 new value에서 적절한 값을 구하고 이를 통해 적절한 애니메이션을 만들어준다. 공통적으로 Durtaion으로 애니메이션 시간을 설정하고, Curve 클래스를 활용하여 보간 비율을 설정하여 다양한 애니메이션으로 구현이 가능하다.

Curve

AnimatedAlign

정렬이 변경될 때 동작하는 애니메이션

AnimatedContainer

height, width, color 등 값 변경될 때 이에 대한 애니메이션을 만들어준다.

AnimatedDefaultTextStyle

TextStyle이 바뀔 때마다 동작하는 애니메이션

AnimatedScale

child의 스케일이 변경될 때 동작하는 애니메이션

AnimatedRotation

child에게 rotation이 발생되었을 때 동작하는 애니메이션

AnimatedSlide

offset 변화 값을 감지하여 그에 따른 애니메이션을 만들어준다.

AnimatedOpacity

투명도가 변경될 때 동작하는 애니메이션

AnimatedPadding

패딩 값의 변경이 일어날 때 동작하는 애니메이션

AnimatedPhysicalModel

borderRadius, elevation이 변경될 때 동작하는 애니메이션이다.

AnimatedTheme에 의해 색상을 독립적으로 애니메이션화 할 수 있는데. 만약 animateColor가 설정되어 있다면 color가 바뀔 때 독립적인 color 애니메이션이 동작한다. 아니라면 PhysicalModel 애니메이션 시작과 동시에 색상이 바로 변하게 된다.

AnimatedPositioned

Stack이 child일 때만 동작한다. 위치나 크기를 변경시키는 애니메이션을 구현할 수 있다.

AnimatedPositionedDirectional

child의 position이 변경되면 동작하는 애니메이션이다.

child가 Stack이어야한다.

child의 위치와 크기를 조절할 수 있다. 해당 애니메이션은 프레임과 레이아웃까지 새로 그리기 때문에 만약에 단순하게 위치만 이동하는 것이라면 프레임만 다시 그리는 SlideTransition이 더 좋은 방식이라고 공식 문서에 나와있다.

AnimatedTheme

color 등과 같은 테마가 변경될 때 동작하는 애니메이션이다.

AnimatedCrossFade

한 위젯이 사라지고 다른 위젯이 그 위로 나타날 때 페이드현상이 일어나는 애니메이션

firstChild: 처음 나타날 위젯
seondChild: firstChild가 페이드 아웃 후 나타날 위젯

만약 상태변화가 또 일어나서 다시 반대로 firstChild가 나와야한다면 자동으로 애니메이션 처리가 된다.

firstCurve, secondCurve로 first->second, second->first로 변경될 때 curve도 각각 따로 설정할 수 있다.

AnimatedSize

사이즈가 변경될 때 동작하는 애니메이션이다.

AnimatedSwitcher

AnimatedCrossFade와 마찬가지로 위젯 위에 다른 위젯이 나오는 경우 사용되는 애니메이션으로 transitionBuilder를 통해 Rotation, Fade 등 다양한 애니메이션으로 사용할 수 있다.

old child와 new child의 key와 parameter가 같으면 동작한다.

TweenAnimationBuilder<T extends Object?>

원하는 특정 값이 변경되었을 때 애니메이션을 실행시킬 수 있다.

TweenAnimationBuilder, BuildContext, Tween의 타입이 일치해야 한다.

Tween은 해당 위젯이 빌드되었을 때 begin 값에서 end 값까지 설정된 애니메이션이 실행된다.

TweenSequence을 통해 여러개의 Tween을 지정하여 순차적으로 실행할 수 있다.

onEnd 콜백을 이용하여 애니메이션이 종료되었을 때 특정한 이벤트들을 시작할 수 있다.

AnimationController

다양하고 복잡한 애니메이션을 구현하기 위해서 Implict Animation을 중첩하게 되면 관리하기가 어려워진다. 따라서 사용자가 원하는 애니메이션을 구현하기 위해 직접 애니메이션을 설정할 수 있게 해준다.

AnimationController를 사용하기 위해선 vsync argument가 필요하기 때문에 TickerProvider를 상속 받아야한다.

AnimationController.value

기본적으로 duration range가 0~1로 되어 있으며, 수정 가능하다.

AnimationController.status

forward: 0~1까지 한 번 진행
repeat: 반복하여 진행. 만약 reverse의 값이 true면 0~1 진행 후 다시 1~0으로 돌아오는 방식
stop: 멈춘 상태
complete: 완료된 상태

같이 애니메이션 상태가 존재한다.

한 번 생성되면 프레임 단위로 리소스를 계속 잡아먹기 때문에 메모리 leak를 없애기 위해 dispose를 해주는 것이 중요하다.

AnimationController.addStatusListener를 사용하면 애니메이션 상태가 변했을 때 콜백을 준다.

AnimationController.addListener를 사용하여 애니메이션에 변화가 생겼을 때 콜백을 준다.

class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
  double targetValue = 0.0;
  late AnimationController animationController;
  
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    animationController=AnimationController(vsync: this, duration: Duration(milliseconds: 2000));

    //animationController.forward();// animation 실행
    animationController.repeat(reverse: true); // animation 실행 0~1 -> 1~0 반복
    //animationController.stop(); animation 멈춤
    animationController.addStatusListener((status) {
      print(status);
    });
    animationController.addListener(() {
      setState(() {
        targetValue=animationController.value * 3.14;
      });
      //print(targetValue); // 0~1
    });
  }

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(

        child: Column(

          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Transform.rotate(angle: targetValue, child: const FlutterLogo(size: 400,),)
          ],
        ),
      ),
 // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

AnimationBuilder를 활용하여 animation 옵션에 만든 AnimationController를 넣고 특정 이벤트시 애니메이션을 동작하게 할 수 있다.

class _MyHomePageState extends State<MyHomePage>
  with SingleTickerProviderStateMixin {
bool isFav = false;
late AnimationController animationController;
late Animation colorAnimation;

@override
void initState() {
  // TODO: implement initState
  super.initState();
  animationController = AnimationController(
      vsync: this, duration: Duration(milliseconds: 2000));
  colorAnimation = ColorTween(begin: Colors.grey[400], end: Colors.red)
      .animate(animationController);

  animationController.addStatusListener((status) {
    if (status == AnimationStatus.completed) {
      setState(() {
        isFav = true;
      });
    }
    if (status == AnimationStatus.dismissed) {
      setState(() {
        isFav = false;
      });
    }
  });
}

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text(widget.title),
    ),
    body: Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          AnimatedBuilder(
            animation: animationController,
            builder: (BuildContext context, Widget? child) {
              return IconButton(
                  color: colorAnimation.value,
                  onPressed: () {
                    isFav
                        ? animationController.reverse()
                        : animationController.forward();
                  },
                  icon: const Icon(Icons.favorite));
            },
          ),
        ],
      ),
    ),
    // This trailing comma makes auto-formatting nicer for build methods.
  );
}
}

0개의 댓글