Bottom Navigation bar vs Tab bar - 인스타그램 클론코딩 1

김가영·2021년 5월 15일
0

Flutter

목록 보기
4/5
post-thumbnail

시작하기

Bottom Navigation 을 만들어보자.
Bottom Navigation 을 만들기 위해서는

  • Bottom Navigation Bar 클래스
  • tab bar 클래스

두가지를 이용할 수 있다.

Bottom Navigation 만들기

Bottom Navigation Bar 클래스

Bottom Navigation Bar 클래스를 이용했다. https://api.flutter.dev/flutter/material/BottomNavigationBar-class.html

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  static const String _title = 'Instagram clone';
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: _title,
      home: MyStatefulWidget(),
      theme: ThemeData(
        primaryColor: Colors.white
      ),
    );
  }
}

class MyStatefulWidget extends StatefulWidget {
  
  _MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}

// 아이콘이 클릭되었을 때 보여줄 widget 화면. 
// 첫 아이콘(홈)에만 Text를 넣어주고 나머지는 Plcaeholder(임시 위젯)을 넣었다.
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  int _selectedIndex = 0;
  List<Widget> _widgetOptions = [
    Text('home'),
    Placeholder(),
    Placeholder(),
    Placeholder(),
    Placeholder(),
  ];

  // 아이콘이 클릭되었을 때의 이벤트 리스너
  // 클릭된 인덱스로 _selectedIndex에 할당하고 build 함수를 호출한다(setState)
  void _onItemTapped(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Instagram'),
      ),
      // body에 넣어줄 아이템
      body: Center(
        child: _widgetOptions.elementAt(_selectedIndex),
      ),
      bottomNavigationBar: BottomNavigationBar(
        items: [
          BottomNavigationBarItem(
            // 현재 아이콘이 선택된 아이콘일때와 선택된 아이콘이 아닌 경우 Icon을 다르게 하기 위함
            icon: _selectedIndex == 0? Icon(Icons.home_filled, color: Colors.black,): Icon(Icons.home_outlined, color: Colors.black),
            label: 'home'
          ),
          BottomNavigationBarItem(
            icon: _selectedIndex == 1? Icon(Icons.search, color: Colors.black,):  Icon(Icons.search_off, color: Colors.black,),
              label: 'search'
          ),
          BottomNavigationBarItem(
            icon: _selectedIndex == 2? Icon(Icons.shopping_bag, color: Colors.black,): Icon(Icons.shopping_bag_outlined, color: Colors.black),
              label: 'media'
          ),
          BottomNavigationBarItem(
            icon: _selectedIndex == 3? Icon(Icons.movie, color: Colors.black,): Icon(Icons.movie_outlined, color: Colors.black,),
              label: 'shop'
          ),
          BottomNavigationBarItem(
            icon: _selectedIndex == 4? Icon(Icons.person, color: Colors.black): Icon(Icons.person_outline, color: Colors.black,),
              label: 'profile'
          )
        ],
        currentIndex: _selectedIndex, 
        selectedItemColor: Colors.black,
        onTap: _onItemTapped,
        showSelectedLabels: false, //(1)
        showUnselectedLabels: false, //(1)
        type: BottomNavigationBarType.fixed, //(2)
      ),
    );
  }

}
  • (1)

Bottom Navigation Bar 클래스는 Material Design 과 함께 쓰려면 label이 not null이어야 한다. 원래 label은 아이콘 아래에서 아이콘이 클릭되었을때 나타나지만, 이를 없애기 위해

showSelectedLabels, showUnselectedLabels 를 false로 바꿔주었다.

  • (2)

type: BottomNavigationBarType.fixed

BottomNavigationBarType 를 설정해준 것

크게 fixed와 shifting 두가지가 있다. 전자는 클릭되었을 때 아무 이벤트도 발생하지 않지만 후자의 경우 클릭되었을 때 커지는 이벤트 / 라벨이 fade in 하는 이벤트 등이 존재한다. 아이콘이 클릭될때마다 navigation bar의 background 색을 다르게 할 수도 있다.

보통 item이 네개 이하이면 자동으로 fixed, 그보다 많으면 shifting으로 세팅되는 것 같다.

특징

가로 스크롤로 화면 전환이 되지 않는다. 이를 위해서는 TabBar를 이용해야 할 것 같다.

아이콘과 라벨 설정이 필수이다. TabBar와 구분되는 명확한 특징인 것 같다. 라벨은 옵션을 통해 안보이게 할 수 있지만 아이콘은 가릴 수 없다. 글씨로만 탭을 구성할 수는 없다.

구현화면

ios

android


tabBar 이용

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  static const String _title = 'Instagram clone';
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: _title,
      home: BottomNavigator(),
      theme: ThemeData(
        primaryColor: Colors.white
      ),
    );
  }
}

class BottomNavigator extends StatelessWidget {

  List<Widget> _widgetOptions = [
    Text('home'),
    Placeholder(),
    Placeholder(),
    Placeholder(),
    Placeholder(),
  ];

  
  Widget build(BuildContext context) {
    return DefaultTabController(
        initialIndex: 1,
        length: 5,
        child: Scaffold(
          appBar: AppBar(
            title: Text('Instagram'),
          ),
          bottomNavigationBar: TabBar( //(1)
            tabs: <Widget>[
              Tab(
                  icon: Icon(Icons.home_outlined)
              ),
              Tab(
                  icon: Icon(Icons.search)
              ),
              Tab(
                  icon: Icon(Icons.movie_outlined)
              ),
              Tab(
                  icon: Icon(Icons.shopping_bag_outlined)
              ),
              Tab(
                  icon: Icon(Icons.person_outline)
              )
            ],
            indicatorColor: Colors.transparent, // indicator 없애기
            unselectedLabelColor: Colors.grey, // 선택되지 않은 tab 색
            labelColor: Colors.black, // 선택된 tab의 색
          ),
          body: TabBarView( //(2)
            children: _widgetOptions, // 어떤 아이템을 넣어줄 지
          ),
        ),
    );
  }
}

TabBar는 defaultTabController를 이용하면 정말 쉽게 이용할 수 있다.

전체 Scaffold를 defaultTabController로 감싼 후,

Scaffold의 bottomNavigationBar 으로 Tabbar을 넣고(아래 아이콘들) (1),

body로 TabBarView(화면에 실제 보여질 위젯)를 넣는다 (2).

특징

홈 스크린의 기본 위젯 트리를 구성하는 Scaffold 상위 위젯으로 존재한다. 기본적으로 Stateless Widget.

아이콘이 선택됐을 때와 선택되지 않았을 때 색을 변화하는 것은 가능하지만 Bottom Navigation bar에서 처럼 아이콘 자체를 변화시키는 것은 힘들었다.

구현화면

ios

android

결론 및 비교

둘 다 구현이 어렵지는 않았다.

결정적으로 둘의 차이는 클릭시 아이콘 변경스와이프가 결정할 수 있을 것 같다.

Bottom Navigation bar는 클릭시 아이콘 변경은 가능하지만 탭간 스와이프는 불가능하며,
Tab bar 는 스와이프는 가능하지만 클릭시 아이콘 변경은 불가하다. 물론 색 변경은 가능하다.

추가적으로 Bottom Navigation bar는 아이콘 숨기기가 불가하므로 글씨로만 탭을 구성하고 싶다면 Tabbar을 써야한다.


탭바 + 아이콘 변경 기능 추가에 도전해보자

그렇다면 탭바에 아이콘 변경 기능을 추가할 순 없을까?
가능했다.

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  static const String _title = 'Instagram clone';
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: _title,
      home: BottomNavigator(),
      theme: ThemeData(
          primaryColor: Colors.white
      ),
    );
  }
}

class BottomNavigator extends StatefulWidget {
  
  State<StatefulWidget> createState() {
    return _BottomNavigatorState();
  }
}



class _BottomNavigatorState extends State<BottomNavigator> with SingleTickerProviderStateMixin {

  int _seletedIndex = 0;

  TabController _tabController;

  
  void initState() {
    super.initState();
    _tabController = TabController(length: 5, vsync: this);
    _tabController.addListener(_handleTabSelection);
  }

  _handleTabSelection() {
    setState(() {
      _seletedIndex = _tabController.index;
    });
  }

  
  void dispose() {
    _tabController.dispose();
    super.dispose();
  }

  List<Widget> _widgetOptions = [
    Text('home'),
    Placeholder(),
    Placeholder(),
    Placeholder(),
    Placeholder(),
  ];

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Instagram'),
      ),
      bottomNavigationBar: TabBar( //(1)
        controller: _tabController,
        tabs: <Widget>[
          Tab(
              icon: _seletedIndex == 0 ? Icon(Icons.home_filled) : Icon(Icons.home_outlined)
          ),
          Tab(
              icon: _seletedIndex == 1 ? Icon(Icons.search) : Icon(Icons.search_off)
          ),
          Tab(
              icon: _seletedIndex == 2? Icon(Icons.movie_creation) : Icon(Icons.movie_outlined)
          ),
          Tab(
              icon: _seletedIndex == 3? Icon(Icons.shopping_bag) : Icon(Icons.shopping_bag_outlined)
          ),
          Tab(
              icon: _seletedIndex == 4? Icon(Icons.person) : Icon(Icons.person_outline)
          )
        ],
        indicatorColor: Colors.transparent, // indicator 없애기
      ),
      body: TabBarView( //(2)
        controller: _tabController,
        children: _widgetOptions, // 어떤 아이템을 넣어줄 지
      ),

    );
  }
}

기본적으로 위에서 구현한 tabbar 뷰를 stateful하게 바꿔줬다.
index가 변화할때마다 아이콘을 변경시키기 위해 가장 필수적인 것은 -> index변화를 감지하는 리스너.

custom tab controller에서 addListener 기능을 찾을 수 있었다. addListener 기능은 object에 변화가 생길 때 변화의 마무리에 호출될 함수를 지정하는 것이다.

tab controller에 selected_index를 현재 인덱스로 바꾸는 함수를 추가해준 후 Tab widget에는 현재 선택된 인덱스가 자기의 인덱스일 때와, 자기 인덱스가 아닐 때의 아이콘을 구분하여 넣어줬다.


성공적! 문제는 탭을 직접 클릭했을 때는 상관없지만 스와이프로 옮겼을 때는 아이콘 변경이 조금 느리다. addListener가 object변화가 생긴 후 함수를 호출해서 그런 것 같다.


인스타그램 클론 앱에는 스와이프가 필요 없기 때문에 Bottom Navigation class 를 이용했다.

profile
개발블로그

0개의 댓글