BottomNavigationBar 기본 이해
하단 탭 역할
Flutter에서 BottomNavigationBar는 화면 아래쪽에 놓는 탭 메뉴입니다. 보통 홈, 검색, 알림, 마이페이지처럼 앱의 대표 화면을 빠르게 바꿀 때 사용합니다. 공식 문서에서도 이 위젯은 보통 3개에서 5개 정도의 주요 화면 이동에 쓰고, Scaffold의 bottomNavigationBar 자리에 넣어 사용한다고 안내합니다. 새 앱이거나 Material 3를 쓰는 앱이라면 NavigationBar가 더 권장됩니다.
언제 쓰면 좋은지
하단 탭은 앱 안에서 자주 오가는 큰 메뉴가 몇 개 정해져 있을 때 잘 맞습니다. 반대로 화면이 넓은 태블릿이나 웹처럼 가로 폭이 큰 경우에는 하단 탭보다 NavigationRail 같은 방식이 더 어울릴 수 있습니다.
준비 코드 작성
가장 쉬운 예제
아래 예제는 BottomNavigationBar를 처음 붙일 때 가장 많이 쓰는 형태입니다. 현재 선택한 탭 번호를 currentIndex로 저장해두고, 탭을 누를 때마다 setState()로 숫자만 바꾸면 됩니다.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: const MainTabPage(),
);
}
}
class MainTabPage extends StatefulWidget {
const MainTabPage({super.key});
@override
State createState() => _MainTabPageState();
}
class _MainTabPageState extends State {
int _currentIndex = 0;
final List _pages = const [
Center(child: Text('홈 화면')),
Center(child: Text('검색 화면')),
Center(child: Text('마이페이지 화면')),
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('BottomNavigationBar 예제'),
),
body: _pages[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (index) {
setState(() {
_currentIndex = index;
});
},
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: '홈',
),
BottomNavigationBarItem(
icon: Icon(Icons.search),
label: '검색',
),
BottomNavigationBarItem(
icon: Icon(Icons.person),
label: '마이',
),
],
),
);
}
}
코드에서 꼭 봐야 할 부분
BottomNavigationBar는 items를 최소 2개 이상 넣어야 하고, 각 항목에는 아이콘과 라벨이 필요합니다. 또 현재 선택된 탭을 보여주려면 currentIndex가 꼭 있어야 합니다. 탭을 눌렀을 때 onTap 안에서 선택 번호를 바꿔주지 않으면 화면이 바뀌지 않습니다.
핵심 속성 정리
currentIndex 의미
currentIndex는 지금 선택된 탭 번호입니다. 첫 번째 탭이면 0, 두 번째 탭이면 1입니다. 이 값이 바뀌면 BottomNavigationBar도 선택 상태를 같이 바꿔서 보여줍니다. 공식 예제도 같은 방식으로 selectedIndex 또는 currentIndex를 바꿔 화면을 전환합니다.
onTap 사용법
onTap은 사용자가 탭을 눌렀을 때 실행됩니다. 여기서 받은 index를 상태값에 저장하면 됩니다. 가장 많이 쓰는 코드는 아래 한 줄입니다.
onTap: (index) {
setState(() {
_currentIndex = index;
});
}
items 작성법
각 탭은 BottomNavigationBarItem으로 만듭니다. 아이콘만 넣기보다 아이콘과 글자를 함께 넣는 경우가 많습니다. 예전에는 title을 쓰던 코드도 있었지만, 공식 문서 기준으로 지금은 label을 사용합니다.
화면 유지 방법
왜 상태가 초기화되는지
처음 예제처럼 body: _pages[_currentIndex] 방식으로 바꾸면 탭을 옮길 때 이전 화면이 다시 만들어질 수 있습니다. 그래서 스크롤 위치, 입력값, 체크 상태가 초기화되는 경우가 자주 나옵니다.
IndexedStack 적용 방법
탭마다 입력값이나 스크롤 위치를 살리고 싶다면 IndexedStack을 쓰는 편이 좋습니다. 공식 문서에서도 IndexedStack은 여러 자식 중 하나만 보이게 하면서 각 자식의 상태를 유지하는 예제를 제공합니다.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: const MainTabPage(),
);
}
}
class MainTabPage extends StatefulWidget {
const MainTabPage({super.key});
@override
State createState() => _MainTabPageState();
}
class _MainTabPageState extends State {
int _currentIndex = 0;
final List _pages = const [
HomePage(),
SearchPage(),
MyPage(),
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('하단 탭 상태 유지 예제'),
),
body: IndexedStack(
index: _currentIndex,
children: _pages,
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (index) {
setState(() {
_currentIndex = index;
});
},
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: '홈',
),
BottomNavigationBarItem(
icon: Icon(Icons.search),
label: '검색',
),
BottomNavigationBarItem(
icon: Icon(Icons.person),
label: '마이',
),
],
),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return const Center(child: Text('홈 화면'));
}
}
class SearchPage extends StatelessWidget {
const SearchPage({super.key});
@override
Widget build(BuildContext context) {
return const Center(child: Text('검색 화면'));
}
}
class MyPage extends StatelessWidget {
const MyPage({super.key});
@override
Widget build(BuildContext context) {
return const Center(child: Text('마이페이지 화면'));
}
}
탭 개수별 차이 정리
3개 이하일 때
BottomNavigationBar는 항목 수가 4개보다 적으면 기본적으로 fixed 방식으로 동작합니다. 선택된 탭과 선택되지 않은 탭이 비교적 일정한 형태로 보입니다.
4개 이상일 때
항목이 4개 이상이면 기본값이 shifting으로 바뀝니다. 이때는 선택된 항목의 배경색이 더 크게 반영될 수 있어서 디자인이 예상과 다르게 보일 수 있습니다. 탭이 많아질수록 화면이 답답해지기 쉬우니 처음에는 3개나 4개 정도로 시작하는 편이 무난합니다.
자주 하는 실수 정리
StatelessWidget으로 시작하는 경우
탭을 누를 때마다 화면이 바뀌려면 선택된 번호가 바뀌어야 합니다. 그래서 보통은 StatefulWidget으로 만들어야 편합니다. 상태값을 저장하고 setState()로 다시 그려야 하기 때문입니다.
currentIndex를 빼먹는 경우
아이콘은 보이는데 선택 표시가 안 맞거나 처음 탭 상태가 이상하게 보이는 경우가 많습니다. 이런 때는 currentIndex를 넣지 않았거나, 바뀌는 값이 잘못 연결된 경우가 많습니다.
items 개수와 화면 개수가 다른 경우
탭은 3개인데 _pages는 2개만 넣으면 눌렀을 때 인덱스 오류가 날 수 있습니다. 탭 개수와 보여줄 화면 개수는 꼭 맞춰야 합니다.
상태 유지가 안 되는 경우
검색창 입력값, 리스트 스크롤 위치가 탭 이동 뒤 사라지면 IndexedStack을 먼저 떠올리면 됩니다. 탭을 옮길 때도 각 화면을 그대로 들고 있기 때문에 이런 문제를 줄이기 좋습니다.
디자인 꾸미기 방법
기본 색상 변경
선택된 아이콘 색, 선택되지 않은 아이콘 색, 배경색은 BottomNavigationBar 속성으로 쉽게 바꿀 수 있습니다.
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
selectedItemColor: Colors.blue,
unselectedItemColor: Colors.grey,
backgroundColor: Colors.white,
onTap: (index) {
setState(() {
_currentIndex = index;
});
},
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: '홈'),
BottomNavigationBarItem(icon: Icon(Icons.search), label: '검색'),
BottomNavigationBarItem(icon: Icon(Icons.person), label: '마이'),
],
),
글자 표시 방식
탭 글자를 항상 보이게 할지, 선택된 탭만 보이게 할지도 디자인에 따라 바꿀 수 있습니다. 다만 처음 배우는 단계에서는 기본값으로 두고, 먼저 탭 전환이 제대로 되는지부터 확인하는 편이 실수가 적습니다.
NavigationBar 같이 알아두기
지금 기준에서 더 많이 권장되는 위젯
Flutter 공식 문서에서는 BottomNavigationBar보다 NavigationBar를 새 앱과 Material 3 환경에서 더 권장합니다. 역할은 비슷하지만 이름과 속성이 조금 다릅니다. BottomNavigationBar의 items 대신 destinations를 쓰고, currentIndex 대신 selectedIndex, onTap 대신 onDestinationSelected를 사용합니다.
bottomNavigationBar: NavigationBar(
selectedIndex: _currentIndex,
onDestinationSelected: (index) {
setState(() {
_currentIndex = index;
});
},
destinations: const [
NavigationDestination(
icon: Icon(Icons.home),
label: '홈',
),
NavigationDestination(
icon: Icon(Icons.search),
label: '검색',
),
NavigationDestination(
icon: Icon(Icons.person),
label: '마이',
),
],
),
지금 무엇을 쓰면 좋은지
기존 강의나 예제를 따라가야 한다면 BottomNavigationBar로 시작해도 괜찮습니다. 다만 새 프로젝트를 처음 만드는 상황이라면 NavigationBar까지 같이 익혀두는 편이 좋습니다. 공식 문서도 그 방향을 안내하고 있습니다.
iOS까지 생각한 작성 방법
안드로이드와 아이폰을 다르게 보여주고 싶을 때
Flutter 공식 문서에서는 안드로이드에서는 NavigationBar, iOS에서는 CupertinoTabBar를 써서 플랫폼에 맞게 보이게 하는 방법도 소개합니다. 앱을 더 자연스럽게 보이게 하고 싶다면 나중에 이 방식까지 넓혀가면 됩니다.
마무리 정리
처음 만들 때 꼭 기억할 것
Flutter 하단 탭 만들기는 생각보다 단순합니다.
먼저 StatefulWidget으로 페이지를 만들고,currentIndex로 현재 탭 번호를 저장한 뒤,BottomNavigationBar를 Scaffold.bottomNavigationBar에 넣고,onTap에서 번호를 바꾸면 끝입니다.
실전에서 가장 많이 쓰는 팁
입문 단계에서는 먼저 기본 예제로 완성해보는 게 좋습니다. 그다음 탭 이동 후에도 입력값이나 스크롤 위치를 살리고 싶다면 IndexedStack을 붙이면 됩니다. 그리고 새 프로젝트라면 NavigationBar도 같이 익혀두면 나중에 다시 손볼 일이 줄어듭니다.
결론
Flutter에서 하단 탭을 만드는 방법은 처음에는 어려워 보여도 실제로는 몇 가지만 이해하면 충분히 구현할 수 있습니다. 핵심은 Scaffold에 bottomNavigationBar를 넣고, 현재 선택된 탭 번호를 currentIndex로 관리하면서 onTap으로 화면을 바꾸는 것입니다. 이 기본만 익혀도 홈, 검색, 마이페이지처럼 자주 쓰는 화면을 깔끔하게 나눌 수 있습니다.
특히 처음에는 BottomNavigationBar로 시작하는 것이 가장 이해하기 쉽습니다. 탭 개수에 맞춰 화면 리스트를 만들고, 눌렀을 때 해당 인덱스의 화면을 보여주도록 연결하면 기본 하단 탭은 바로 완성됩니다. 여기에 IndexedStack까지 같이 사용하면 탭을 옮겨도 입력값이나 스크롤 위치가 유지되어 훨씬 실용적인 앱 화면을 만들 수 있습니다.
또 하나 기억할 점은, 예전 예제에서는 BottomNavigationBar가 많이 보이지만 최근에는 NavigationBar도 자주 사용된다는 점입니다. 그래서 입문 단계에서는 BottomNavigationBar로 먼저 익히고, 이후 NavigationBar까지 넓혀서 보면 최신 방식까지 자연스럽게 이어갈 수 있습니다.
정리하면 Flutter 하단 탭은 어렵게 접근할 필요가 없습니다. 기본 예제로 먼저 직접 만들어보고, 그다음 상태 유지와 디자인 수정까지 하나씩 붙여가면 충분합니다. 처음 한 번 제대로 만들어보면 이후에는 대부분의 앱 화면에서 비슷한 방식으로 쉽게 응용할 수 있습니다.
FAQ
BottomNavigationBar는 꼭 StatefulWidget으로 만들어야 하나요?
대부분의 경우는 StatefulWidget으로 만드는 것이 맞습니다. 탭을 누를 때마다 현재 선택된 번호가 바뀌어야 하고, 그에 따라 화면도 다시 표시되어야 하기 때문입니다. 아주 단순한 상태관리 도구를 따로 쓰는 경우가 아니라면 처음에는 StatefulWidget으로 시작하는 것이 가장 편합니다.
BottomNavigationBar와 NavigationBar는 뭐가 다른가요?
둘 다 하단 탭 메뉴를 만드는 위젯이지만, BottomNavigationBar는 오래전부터 많이 쓰이던 방식이고 NavigationBar는 최근 Material 3 스타일에 맞춰 더 많이 권장되는 위젯입니다. 기존 강의나 예제를 따라갈 때는 BottomNavigationBar가 자주 보이고, 새 프로젝트에서는 NavigationBar를 함께 알아두면 좋습니다.
탭을 눌렀는데 화면이 안 바뀌는 이유는 뭔가요?
가장 흔한 이유는 onTap에서 setState()로 currentIndex 값을 바꾸지 않았기 때문입니다. 또는 body에 연결한 화면 리스트와 탭 개수가 맞지 않는 경우도 많습니다. 탭이 눌리는 것처럼 보여도 실제로 상태값이 바뀌지 않으면 화면은 그대로 남아 있습니다.
탭을 바꾸면 입력한 값이 사라지는데 왜 그런가요?
기본 방식으로 화면을 바꾸면 이전 화면이 다시 생성되면서 입력값이나 스크롤 위치가 초기화될 수 있습니다. 이런 경우에는 IndexedStack을 사용하면 해결되는 경우가 많습니다. 각 화면을 그대로 유지한 상태에서 보이는 화면만 바꾸기 때문에 탭을 이동해도 값이 유지됩니다.
하단 탭은 몇 개까지 넣는 게 좋나요?
보통은 3개에서 5개 정도가 가장 많이 사용됩니다. 너무 많아지면 아이콘과 글자가 좁아져서 보기 불편해질 수 있습니다. 처음에는 홈, 검색, 알림, 마이페이지처럼 자주 쓰는 대표 메뉴만 넣는 것이 좋습니다.
BottomNavigationBarItem에는 무엇을 넣어야 하나요?
기본적으로 아이콘과 라벨을 넣습니다. 보통 icon에는 Icon(Icons.home) 같은 아이콘을 넣고, label에는 홈, 검색, 마이 같은 글자를 넣습니다. 사용자가 어떤 탭인지 바로 알아볼 수 있게 짧고 분명한 이름으로 적는 것이 좋습니다.
currentIndex는 어떤 역할을 하나요?
currentIndex는 현재 선택된 탭 번호를 뜻합니다. 첫 번째 탭이면 0, 두 번째 탭이면 1처럼 숫자로 관리합니다. 이 값이 바뀌면 BottomNavigationBar도 선택된 탭 표시를 바꾸고, 화면도 해당 번호에 맞게 바뀌게 됩니다.
BottomNavigationBar를 쓰면 AppBar도 꼭 같이 넣어야 하나요?
꼭 넣어야 하는 것은 아닙니다. AppBar 없이도 하단 탭은 정상적으로 만들 수 있습니다. 다만 화면 제목이나 상단 메뉴가 필요하다면 함께 넣는 경우가 많고, 단순한 탭 예제를 연습할 때는 AppBar를 같이 두면 현재 어떤 화면인지 확인하기 쉬워집니다.
탭마다 완전히 다른 페이지를 보여줄 수 있나요?
물론 가능합니다. 각 탭마다 별도의 위젯 파일을 만들어 연결하면 됩니다. 예를 들어 홈 화면은 HomePage, 검색 화면은 SearchPage, 마이페이지는 MyPage처럼 나눠두면 관리하기도 훨씬 편합니다. 실제 앱에서는 이런 식으로 분리해서 작성하는 경우가 많습니다.
처음 배우는 입장에서는 BottomNavigationBar와 NavigationBar 중 무엇부터 시작하면 좋나요?
처음에는 BottomNavigationBar부터 시작하는 편이 이해하기 쉽습니다. 예제도 많고 기본 원리를 익히기에 좋기 때문입니다. 그 다음에 NavigationBar를 보면 이름만 조금 다를 뿐 사용 방식이 비슷해서 훨씬 쉽게 받아들일 수 있습니다.