Flutter

플러터 애니메이션

hamiric 2024. 12. 19. 12:43

애니메이션이란?

애니메이션은 위젯의 속성 (위치, 크기, 색상 등등)을 시간에 따라 변경하여, 시각적인 변화를 부드럽게 보여주는 방식이다.

애니메이션은 사용자 경험을 향상시키는 중요한 요소로써, 사용자로 하여금 앱의 동작을 직관적으로 이해시키고, 몰입감을 높이는 데 사용된다.

 

결과적으로 UI를 움직이는 동작이기 때문에, StatefulWidget에서 사용된다.

 

 

애니메이션을 제어하는 속성들

애니메이션을 만들기에 앞서, 아래의 애니메이션들을 제어하는 속성들이 있다.

애니메이션 효과의 속도를 직접적으로 제어하는 Curve 와 애니메이션 시간을 제어하는 Duration 이다.

 

  • Curve

애니메이션은 기본적으로 0 ~ 1 까지의 진행되는 값을 기반으로 작동한다.

Curve 객체는, 0 ~ 1 사이의 진행속도를 변화시키는 것을 제어한다.

 

 

  • Duration

애니메이션이 진행되는 시간을 정의하는 객체이다.

애니메이션이 진행되는 0 ~ 1 을 진행하는데 걸리는 시간을 정의한다.

duration: const Duration(seconds: 2)

 

 

 

암시적 애니메이션 ( Implict Animation )

개발자가 직접 애니메이션의 세부 동작을 구현하지 않아도, 위젯의 특정 속성이 변경되면 자동으로 플러터가 애니메이션 효과를 적용시켜 주는 방식이다.

즉, 간단히 말하자면, 플러터에서 제공하는 애니메이션 위젯들이라고 생각하면 된다.

 

예시로는 다음과 같은 것들이 있다.

  • AnimatedAlign

정렬 속성이 변경되면, 애니메이션이 진행되는 위젯

AnimatedAlign(
  curve: Curves.easeIn,
  alignment: selected ? Alignment.topRight : Alignment.bottomLeft,
  duration: Duration(seconds: 1),
  child: Container(
    width: 50,
    height: 50,
    color: Colors.red,
  ),
),

 

  • AnimatedContainer

컨테이너 속성이 변경되면, 애니메이션이 진행되는 위젯

AnimatedContainer(
  alignment: selected ? Alignment.topRight : Alignment.bottomLeft,
  height: selected ? 200 : 100,
  width: selected ? 200 : 100,
  decoration: BoxDecoration(
    color: selected ? Colors.green : Colors.red,
    borderRadius: BorderRadius.circular(
      selected ? 0 : 20,
    ),
  ),
  padding: EdgeInsets.all(selected ? 10 : 0),
  curve: Curves.easeIn,
  duration: Duration(seconds: 1),
  child: Text('CHILD'),
)

 

  • AnimatedPositioned

위치 속성이 변경되면, 애니메이션이 진행되는 위젯 (Stack에서 사용됨)

AnimatedPositioned(
  left: selected ? 100 : 0,
  top: selected ? 100 : 0,
  curve: Curves.easeIn,
  duration: Duration(seconds: 1),
  child: Container(
    width: 50,
    height: 50,
    color: Colors.amber,
  ),
)

 

등등 ( 추가적인 것들은 아래의 참고자료 (공식문서)) 참고)

 

Hero 위젯

화면이 이동될때 사용되는 애니메이션으로써, 같은 태그의 Hero 위젯들은 페이지가 이동되었을시, 마치 연결된 것처럼 화면 전환시 애니메이션 효과가 작동한다.

 

밑의 각 페이지의 Hero위젯들은, 'sample-image'라는 태그로 묶여있다.

이렇게 될 경우 A <-> B 로 페이지가 전환될때, Hero 위젯들은 화면전환 애니메이션 효과가 적용되어 보이게 된다!

// Page A
Hero(
  tag: 'sample-image',
  child: SizedBox(
    width: 100,
    height: 100,
    child: Image.network(
      'https://picsum.photos/200/200',
      fit: BoxFit.cover,
    ),
  ),
)

// Page B
Hero(
  tag: 'sample-image',
  child: AspectRatio(
    aspectRatio: 1,
    child: Image.network(
      'https://picsum.photos/200/200',
      fit: BoxFit.cover,
    ),
  ),
)

 

 

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

개발자가 애니메이션의 세부 동작을 직접 구현하는 방식이다.

AnimationController 객체로 애니메이션의 세부 동작을 제어할 수 있다.

 

이때 주의해야 하는것은, State에 vsync 속성이 들어가야 한다는 점이다.

일반적인 방법으로는 넣을 수 없고, State에 with 키워드를 통해 TickerProvider 객체를 이용해 주어야 한다.

 

TickerProvier 객체에는 크게 두가지의 종류가 있는데, 내부 설명은 생략하고, 간단하게 사용처만 말하면

 

SingleTickerProviderStateMixin 은 한 State에 애니메이션 하나만 사용할때 사용하고,

TickerProviderStateMixin은 한 State에 2개 이상의 애니메이션이 사용될때 사용하면 된다.

 

Tween 객체로는 애니메이션의 시작과 끝 값을 정의한다.

즉, 일반적인 AnimationController는 0 ~ 1 사이로 애니메이션의 시작과 끝을 정의하기에,
Tween과 AnimationController가 결합되면, 각각 0 ~ 1 과 Tween의 begin, end 가 각각 매칭되어 작동되게끔 한다.

// 이미지가 위, 아래로 조금씩만 반복적으로 움직이는 애니메이션
class ExamAnimation extends StatefulWidget {
  const ExamAnimation({super.key});
  
  @override
  State<ExamAnimation> createState() => _ExamAnimationState();
}

class _ExamAnimationState extends State<ExamAnimation> with SingleTickerProviderStateMixin {
  final AnimationController animationController;
  final Animation<Offset> animation_offset;
  final CurvedAnimation curve;
  
  // animationController 정의, vsync와 duration 설정
  animationController = AnimationController(
    vsync: this,
    duration: const Duration(seconds: 2),
  );
  
  // animationController의 애니메이션 커브를 linear로 설정
  curve =
    CurvedAnimation(parent: animationController, curve: Curves.linear);
  
  // 실제 애니메이션 정의
  // Tween을 통해 begin에 Offset(0,-0.1), end에 Offset.zero를 매칭
  // curve와 animationController 연결
  animation_offset = Tween<Offset>(begin: const Offset(0, -0.1), end: Offset.zero)
    .animate(curve);
  
  
  @override
  void initState(){
    super.initState();
    // 애니메이션 시작
    animationController.forward();
    
    // 애니메이션에 Listner 추가
    fruitAnimationController.addStatusListener((state) {
      // 애니메이션이 완료되었을 경우 reverse
      if (state == AnimationStatus.completed) {
        fruitAnimationController.reverse();
      } 
      // 애니메이션이 초기상태이거나 시작되지 않았을 경우 forward
      else if (state == AnimationStatus.dismissed) {
        fruitAnimationController.forward();
      }
    });
  }
  
  @override
  void dispose(){
    super.dispose();
    animationController.dispose();
  }
  
  @override
  void build(){
    return SlideTransition(position: animation_offset, child: Image.asset(assetImage)));
  }
}

 

 

 

< 참고자료 >

 

Introduction to animations

How to perform animations in Flutter.

docs.flutter.dev

 

 

Animation and motion widgets

A catalog of Flutter's animation widgets.

docs.flutter.dev