HyunJun 기술 블로그

Navigation Pattern, Tab Navigation 본문

Flutter

Navigation Pattern, Tab Navigation

공부 좋아 2023. 7. 16. 10:29
728x90
반응형

Tab Navigation

이전 글 (2023.07.11 - [Dart/Flutter] - Navigation Pattern, Navigator, Named Routes, Parameters)에서 플러터에는 어떠한 Navigation Pattern이 있는지와 플러터의 가장 기본적인 내비게이션 패턴인 스택 기반 내비게이션(Stack-based Navigation)을 이용한 Navigator 사용법 등과 Named Routes 등을 알아보았다. 그렇다면 이번 시간에는 Tab Navigation에 대해서 알아보려고 한다.

 

Tab Navigation이란 여러 개의 탭을 사용하여 다양한 기능이나 섹션을 표현하고, 사용자가 각 탭을 선택(클릭) 하여 해당 컨텐츠를 보거나 해당 기능으로 이동할 수 있는 내비게이션 패턴을 말한다. Tab Navigation에는 크게 4가지 정도의 패턴이 있다.

 

  1. BottomNavigationBar: 하단에 위치하여 여러 개의 탭을 아이콘 또는 라벨과 함께 보여주고, 사용자가 탭을 선택하여 해당 컨텐츠를 보여주는데 사용되는 방식이다.
  2. TabBar와 TabBarView: 상단에 위치하여 탭 바를 구현하는 방식으로, TabBar는 탭 바에 표시될 탭들의 목록을 표시하고, TabBarView는 선택된 탭에 해당하는 컨텐츠를 표시한다.
  3. CupertinoTabBar와 CupertinoTabScaffold: iOS 스타일의 탭 바를 구현하는 방식으로, CupertinoTabBar는 iOS의 하단에 위치하는 탭 바를 표시하고, CupertinoTabScaffold는 iOS의 기본 앱 구성을 따르는 스캐폴드(Scaffold)를 제공한다.
  4. Custom Tab Bar: 개발자의 요구에 따라 커스텀 한 탭 바를 구현하는 방식으로, 기본적인 탭 바의 디자인과 기능을 커스텀 하여 원하는 스타일로 탭 바를 구성할 수 있다.

 

탭 내비게이션은 앱의 다양한 기능과 섹션을 사용자에게 쉽게 제공하고, 각 탭을 선택하여 빠르게 화면 전환을 할 수 있도록 해주는 유용한 패턴이다. 따라서 위에서 언급한 4가지 방식은 모두 탭 내비게이션에 속하며, 앱의 구조와 디자인에 맞게 선택하여 사용할 수 있다.

 

1) BottomNavigationBar

BottomNavigationBar에 item들을 정의하고 해당 item 클릭 시 인덱스를 받아와 body에 위젯을 변경하는 형태이다.

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyBottomNavigationBarApp(),
    );
  }
}

class MyBottomNavigationBarApp extends StatefulWidget {
  @override
  _MyBottomNavigationBarAppState createState() => _MyBottomNavigationBarAppState();
}

class _MyBottomNavigationBarAppState extends State<MyBottomNavigationBarApp> {
  int _currentIndex = 0;

  // 각 탭에 해당하는 화면들을 리스트로 정의
  List<Widget> _screens = [
    HomeScreen(),
    SearchScreen(),
    FavoritesScreen(),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Bottom Navigation Bar Example'),
      ),
      // 현재 선택된 탭에 해당하는 화면을 표시하는 바디 부분
      body: _screens[_currentIndex],
      // 하단에 탭 네비게이션 바를 생성
      bottomNavigationBar: BottomNavigationBar(
        // 현재 선택된 탭의 인덱스를 추적
        currentIndex: _currentIndex,
        // 탭을 선택했을 때 실행될 콜백 함수
        onTap: (index) {
          setState(() {
            // 선택한 탭의 인덱스로 현재 선택된 탭을 변경
            _currentIndex = index;
          });
        },
        // 각 탭의 아이템들 정의
        items: [
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: 'Home',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.search),
            label: 'Search',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.favorite),
            label: 'Favorites',
          ),
        ],
      ),
    );
  }
}

// 각 탭에 해당하는 컨텐츠 위젯들
class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text('Home Screen', style: TextStyle(fontSize: 24)),
    );
  }
}

class SearchScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text('Search Screen', style: TextStyle(fontSize: 24)),
    );
  }
}

class FavoritesScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text('Favorites Screen', style: TextStyle(fontSize: 24)),
    );
  }
}

 

 

2) TabBar

AppBar 위치에 TabBar의 Tab들을 등록하여 클릭 시 화면전환이 되게 구현할 수 있다.

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyTabBarApp(),
    );
  }
}

class MyTabBarApp extends StatefulWidget {
  @override
  _MyTabBarAppState createState() => _MyTabBarAppState();
}

class _MyTabBarAppState extends State<MyTabBarApp> with SingleTickerProviderStateMixin {
  late TabController _tabController;

  @override
  void initState() {
    super.initState();
    // TabController를 초기화하고 탭의 수를 설정
    _tabController = TabController(length: 3, vsync: this);
  }

  @override
  void dispose() {
    // TabController를 dispose
    _tabController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Tab Bar Example'),
        bottom: TabBar(
          // TabBar와 TabBarView를 연결하기 위해 TabController를 설정
          controller: _tabController,
          // 탭 바에 표시될 탭들의 목록
          tabs: [
            Tab(icon: Icon(Icons.home), text: 'Home'),
            Tab(icon: Icon(Icons.search), text: 'Search'),
            Tab(icon: Icon(Icons.favorite), text: 'Favorites'),
          ],
        ),
      ),
      // 현재 선택된 탭에 해당하는 컨텐츠를 표시하는 바디 부분
      body: TabBarView(
        // TabBar와 TabBarView를 연결하기 위해 TabController를 설정
        controller: _tabController,
        // 각 탭에 해당하는 컨텐츠 위젯들 정의
        children: [
          HomeScreen(),
          SearchScreen(),
          FavoritesScreen(),
        ],
      ),
    );
  }
}

// 각 탭에 해당하는 컨텐츠 위젯들
class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text('Home Screen', style: TextStyle(fontSize: 24)),
    );
  }
}

class SearchScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text('Search Screen', style: TextStyle(fontSize: 24)),
    );
  }
}

class FavoritesScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text('Favorites Screen', style: TextStyle(fontSize: 24)),
    );
  }
}

 

 

3) CupertinoTabBar

import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyCupertinoTabBarApp(),
    );
  }
}

class MyCupertinoTabBarApp extends StatefulWidget {
  @override
  _MyCupertinoTabBarAppState createState() => _MyCupertinoTabBarAppState();
}

class _MyCupertinoTabBarAppState extends State<MyCupertinoTabBarApp> {
  int _currentIndex = 0;

  // 각 탭에 해당하는 화면들을 리스트로 정의
  List<Widget> _screens = [
    HomeScreen(),
    SearchScreen(),
    FavoritesScreen(),
  ];

  @override
  Widget build(BuildContext context) {
    return CupertinoTabScaffold(
      tabBar: CupertinoTabBar(
        // 현재 선택된 탭의 인덱스를 추적
        currentIndex: _currentIndex,
        // 탭을 선택했을 때 실행될 콜백 함수
        onTap: (index) {
          setState(() {
            // 선택한 탭의 인덱스로 현재 선택된 탭을 변경
            _currentIndex = index;
          });
        },
        // 각 탭의 아이템들 정의
        items: [
          BottomNavigationBarItem(
            icon: Icon(CupertinoIcons.home),
            label: 'Home',
          ),
          BottomNavigationBarItem(
            icon: Icon(CupertinoIcons.search),
            label: 'Search',
          ),
          BottomNavigationBarItem(
            icon: Icon(CupertinoIcons.heart),
            label: 'Favorites',
          ),
        ],
      ),
      tabBuilder: (context, index) {
        // 현재 선택된 탭에 해당하는 컨텐츠를 표시
        return _screens[index];
      },
    );
  }
}

// 각 탭에 해당하는 컨텐츠 위젯들
class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      navigationBar: CupertinoNavigationBar(
        middle: Text('Home'),
      ),
      child: Center(
        child: Text('Home Screen', style: TextStyle(fontSize: 24)),
      ),
    );
  }
}

class SearchScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      navigationBar: CupertinoNavigationBar(
        middle: Text('Search'),
      ),
      child: Center(
        child: Text('Search Screen', style: TextStyle(fontSize: 24)),
      ),
    );
  }
}

class FavoritesScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      navigationBar: CupertinoNavigationBar(
        middle: Text('Favorites'),
      ),
      child: Center(
        child: Text('Favorites Screen', style: TextStyle(fontSize: 24)),
      ),
    );
  }
}

 

 

 

 

4) Custom Tab Bar

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyCustomTabBarApp(),
    );
  }
}

class MyCustomTabBarApp extends StatefulWidget {
  @override
  _MyCustomTabBarAppState createState() => _MyCustomTabBarAppState();
}

class _MyCustomTabBarAppState extends State<MyCustomTabBarApp> with TickerProviderStateMixin {
  int _currentIndex = 0;

  late AnimationController _controller;

  // 각 탭에 해당하는 컨텐츠 위젯들을 리스트로 정의
  List<Widget> _screens = [
    HomeScreen(),
    SearchScreen(),
    FavoritesScreen(),
  ];

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this, duration: Duration(seconds: 1));
    _controller.repeat(); // 애니메이션 반복
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Custom Tab Bar Example'),
      ),
      // 현재 선택된 탭에 해당하는 컨텐츠를 표시하는 바디 부분
      body: _screens[_currentIndex],
      // 커스텀 탭 바
      bottomNavigationBar: Container(
        height: 80,
        padding: EdgeInsets.symmetric(horizontal: 20),
        decoration: BoxDecoration(
          color: Colors.blue,
          borderRadius: BorderRadius.vertical(top: Radius.circular(16.0)),
          boxShadow: [
            BoxShadow(
              color: Colors.black26,
              blurRadius: 4,
              offset: Offset(0, -2),
            ),
          ],
        ),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            GestureDetector(
              onTap: () {
                _changeTab(0);
              },
              child: TabItem(text: 'Home', icon: Icons.home, isSelected: _currentIndex == 0, controller: _controller),
            ),
            GestureDetector(
              onTap: () {
                _changeTab(1);
              },
              child: TabItem(text: 'Search', icon: Icons.search, isSelected: _currentIndex == 1, controller: _controller),
            ),
            GestureDetector(
              onTap: () {
                _changeTab(2);
              },
              child: TabItem(text: 'Favorites', icon: Icons.favorite, isSelected: _currentIndex == 2, controller: _controller),
            ),
          ],
        ),
      ),
    );
  }

  void _changeTab(int index) {
    setState(() {
      _currentIndex = index;
    });
  }
}

// 커스텀 탭 아이템
class TabItem extends StatelessWidget {
  final String text;
  final IconData icon;
  final bool isSelected;
  final AnimationController controller;

  TabItem({required this.text, required this.icon, required this.isSelected, required this.controller});

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        RotationTransition(
          turns: controller,
          child: Icon(
            icon,
            color: isSelected ? Colors.white : Colors.white60,
            size: 28,
          ),
        ),
        SizedBox(height: 8),
        Text(
          text,
          style: TextStyle(
            color: isSelected ? Colors.white : Colors.white60,
            fontSize: 14,
            fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
          ),
        ),
      ],
    );
  }
}

// 각 탭에 해당하는 컨텐츠 위젯들
class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(

      child: Center(
        child: Text('Home Screen', style: TextStyle(fontSize: 24)),
      ),
    );
  }
}

class SearchScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(

      child: Center(
        child: Text('Search Screen', style: TextStyle(fontSize: 24)),
      ),
    );
  }
}

class FavoritesScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(

      child: Center(
        child: Text('Favorites Screen', style: TextStyle(fontSize: 24)),
      ),
    );
  }
}

728x90
반응형
Comments