PromleeBlog
sitemap
aboutMe

posting thumbnail
네비게이션 바 탭 단위 스택 유지하기
Setting up navigation bar tab events

📅

🚀

들어가기 전에 🔗

  1. 목표, 조건 설정 및 초기설정, 개요 링크
  2. 로그인 페이지 분기(FlutterSecureStorage) 링크
  3. 하단 네비게이션 바 설정하기 링크
위 링크를 순서대로 참고하여 프로젝트를 설정하고 진행하였다고 가정합니다.
이 포스팅은 Flutter 3.24 버전 기준으로 작성되었습니다.
지금까지 로그인 페이지 분기를 설정하고, 하단 네비게이션 바를 설정하는 방법을 알아보았습니다. 이제 각 탭에 depth를 추가하여 탭 내부에서도 페이지 이동을 할 수 있도록 설정해보겠습니다.
이번 포스팅에서 필자의 lib 폴더 내부 file tree 구조입니다. 미리 생성해두고 시작하시면 편리합니다.
📦 lib
┣ 📂 model						// 데이터 보관하는 모델 폴더(이번 포스팅에서는 사용하지 않음)
┣ 📂 view						// 화면을 구성하는 뷰 폴더
┃ ┣ 📂 tabs						// 탭 4개를 구성하는 화면
┃ ┃ ┣ 📜 home_screen.dart
┃ ┃ ┣ 📜 notifications_screen.dart
┃ ┃ ┣ 📜 profile_screen.dart
┃ ┃ ┗ 📜 search_screen.dart
┃ ┣ 📜 external_screen.dart
┃ ┣ 📜 login_page.dart
┃ ┗ 📜 main_page.dart
┣ 📂 viewModel					// 데이터와 화면을 잇는 viewModel 폴더
┃ ┗ 📜 auth_service.dart
┗ 📜 main.dart

🚀

탭 내부에서 페이지 이동 구현 🔗

1. External Screen 추가 🔗

External Screen은 하단 네비게이션 바를 통해 이동할 수 있는 화면이 아닌, 다른 화면에서 이동할 수 있는 화면을 의미합니다. 이번 포스팅에서는 External Screen을 추가하여 Home Screen에서 이동할 수 있도록 설정해보겠습니다.
lib/view/external_screen.dart
import 'package:flutter/material.dart';
 
class ExternalScreen extends StatelessWidget {
  const ExternalScreen({super.key});
 
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('External Screen'), // 추가 화면 제목
      ),
      body: const Center(
        child: Text('External Screen', style: TextStyle(fontSize: 24)),
      ),
    );
  }
}

2. Home Screen 수정 🔗

Home Screen 에서 External Screen으로 이동할 수 있는 버튼을 추가하겠습니다.
lib/view/home_screen.dart
import 'package:flutter/material.dart';
import 'package:navigationbar_example/view/external_screen.dart';
 
class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});
 
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          const Text('Home Screen', style: TextStyle(fontSize: 24)),
          IconButton(
              onPressed: () {
                Navigator.push(
                    context,
                    MaterialPageRoute(
                        builder: (context) => const ExternalScreen()));
              },
              icon: const Icon(Icons.next_plan))
        ],
      ),
    );
  }
}

3. 실행 결과 확인 🔗

Home Screen에서 External Screen으로 이동할 수 있는 버튼이 추가되었습니다.
image
하지만 문제가 생겼습니다. External Screen 에서는 하단 네비게이션 바가 보이지 않습니다. 이 문제를 해결하기 위해 IndexedStack을 사용하여 각 탭마다 스택을 유지하도록 설정해보겠습니다.

🚀

추가 페이지에도 네비게이션바 유지하기 🔗

추가 페이지에 네비게이션 바를 표시하기 위해
IndexedStack
Navigator
을 사용하여 각 탭마다 스택을 유지하도록 설정하겠습니다.
또한, 탭 내부에서 페이지 이동 시에도 네비게이션 바가 유지되도록 설정하겠습니다.
lib/view/main_page.dart
import 'package:flutter/material.dart';
 
import 'package:navigationbar_example/view/tabs/home_screen.dart';
import 'package:navigationbar_example/view/tabs/notifications_screen.dart';
import 'package:navigationbar_example/view/tabs/profile_screen.dart';
import 'package:navigationbar_example/view/tabs/search_screen.dart';
 
class MainPage extends StatefulWidget {
  const MainPage({super.key});
 
  @override
  MainPageState createState() => MainPageState();
}
 
class MainPageState extends State<MainPage> {
  int _currentIndex = 0; // 현재 선택된 탭의 인덱스
 
  // 각 탭에서 보여줄 화면 리스트
  final List<Widget> _pages = [
    const HomeScreen(), // 첫 번째 탭 화면
    const SearchScreen(), // 두 번째 탭 화면
    const NotificationsScreen(), // 세 번째 탭 화면
    ProfileScreen(), // 네 번째 탭 화면
  ];
 
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: IndexedStack( // 각 탭마다 스택을 유지하도록 설정
        index: _currentIndex,
        children: List.generate(_pages.length, (index) {
          return Navigator( // 탭 선택 시 페이지 이동을 위한 Navigator
            onGenerateRoute: (routeSettings) {
              return MaterialPageRoute(	
                builder: (context) => _pages[index],
              );
            },
          );
        }),
      ), // 선택된 탭의 화면을 보여줄 위젯
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex, // 선택된 탭 인덱스
        onTap: (index) {
          setState(() {
            _currentIndex = index; // 탭 전환 시 상태 업데이트
          });
        },
        items: const [
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: 'Home',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.search),
            label: 'Search',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.notifications),
            label: 'Notifications',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.person),
            label: 'Profile',
          ),
        ],
        selectedItemColor: Colors.blue, // 선택된 아이템 색상
        unselectedItemColor: Colors.grey, // 선택되지 않은 아이템 색상
      ),
    );
  }
}

실행 결과 확인 🔗

탭 내부에서 페이지 이동 시에도 하단 네비게이션 바가 유지되는 것을 확인할 수 있습니다.
또한 다른 탭 선택 시에도 이전에 선택한 탭의 상태가 유지되는 것을 확인할 수 있습니다.
image

🚀
여전히 문제가 남았습니다. 개요에서 설저한 조건 중 하나인 "탭이 이미 선택된 상태에서 다시 선택할 경우 해당 탭의
최상단 페이지
로 이동하도록 설정" 조건을 만족하지 못합니다.
이 문제를 해결하기 위해
NavigatorStack
을 사용하여 각 탭마다 스택을 관리하도록 설정하겠습니다.
lib/view/main_page.dart
import 'package:flutter/material.dart';
import 'package:navigationbar_example/view/tabs/home_screen.dart';
import 'package:navigationbar_example/view/tabs/notifications_screen.dart';
import 'package:navigationbar_example/view/tabs/profile_screen.dart';
import 'package:navigationbar_example/view/tabs/search_screen.dart';
 
class MainPage extends StatefulWidget {
  const MainPage({super.key});
 
  @override
  MainPageState createState() => MainPageState();
}
 
class MainPageState extends State<MainPage> {
  int _currentIndex = 0; // 현재 선택된 탭의 인덱스
 
  // 각 탭에서 보여줄 화면 리스트
  final List<Widget> _pages = [
    const HomeScreen(), // 첫 번째 탭 화면
    const SearchScreen(), // 두 번째 탭 화면
    const NotificationsScreen(), // 세 번째 탭 화면
    ProfileScreen(), // 네 번째 탭 화면
  ];
 
  final List<GlobalKey<NavigatorState>> _navigatorKeys = [ // 각 탭의 NavigatorKey
    GlobalKey<NavigatorState>(),
    GlobalKey<NavigatorState>(),
    GlobalKey<NavigatorState>(),
    GlobalKey<NavigatorState>(),
  ];
 
  void _onTap(int index) { // 탭 선택 시 호출할 함수
    if (index == _currentIndex) {
      _navigatorKeys[index].currentState?.popUntil((route) => route.isFirst); // 탭이 이미 선택된 상태에서 다시 선택할 경우 최상단 페이지로 이동
    } else {
      setState(() {
        _currentIndex = index;
      });
    }
  }
 
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: IndexedStack(
        index: _currentIndex,
        children: List.generate(_pages.length, (index) {
          return Navigator(
            key: _navigatorKeys[index], // 각 탭의 NavigatorKey 설정
            onGenerateRoute: (routeSettings) {
              return MaterialPageRoute(
                builder: (context) => _pages[index],
              );
            },
          );
        }),
      ), // 선택된 탭의 화면을 보여줄 위젯
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex, // 선택된 탭 인덱스
        onTap: _onTap, // 탭 선택 시 호출할 함수
        items: const [
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: 'Home',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.search),
            label: 'Search',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.notifications),
            label: 'Notifications',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.person),
            label: 'Profile',
          ),
        ],
        selectedItemColor: Colors.blue, // 선택된 아이템 색상
        unselectedItemColor: Colors.grey, // 선택되지 않은 아이템 색상
      ),
    );
  }
}

실행 결과 확인 🔗

탭이 이미 선택된 상태에서 다시 선택할 경우 해당 탭의 최상단 페이지로 이동하는 것을 확인할 수 있습니다.
완성!
완성!

🚀

결론 🔗

이번 포스팅에서는 각 탭마다 스택을 유지하도록 설정하여 탭 내부에서도 페이지 이동을 할 수 있도록 설정해보았습니다. 또한, 탭이 이미 선택된 상태에서 다시 선택할 경우 해당 탭의 최상단 페이지로 이동하도록 설정하였습니다.

더 생각해 보기 🔗

참고 🔗