본문 바로가기
flutter/Package of the Week

Flutter[플러터] / animations 패키지 사용법 (화면 전환 효과, 애니메이션, 페이드, OpenContainer, SharedAxisTransition, FadeThroughTransition) animations (Flutter Widget of the Week)

by ch5c 2025. 7. 22.
반응형

package:animations

이 패키지에는 일반적으로 원하는 효과를 위한 미리 제작된 애니메이션이 포함되어 있습니다.

https://youtu.be/HHzAJdlEj1c


앱에서 다른 화면으로 넘어갈 때 우리는 어떠한 전환 효과를 넣어 주는가? Hero 위젯을 사용하여 특정한 위젯을 이동시키는 느낌을 줄 수도 있고 혹은 라우터 동작하는 부분을 FadeTransition으로 감싸서 페이드 효과를 먹여줄 수도 있을 것이다. 하지만 이러한 위젯들을 사용하여 만든 전환 효과는 너무 뻔하고 재미가 없는 경우가 대부분이다. 하지만 이 package:animations를 사용하면 Material motion 기반의 고급 전환 효과를 손쉽게 사용할 수 있다.

 

일단 사용하기 위해선 먼저 프로젝트의 pubspec.yaml 파일 안에 animations 패키지를 추가해야 할 것이다.

펍데브(pub.dev)에서 가져와서 프로젝트에 추가해 준다.

flutter pub add animations

pubspec.yaml 파일 안에 문제없이 들어갔다면 이제 바로 사용해 주면 되는데 이 animations 패키지는 다양한 전환 효과들을 위젯들로 제공해주고 있다. 그러한 전환 효과를 담은 위젯들은 아래와 같은데 

주요 위젯
위젯 설명
OpenContainer 화면 전환 시 부드러운 Container 변형 애니메이션을 적용하는 위젯 (Container Transform 패턴)
SharedAxisTransition X, Y, Z축 중 하나를 기준으로 화면 전환 시 공통 축을 따라 애니메이션이 발생
FadeThroughTransition 새 화면이 페이드 인되며 이전 화면이 페이드 아웃되는 애니메이션을 적용 (Fade + Scale 효과)
FadeScaleTransition 페이드 인/아웃과 동시에 스케일 변화가 함께 발생하는 애니메이션으로, 팝업 UI에 유용

이 위젯들에 대해 순차적으로 알아보겠다.

OpenContainer

첫 번째로 알아볼 위젯은 바로 OpenContainer이다. 이 OpenContainer 위젯은 Container Transform 애니메이션을 제공해 주는 위젯인데 이 Container Transform 애니메이션이란 아래와 같은 애니메이션을 말한다.

Container transform

위와 같이 특정 위젯을 클릭하면 그 위젯이 커지면서 다음 화면으로 자연스럽게 넘어가는 것을 볼 수 있다. 이러한 동작을 OpenContainer로 만들기 위해선 일단 그 파라미터를 기본적으로 알아야 하는데 OpenContainer의 파라미터는 아래와 같다.

속성명 타입 기본값 설명
closedColor Color Colors.white 닫힌 상태일 때 배경색
openColor Color Colors.white 열린 상태일 때 배경색
middleColor Color? Theme.of(context).canvasColor 전환 중 중간 단계에 사용할 색상
closedElevation double 1.0 닫힌 상태의 그림자 높이
openElevation double 4.0 열린 상태의 그림자 높이
closedShape ShapeBorder RoundedRectangleBorder(4.0) 닫힌 상태의 모양
openShape ShapeBorder RoundedRectangleBorder() 열린 상태의 모양
onClosed ClosedCallback<T?>? 닫힐 때 호출되는 콜백
closedBuilder CloseContainerBuilder 닫힌 상태일 때 표시할 위젯을 생성하는 함수
openBuilder OpenContainerBuilder<T> 열린 상태일 때 표시할 위젯을 생성하는 함수
tappable bool true 탭하여 열 수 있는지 여부
transitionDuration Duration 300ms 열고 닫을 때 애니메이션 지속 시간
transitionType ContainerTransitionType fade 전환 애니메이션의 종류
useRootNavigator bool false 루트 Navigator를 사용할지 여부
routeSettings RouteSettings? 열린 화면의 라우팅 설정 정보
clipBehavior Clip Clip.antiAlias 닫힌 위젯의 클리핑 방식

 

사용해 주기 위해선 가장 먼저 openBuilder 파라미터를 사용해줘야 한다. 이름에서 알 수 있다시피 openBuilder 파라미터에는 빌더함수를 넣어줘야 하는데 어떠한 동작이 들어가냐 하면 바로 화면 전환을 적용할, 그러니까 다음 페이지로 넘어가는 동작이 들어가게 된다.

그렇다면 아래와 같이 Navigator 메서드를 사용하여 다른 페이지로 넘겨야 한다고 생각할 수 있다.

openBuilder: (context, action) =>
    Navigator.of(context).push(MaterialPageRoute(builder: (context) {
      return PageTwo();
},)), // openBuilder 안에서는 사용 X,

하지만 이 콜백 함수인 openBuilder 안에서는 위와 같이 사용하면 오류가 나서 사용할 수 없는데 그 이유는 굳이 위와 같이 하지 않아도 그냥 다음 페이지로 연결이 가능하기 때문이다. 아래와 같이 해주면 된다.

openBuilder: (context, action) => PageTwo(),

이렇게 해주면 OpenContainer에서 내부적으로 Navigator.of(context).push를 호출하고 있기 때문에 다음 화면으로 넘어가지는 동작이 완료 되게 된다.

그다음은 또 다른 필수 파라미터인 closedBuilder를 사용해줘야 한다. 이 closedBuilder 파라미터 안에는 이제 확대가 되면서 다른 화면으로 전환이 될, 그니까 Container Transform 애니메이션이 적용될 위젯을 넣어주면 된다.

closedBuilder: (context, action) {
  return FloatingActionButton(
    onPressed: action,
    child: Icon(Icons.arrow_forward),
  );
},

나는 FloatingActionButton 위젯을 사용해 줬는데 당연하겠지만 어떠한 위젯이든 상관없다.

이제 애니메이션을 조작해 주면 되는데 transitionType 파라미터를 사용하여 원하는 전환 애니메이션의 종류를 설정해 주면 된다.

이 transitionType 파라미터는 ContainerTransitionType을 타입으로 갖고 있는데 바로 이 위젯에서 생성자를 뽑아 사용해 주면 된다.

transitionType: ContainerTransitionType.fade,

생성자는 fade fadeThrough, 이 두 개가 있는데 딱히 차이점은 없으니 그냥 fade를 사용해 주면 되며 또한 추가로 transitionDuration을 사용하여 동작되는 시간을 설정해 줄 수 있다.

근데 이 코드를 실행해 보면 UI가 좀 이상하게 나오는 모습을 볼 수 있는데

그 이유로는 내가 현재 사용하고 있는 자식 위젯이 FloatingActionButton이라서 그렇다. 이 위젯은 기본적으로 그림자가 먹여져 있고 래디우스가 먹여져 있어서 래디우스 부분에 OpenContainer의 기본 색상인 흰색이 보이고 그림자는 잘려서 어색한 모습으로 표시되고 있는 것인데 이를 해결하기 위해선 clipBehavior, closedColor, closedElevation 파라미터를 사용하여 값을 조절해줘야 한다.

OpenContainer(
  clipBehavior: Clip.none,
  closedColor: ColorScheme.of(context).surface,
  closedElevation: 0,
  transitionType: ContainerTransitionType.fadeThrough,
  openBuilder: (context, action) => PageTwo(),
  closedBuilder: (context, action) {
    return FloatingActionButton(
      onPressed: action,
      child: Icon(Icons.arrow_forward),
    );
  },
),

아래의 다트 패드를 실행하여 잘 동작이 되는지 확인해 보자.

 

 

그리고 아래의 다트 패드를 실행해 보면 알 수 있듯이 위치에 따라 내부적으로 어떤 방식으로 열리는지가 결정된다.

SharedAxisTransition

두 번째로 알아볼 위젯은 SharedAxisTransition이다. 이 위젯은 두 화면이 동일한 축(X, Y, Z)을 공유하면서 전환되는 애니메이션을 제공해 준다. 더 쉽게 설명하자면 아래의 동작 예제를 봐보자.

위의 예제에서 진행되고 있는 애니메이션은 아래와 같다.

  1. 온보딩 플로우가 X축을 따라 전환
  2. 스텝퍼(Stepper)가 Y축을 따라 전환
  3. 상위-하위 내비게이션이 Z축을 따라 전환

가장 먼저 온보딩 시에 X축을 따라 전환되게 하는 예제를 봐볼 것인데 SharedAxisTransition 은 단순한 애니메이션 위젯이라  OpenContainer와 다르게 자체적으로 상태를 바꿀 수 없다.

즉 상태 전환, 라우터 기능은 Navigator 같은 위젯을 사용해야 한다는 말인데 일단 파라미터를 알아보고 진행하겠다.

속성명 타입 기본값 설명
animation Animation<double> child 위젯의 진입 및 퇴장을 제어하는 애니메이션
secondaryAnimation Animation<double> 다른 화면이 위에 push될 때의 child 애니메이션
transitionType SharedAxisTransitionType 전환에 사용할 축 (X, Y, Z 중 선택)
fillColor Color? Theme.of(context).canvasColor 전환 중 배경에 사용할 색상
child Widget? 전환 대상이 되는 위젯

 

속성표를 보면 알겠지만 animation에 들어갈 애니메이션이 필요한데 그를 위해서 PageRouteBuilder를 사용하여 인자값을 가져와 주겠다.

ElevatedButton(
  onPressed: () {
    Navigator.of(context).push(PageRouteBuilder(),);
  },
  child: Text('Previous'),
),

사용은 ElevatedButton 위젯 안에서 해줬는데 보면 알겠지만 Navigator을 사용해주고 있는 모습이다.

이제 PageRouterBuilder 위젯의 안을 채워주면 될 것이다.

Navigator.of(context).push(PageRouteBuilder(
  pageBuilder: (context, animation, secondaryAnimation) => PreviousPage(),
),);

가장 먼저 필수 파라미터인 pageBuilder에 우리가 이동시킬 페이지를 연결해 준다.

그다음은 이제 드디어 SharedAxisTransition을 사용해 줄 차례인데 파라미터로 animation, secondaryAnimation, child를 받는 만큼 그 인자값을 다 주는 transitionBuilder 파라미터를 사용해 주면 된다.

transitionsBuilder: (context, animation, secondaryAnimation, child) {
  return SharedAxisTransition(
    animation: animation,
    secondaryAnimation: secondaryAnimation,
    child: child,
  );
},

자 이제 여기서 우리는 전환에 사용할 축(X, Y, Z)을 고를 수 있게 된다. 바로 transitionType 파라미터를 통해서 말이다.

사실 거창하게 축이라 했지만 실상은 그냥 어디로 애니메이션 될지 정하는 거다.

사용하는 방법은 SharedAxisTransitionType 위젯에서 생성자를 불러내서 사용해 주면 된다.

transitionType: SharedAxisTransitionType.horizontal,

지금 예제로 온보딩 페이지를 들고 있으니 근본 넘치게 옆으로 스와이프 되는 것 같은, X축(horizontal)을 사용해 주겠다.

튜토리얼 같은 동작을 진행할 땐 Y축(vertical)으로 사용해 주면 되고 뭔가 튀어나오는 동작을 해주려면 Z축(scaled)을 사용해 주면 된다. (개인적으론 난 scaled가 가장 마음에 든다.)

이러면 동작 끝이다. 근데 이제 온보딩 페이지 하면 보통 양 옆으로 버튼이 두 개가 아닌가 그렇기 때문에 이 PageRouterBuilder는 따로 위젯으로 빼놔서 재활용시키면 더 쉽게 사용할 수 있다.

 

다만 안타깝게도 SharedAxisTransition의 동작의 방향을 바꾸는 것을 불가능하다. 실행해 보면 둘 다 공통적으로 오른쪽에서 왼쪽으로 이동하는 애니메이션을 보여주고 있는 것도 그 이유다.

FadeThroughTransition

그다음은 FadeThroughTransition 위젯이다.

이 위젯은 두 화면이 교차하면서 자연스럽게 페이드 인/아웃되는 애니메이션을 제공해 주는데 이 전환 방식으로 콘텐츠 교체를 자연스럽게 해 줄 수 있다. 

속성명 타입 기본값 설명
animation Animation<double> child 위젯의 진입 및 퇴장을 제어하는 애니메이션
secondaryAnimation Animation<double> 새 화면이 push될 때 기존 child의 전환을 제어하는 애니메이션
fillColor Color? Theme.of(context).canvasColor 전환 중 배경으로 사용할 색상
child Widget? 전환 대상이 되는 위젯

이 위젯도 SharedAxisTransition과 마찬가지로 직접적으로 라우팅 하는 동작이 없기 때문에 PageRouterBuilder같은 위젯가 연계해서 사용해줘야 한다. 사용하는 방법은 훨씬 쉽다.

ElevatedButton(
  onPressed: () {
    Navigator.of(context).push(
      PageRouteBuilder(
        pageBuilder: (context, animation, secondaryAnimation) => NextPage(),
        transitionsBuilder: (context, animation, secondaryAnimation, child,) {
          return FadeThroughTransition(
            animation: animation,
            secondaryAnimation: secondaryAnimation,
            child: child,
          );
        },
      ),
    );
  },
  child: Text('Next'),
),

그냥 바로 PageRouteBuilder transitionsBuilder 파라미터에서 필수 값인 animation, secondaryAnimation child를 가져와주면 된다. 근데 이것을 실행해 보면 알겠지만 진짜 그냥 페이드다. 그래서 사실 복잡한 전환이 아닌 경우에는 이 package:animations까지 쓰지 않아도 기본 위젯인 FadeTransition으로도 충분히 구현가능할 것이다.

 

그래도 다른 점이 있다면 그냥 Fade 동작만 하고 끝이 아니라 Material motion 기반의 페이드 아웃 + 축소, 새 위젯은 페이드 인 + 확대 동작을 해주기 때문에 좀 더 애니메이션이 풍성해 보이긴 한다.

FadeScaleTransition

마지막으로 알아볼 위젯은 FadeScaleTransition이다. 이 위젯은 SharedAxisTransition SharedAxisTransitionType.scaled 타입과 FadeThroughTransition 위젯의 페이드 동작을 합친듯한 위젯으로 서서히 나타나면서 점점 커지는 (scale) 애니메이션을 제공해 준다.

보통 팝업, 다이얼로그, 메뉴, 카드 같은 위젯의 등장 효과에 많이 사용된다.

속성명 타입 기본값 설명
animation Animation<double> child의 진입 및 퇴장을 제어하는 애니메이션
child Widget? 전환 대상이 되는 위젯

 

속성표만 봐도 알 수 있다시피 파멸적으로 사용하기 쉬운데 다만 보통 화면에 '띄우는' 동작을 하는 UI에 많이 적용하는 위젯이기 때문에 PageRouterBuilder를 사용하는 것은 가능하나 적합하지 않을 수 있다. 그래서 가장 많이 적용하는 다이얼로그에 사용하는 방법을 봐보자.

먼저 다이얼로그를 호출해줘야 하는데 흔히 사용하는 showDialog가 아닌 showGeneralDialog 함수를 사용해 줄 것이다.

그 이유로는 showDialog는 자체적인 애니메이션 시스템을 가지고 있어서 FadeScaleTransition의 애니메이션이 적용되지 않을 가능성이 크기 때문이다.

ElevatedButton(
  onPressed: () {
    showGeneralDialog(
      context: context,
      pageBuilder: (context, animation, secondaryAnimation) {
        return DatePickerDialog(
          firstDate: DateTime(2000),
          lastDate: DateTime.now(),
        );
      },
    );
  },
  child: Text('showDialog'),
),

일단 기본적인 다이얼로그를 사용해 주면 된다. 사용해 보면 그냥 버튼을 누르는 즉시 바로 다이얼로그가 '뿅' 하고 나오는 모습을 볼 수 있다.

이제 여기에서 다른 package:animations 위젯들처럼 똑같이 transitionBuilder 안에서 사용해 주면 된다.

transitionBuilder: (context, animation, secondaryAnimation, child) {
  return FadeScaleTransition(
    animation: animation,
    child: child,
  );
},

이러면 끝이다. 다만 showGeneralDialog 사용했기 때문에 기본적으로 barrierDismissible 속성이 꺼져있는데 이거 true로 활성시키기 위해선 barrierLabel도 스트링을 먹여놔야 한다. 안 그럼 오류 뜬다.

barrierLabel: 'DatePickerDialog',
barrierDismissible: true,

 

 

이렇게 package:animations의 4가지 전환 효과를 제공하는 위젯들에 대해서 알아보았다. 이런 거 막상 또 직접 제작하려고 하면 귀찮고 결과물도 별로 안 좋기 마련인데 이참에 이 패키지를 한번 본인의 앱에 적용시켜 보는 것은 어떨까? 퍼블리셔가 심지어 flutter.dev라 업데이트가 끊길 일도 없을 것이다. 암튼 도움이 되었길 바라며 마치겠다.

반응형