개발노트

70. [Flutter] animations 패키지로 Page 전환 효과주기(with OpenContainer,SharedAxisTransition,FadeThroughTransition) 본문

앱 개발/Flutter

70. [Flutter] animations 패키지로 Page 전환 효과주기(with OpenContainer,SharedAxisTransition,FadeThroughTransition)

mroh1226 2024. 2. 22. 18:31
반응형

animations 

Flutter 애니메이션을 관리하고 제어하는 데 도움이 되는 다양한 패키지가 있습니다. 그 중 하나가 animations 패키지입니다. 이 패키지는 다양한 애니메이션 효과와 전환을 구현하는 데 사용될 수 있습니다. animations 패키지는 Flutter의 기본적인 애니메이션을 보완하고 확장하여 더 많은 제어와 유연성을 제공합니다.

animations 패키지의 주요 기능은 다음과 같습니다:

  1. 다양한 애니메이션 효과 제공: 패키지에는 페이드 인/아웃, 슬라이드, 크기 조절 등 다양한 애니메이션 효과를 구현하는 위젯이 포함되어 있습니다.
  2. 전환 애니메이션 제공: 페이지 전환 애니메이션, 위젯 간의 전환 효과 등을 구현할 수 있습니다. 이를 통해 사용자 경험을 향상시키고 애플리케이션의 전체적인 외관을 개선할 수 있습니다.
  3. 제공되는 위젯들이 간단하고 쉽게 사용 가능: 대부분의 애니메이션 위젯은 기존의 Flutter 위젯과 유사하게 작동하며, 일반적인 방식으로 구성하고 제어할 수 있습니다.

animations 패키지에서 제공하는 위젯은 대표적으로 4가지가 있습니다.

  1. Container transform
  2. Shared axis
  3. Fade through
  4. Fade

아래 링크에서 animations 패키지를 설치하고 대표적인 3가지 전환 위젯을 구현해봅니다.

 

- animation 패키지 설치링크: https://pub.dev/packages/animations

 

animations | Flutter package

Fancy pre-built animations that can easily be integrated into any Flutter application.

pub.dev


OpenContainer 위젯

OpenContainer를 사용하면 화면이 전환되는 동안 텍스트, 이미지 및 기타 콘텐츠가 부드럽게 확장되거나 축소되는 등의 애니메이션 효과를 적용할 수 있습니다. 이는 사용자가 새로운 콘텐츠로 이동하는 과정에서 부드럽고 자연스러운 경험을 제공합니다.

(좌)ListTile에서 사용 (우)GridView 에서 사용

OpenContainer 위젯 예제소스
import 'package:animations/animations.dart'; // animations 패키지를 import

import 'package:flutter/material.dart'; // 필요한 머티리얼 위젯 import

// ContainerTransformScreen 클래스 선언
class ContainerTransformScreen extends StatefulWidget {
  static String routeName = "containertrans";
  static String routeURL = "containertrans";
  const ContainerTransformScreen({super.key});

  @override
  State<ContainerTransformScreen> createState() =>
      _ContainerTransformScreenState();
}

// ContainerTransformScreen의 State 클래스 선언
class _ContainerTransformScreenState extends State<ContainerTransformScreen> {
  bool _isGrid = false; // 그리드 뷰와 리스트 뷰 전환을 위한 상태 변수

  // 그리드 뷰와 리스트 뷰를 전환하는 함수
  void _toggleGrid() {
    setState(() {
      _isGrid = !_isGrid;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        centerTitle: true,
        title: const Text("Container Transform"), // 앱바 타이틀
        actions: [
          IconButton(
            onPressed: _toggleGrid, // 그리드 뷰와 리스트 뷰 전환을 위한 아이콘 버튼
            icon: const Icon(Icons.grid_4x4), // 그리드 아이콘
          ),
        ],
      ),
      body: _isGrid
          ? GridView.builder(
              itemCount: 13, // 그리드 뷰 아이템 개수
              gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 3, // 그리드 열 개수
                mainAxisSpacing: 8, // 메인 축 간격
                crossAxisSpacing: 8, // 교차 축 간격
                childAspectRatio: 1 / 1.5, // 아이템 종횡비
              ),
              itemBuilder: (context, index) => OpenContainer(
                transitionDuration: const Duration(milliseconds: 400), // 전환 지속 시간
                closedBuilder: (context, action) => Column(
                  children: [
                    Image.asset(
                      "assets/movies/${index + 1}.jpg", // 영화 이미지
                      width: 120, // 이미지 너비
                    ),
                    const Text("MovieName"), // 영화 이름
                  ],
                ),
                openBuilder: (context, action) =>
                    DetailScreen(imgIndex: index), // 영화 상세 화면
              ),
            )
          : ListView.separated(
              itemBuilder: (context, index) => OpenContainer(
                closedElevation: 0, // 닫힌 상태의 고도
                openElevation: 0, // 열린 상태의 고도
                transitionDuration: const Duration(milliseconds: 700), // 전환 지속 시간
                openBuilder: (context, action) =>
                    DetailScreen(imgIndex: index), //열었을 때 영화 상세 화면
                closedBuilder: (context, action) => ListTile(	//닫았을 때 ListView, ListTile 화면
                  leading: Container(
                    width: 50,
                    height: 50,
                    decoration: BoxDecoration(
                      shape: BoxShape.circle,
                      image: DecorationImage(
                        fit: BoxFit.cover,
                        image: AssetImage("assets/movies/${index + 1}.jpg"),
                      ),
                    ),
                  ),
                  title: const Text("MovieName"), // 영화 이름
                  subtitle: const Text("2024.02.21"), // 영화 출시일
                  trailing: const Icon(Icons.arrow_forward_ios), // 아이콘
                ),
              ),
              separatorBuilder: (context, index) => const SizedBox(
                height: 20, // 분리기 높이
              ),
              itemCount: 13, // 리스트 뷰 아이템 개수
            ),
    );
  }
}

// 상세 화면을 표시하는 위젯
class DetailScreen extends StatelessWidget {
  final int imgIndex; // 이미지 인덱스
  const DetailScreen({super.key, required this.imgIndex});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(), // 앱바
      body: Column(
        children: [
          Image.asset("assets/movies/${imgIndex + 1}.jpg"), // 영화 이미지
          const Text(
            "MovieName", // 영화 이름
            style: TextStyle(
              fontSize: 18, // 글꼴 크기
            ),
          )
        ],
      ),
    );
  }
}

PageTransitionSwitcher 위젯

PageTransitionSwitcher는 Flutter의 위젯 중 하나로, 여러 페이지 간의 전환을 관리하는 데 사용됩니다. 일반적으로 페이지 간 전환 시 애니메이션 효과를 부여하고, 페이지가 추가되거나 제거될 때 애니메이션을 제공하는 데에 활용됩니다. 예를 들어 페이지가 슬라이드되거나 페이드되는 등의 애니메이션 효과를 적용할 수 있습니다.

 

SharedAxisTransition 위젯

SharedAxisTransition은 Flutter의 애니메이션 기능 중 하나로, 두 개의 위젯 간에 공유 축을 사용하여 부드럽게 전환되는 애니메이션 효과를 제공합니다. 이는 화면 전환에 사용되며, 화면 간의 연결성을 강화하고 사용자 경험을 향상시키는 데 유용합니다.

공유 축에는 세로 축 또는 가로 축이 있으며, 두 위젯이 공유되는 축에 따라 이동 및 회전 애니메이션이 발생합니다. 일반적으로 두 개의 화면이 서로 연결되어 있거나 관련되어 있을 때 사용됩니다. 예를 들어 화면 간에 데이터를 공유하는 경우나 사용자가 동일한 콘텍스트에서 다른 콘텐츠로 전환하는 경우에 적합합니다.

PageTransitionSwitcher, SharedAxisTransition 예제소스
import 'package:animations/animations.dart'; // animations 패키지 import

import 'package:flutter/cupertino.dart'; // Cupertino 패키지 import
import 'package:flutter/foundation.dart'; // Flutter 기본 패키지 import
import 'package:flutter/material.dart'; // Material 패키지 import
import 'package:flutter/widgets.dart'; // Flutter 위젯 패키지 import

// SharedAxisScreen 클래스 선언
class SharedAxisScreen extends StatefulWidget {
  static String routeURL = "sharedaxis";
  static String routeName = "sharedaxis";
  const SharedAxisScreen({super.key});

  @override
  State<SharedAxisScreen> createState() => _SharedAxisScreenState();
}

// SharedAxisScreen의 State 클래스 선언
class _SharedAxisScreenState extends State<SharedAxisScreen> {
  int _currentImage = 1; // 현재 이미지 인덱스

  // 이미지 변경 함수
  void _goToImange(int newImage) {
    setState(() {
      _currentImage = newImage;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Shared Axis"), // 앱바 타이틀
      ),
      body: Column(
        children: [
          PageTransitionSwitcher(
            // 페이지 전환 스위처 위젯
            transitionBuilder: (child, primaryAnimation, secondaryAnimation) =>
                SharedAxisTransition(
              // SharedAxisTransition 애니메이션
              animation: primaryAnimation, // 주 애니메이션
              secondaryAnimation: secondaryAnimation, // 보조 애니메이션
              transitionType: SharedAxisTransitionType.horizontal, // 애니메이션 종류
              child: child, // 자식 위젯
            ),
            child: Container(
              key: ValueKey(_currentImage), // 고유한 키
              clipBehavior: Clip.hardEdge, // 클립 동작
              decoration: const BoxDecoration(
                borderRadius: BorderRadius.all(Radius.circular(45)), // 박스 디자인
              ),
              width: 400, // 너비
              height: 400, // 높이
              child: Image.asset(
                // 이미지 위젯
                "assets/movies/$_currentImage.jpg", // 이미지 경로
                fit: BoxFit.cover, // 이미지 맞춤
              ),
            ),
          ),
          const SizedBox(
            height: 50, // 간격 높이
          ),
          Row(
            crossAxisAlignment: CrossAxisAlignment.center, // 수직 정렬 방식
            mainAxisAlignment: MainAxisAlignment.spaceAround, // 수평 정렬 방식
            children: [
              for (int i = 1; i <= 5; i++)
                ElevatedButton(
                  // 활성화된 버튼
                  onPressed: () => _goToImange(i), // 클릭 이벤트 핸들러
                  child: Text(i.toString()), // 텍스트 표시
                )
            ],
          )
        ],
      ),
    );
  }
}

 

 


FadeThroughTransition 위젯

FadeThroughTransition은 Flutter의 애니메이션을 지원하는 패키지 중 하나인 animations 패키지에 포함된 위젯 중 하나입니다. 이 위젯은 여러 위젯 간의 전환 시 페이드 인/아웃 애니메이션을 제공하여 부드러운 전환 효과를 만들어냅니다.

FadeThroughTransition은 기존의 위젯이 사라지는 동안 새로운 위젯이 나타나는 효과를 제공합니다. 이는 일종의 교체 애니메이션으로, 이전의 위젯이 페이드 아웃되면서 새로운 위젯이 페이드 인됩니다. 이를 통해 화면 간의 전환을 부드럽게 만들고 사용자 경험을 향상시킬 수 있습니다.

FadeThroughTransition 예제소스
import 'package:animations/animations.dart'; // animations 패키지 import
import 'package:flutter/material.dart'; // Material 패키지 import

// FadeThroughScreen 클래스 선언
class FadeThroughScreen extends StatefulWidget {
  static String routeName = "fade";
  static String routeURL = "fade";
  const FadeThroughScreen({super.key});

  @override
  State<FadeThroughScreen> createState() => _FadeThroughScreenState();
}

// FadeThroughScreen의 State 클래스 선언
class _FadeThroughScreenState extends State<FadeThroughScreen> {
  int index = 0; // 현재 페이지 인덱스

  // 새로운 목적지로 이동하는 함수
  void _onNewDestination(int newIndex) {
    setState(() {
      index = newIndex;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(), // 앱바
      body: Center(
        child: PageTransitionSwitcher(
          // 페이지 전환 스위처 위젯
          transitionBuilder: (child, primaryAnimation, secondaryAnimation) =>
              FadeThroughTransition(
            // FadeThroughTransition 애니메이션
            animation: primaryAnimation, // 주 애니메이션
            secondaryAnimation: secondaryAnimation, // 보조 애니메이션
            child: child, // 자식 위젯
          ),
          child: [
            // 페이지 리스트
            const NavigationPage(
              key: ValueKey(0), // 고유한 키
              text: "Profile Page", // 텍스트
              icon: Icons.person, // 아이콘
            ),
            const NavigationPage(
              key: ValueKey(1), // 고유한 키
              text: "Notify Page", // 텍스트
              icon: Icons.notifications, // 아이콘
            ),
            const NavigationPage(
              key: ValueKey(2), // 고유한 키
              text: "Settings Page", // 텍스트
              icon: Icons.settings, // 아이콘
            ),
          ][index], // 현재 페이지 인덱스에 따라 페이지가 변경됨
        ),
      ),
      bottomNavigationBar: NavigationBar(
        // 하단 네비게이션 바
        selectedIndex: index, // 현재 선택된 인덱스
        onDestinationSelected: _onNewDestination, // 새로운 목적지로 이동하는 함수
        destinations: const [
          // 목적지 리스트
          NavigationDestination(
              icon: Icon(Icons.person), label: "Profile"), // 프로필
          NavigationDestination(
              icon: Icon(Icons.notifications), label: "Notify"), // 알림
          NavigationDestination(
              icon: Icon(Icons.settings), label: "Settings"), // 설정
        ],
      ),
    );
  }
}

// 네비게이션 페이지 위젯
class NavigationPage extends StatelessWidget {
  final String text; // 텍스트
  final IconData icon; // 아이콘

  const NavigationPage({
    super.key,
    required this.text,
    required this.icon,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Container(
          height: 500, // 높이
          width: 400, // 너비
          color: Colors.amber, // 배경색
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center, // 주 축 정렬
            children: [
              Icon(
                // 아이콘 표시
                icon, // 아이콘
                size: 48, // 아이콘 크기
              ),
              Text(
                // 텍스트 표시
                text, // 텍스트
                style: const TextStyle(
                  fontSize: 20, // 글꼴 크기
                ),
              )
            ],
          ),
        ),
      ),
    );
  }
}
반응형
Comments