0379

FE

[Flutter] 'PageView'로 직관적인 탭 스와이프 UI 구현하기

| 서론

안녕하세요! 팡일입니다.

이번 글에서는 Flutter로 앱을 개발하면서 탭 전환 방식을 스와이프 기반으로 개선한 과정을 소개해드리려고 합니다. 기존에는 하단 탭 버튼을 눌러야만 화면이 전환되었지만, 많은 사용자들은 이미 다양한 앱에서 좌우 스와이프를 통해 화면을 넘기는 경험에 익숙합니다. 그렇다면 우리 앱의 탭 UI도 스와이프를 지원한다면 훨씬 더 자연스럽고 직관적인 사용 경험을 제공할 수 있지 않을까요?

그 고민에서 출발해 Flutter의 PageView를 MainTabPage에 적용했고, 기존의 클릭 방식보다 훨씬 유려한 전환을 구현할 수 있었습니다. 이 글에서는 기존 구조가 어떠했는지, PageView를 도입하면 어떤 장점이 있는지, 그리고 실제 코드 기준으로 어떻게 적용했는지를 차근차근 정리해보겠습니다.

| 기존 MainTabPage 구조 이해

TypeScript
├── main
│   ├── main_tab_page.dart
│   └── tabs
│       ├── home_tab.dart
│       ├── list_tab.dart
│       └── my_tab.dart

저희 앱의 메인 탭 구조는 main_tab_page.dart에서 모두 관리되고 있었습니다.

currentTab이라는 상태값을 통해 현재 보고 있는 탭을 정수 인덱스로 표현했고, 화면 상단에는 CustomAppBar가 위치하여 클릭된 탭을 하이라이트하며 onTabSelected 콜백을 통해 currentTab 값을 갱신했습니다.

각 탭의 콘텐츠는 home_tab.dart, list_tab.dart, my_tab.dart처럼 독립적인 위젯으로 나눠져 있었고, Scaffold의 body에서는 단순히 pages[currentTab]을 렌더링하는 방식이었습니다.

즉, “메인 → 챌린지 → 마이”처럼 탭을 이동하려면 버튼 클릭 외에는 방법이 없었고, 스와이프 제스처는 지원되지 않는 구조였습니다.

| 왜 PageView를 도입했는가

스와이프 기반 탭 전환을 지원하기 위해 가장 자연스러운 선택지는 PageView였습니다. PageView는 자식 위젯을 페이지 형태로 배치하고, 사용자가 가로로 넘기면 자동으로 페이지 전환 애니메이션을 제공하는 강력한 위젯입니다.

PageView 도입으로 얻게 되는 장점은 다음과 같습니다.

  1. 사용자에게 익숙한 스와이프 제스처 지원

  2. 기본적으로 부드럽고 자연스러운 전환 애니메이션 제공

  3. 복잡한 터치 이벤트를 직접 구현할 필요 없이 선언적으로 구성 가능

  4. 기존보다 유연한 페이지 상태 관리와 다양한 확장성

기존의 pages[currentTab] 방식이 단순히 화면을 교체하는 정적인 구조였다면, PageView는 여러 페이지를 미리 준비해두고 사용자 제스처에 따라 자연스럽게 이동하는 방식이기 때문에 UX 수준이 확연히 달라집니다.

| PageView 적용하기: MainTabPage 개선 과정

아래는 PageView를 MainTabPage에 적용한 실제 코드 기반 가이드입니다.

1) TickerProviderStateMixin 추가

PageController가 페이지 전환 애니메이션을 구현하기 위해 필요한 믹스인입니다.

code
class _MainTabPageState extends State<MainTabPage> with TickerProviderStateMixin {
  ...
}

2) PageController 선언 및 초기화

PageView를 제어하기 위한 컨트롤러를 상태 변수로 선언하고 관리합니다.

code
int currentTab = 0;
late PageController _pageController;

@override
void initState() {
  super.initState();
  _pageController = PageController(initialPage: currentTab);
}

@override
void dispose() {
  _pageController.dispose();
  super.dispose();
}

3) didChangeDependencies로 특정 탭으로 즉시 이동하기

알림 클릭 등으로 특정 탭부터 시작해야 할 때 활용할 수 있습니다.

code
@override
void didChangeDependencies() {
  super.didChangeDependencies();

  final args = ModalRoute.of(context)?.settings.arguments;
  if (args is Map && args["tabIndex"] != null) {
    final newTabIndex = args["tabIndex"] as int;
    if (newTabIndex != currentTab) {
      setState(() {
        currentTab = newTabIndex;
        _pageController.jumpToPage(currentTab);
      });
    }
  }
}

jumpToPage는 애니메이션 없이 바로 이동하기 때문에 초기에 깜빡임 없이 자연스럽게 전환됩니다.

4) changeTab에서 animateToPage로 부드럽게 전환하기

탭 클릭 시 PageView가 애니메이션과 함께 이동하도록 변경했습니다.

code
void changeTab(int index) {
  setState(() {
    currentTab = index;
  });
  _pageController.animateToPage(
    index,
    duration: const Duration(milliseconds: 300),
    curve: Curves.ease,
  );
}

5) Scaffold의 body를 PageView로 변경

이제 탭의 핵심 구조가 완전히 PageView 기반으로 바뀝니다.

code
body: PageView(
  controller: _pageController,
  onPageChanged: (index) {
    setState(() {
      currentTab = index;
    });
  },
  children: pages.map((page) {
    return Padding(
      padding: const EdgeInsets.fromLTRB(AppSpaces.md, 0, AppSpaces.md, 0),
      child: page,
    );
  }).toList(),
),

스와이프를 하면 PageView가 자동으로 onPageChanged를 호출하고, currentTab이 갱신되며 CustomAppBar도 자연스럽게 업데이트됩니다.

| CustomAppBar와의 동기화는 어떻게 이루어지는가

탭 버튼 클릭 → changeTab 호출 → animateToPage 실행 → currentTab 갱신

스와이프 → onPageChanged 실행 → currentTab 갱신

이렇게 두 입력 방식이 모두 동일한 currentTab 값을 공유하기 때문에,

CustomAppBar는 항상 올바른 탭을 하이라이트하게 됩니다.

사용자가 클릭하든 스와이프하든 UI는 완벽하게 동기화된 상태로 유지되며, 자연스럽고 끊김 없는 UX를 제공합니다.

| 결론

이번 MainTabPage 개선 작업을 통해 앱의 탭 전환 방식은 클릭 중심에서 스와이프 기반으로 진화했습니다. PageView는 Flutter 개발자에게 큰 구조 변경 없이도 자연스러운 전환과 현대적인 사용자 경험을 구현할 수 있는 매우 강력한 도구입니다.

직관적인 스와이프, 부드러운 애니메이션, 간결한 코드 구조까지 모두 만족할 수 있었으며, 직접 적용해보니 사용자 경험이 한 단계 상승하는 효과를 체감할 수 있었습니다. 앞으로도 사용자 입장에서 더 편리한 인터랙션을 고민하며 계속 개선해 나가겠습니다.

원하시면 이 개선 과정을 시각적으로 보여주는 애니메이션 예시나, 전체 완성 코드도 함께 정리해드릴 수 있습니다!