[Flutter] 탭 네비게이션 — TabBar와 TabBarView

Flutter에서 여러 화면을 탭으로 전환하는 UI는 TabBar(상단 탭) + TabBarView(탭별 내용) + TabController(둘을 연결) 조합으로 만듭니다. 이 글에서는 기본 구조와 동작 예제, 그리고 스크롤 가능한 탭·상태 유지 같은 실전 옵션까지 정리합니다.

🧩 핵심 위젯 3가지 #

  • TabBar — 상단에 탭 목록을 배치하는 위젯. 사용자가 선택할 항목(Tab)들을 나열합니다. (“RootTab"이라고 부르는 상단 탭 구성이 이것입니다.)
  • TabBarView — 각 탭에 대응하는 내용 화면을 표시하는 위젯. 자식 위젯 리스트를 받아 선택된 탭의 콘텐츠를 보여줍니다.
  • TabController — TabBar와 TabBarView의 선택 상태를 동기화하는 컨트롤러.

1️⃣ 기본 예제 — DefaultTabController #

가장 간단한 방법은 DefaultTabController로 컨트롤러를 자동 제공하는 것입니다. 별도 패키지 없이 material.dart만으로 충분합니다.

1flutter create my_app
2cd my_app

lib/main.dart:

 1import 'package:flutter/material.dart';
 2
 3void main() {
 4  runApp(MyApp());
 5}
 6
 7class MyApp extends StatelessWidget {
 8  @override
 9  Widget build(BuildContext context) {
10    return MaterialApp(
11      title: 'Flutter Tab Example',
12      home: HomeScreen(),
13    );
14  }
15}
16
17class HomeScreen extends StatelessWidget {
18  @override
19  Widget build(BuildContext context) {
20    return DefaultTabController(
21      length: 3, // 탭의 개수
22      child: Scaffold(
23        appBar: AppBar(
24          title: Text('Tab Example'),
25          bottom: TabBar(
26            tabs: [
27              Tab(text: 'Tab 1'),
28              Tab(text: 'Tab 2'),
29              Tab(text: 'Tab 3'),
30            ],
31          ),
32        ),
33        body: TabBarView(
34          children: [
35            Center(child: Text('Content for Tab 1')),
36            Center(child: Text('Content for Tab 2')),
37            Center(child: Text('Content for Tab 3')),
38          ],
39        ),
40      ),
41    );
42  }
43}
1flutter run

코드 설명 #

  • DefaultTabController: 하위 트리에 TabController를 제공하며, length로 탭 개수를 지정합니다.
  • Scaffold / AppBar: 기본 레이아웃과 상단 영역을 구성합니다.
  • TabBar: tabsTab 위젯 리스트를 넣어 상단 탭을 만듭니다.
  • TabBarView: children의 각 위젯이 탭 선택에 따라 표시됩니다. 탭 전환 애니메이션은 기본 제공됩니다.

2️⃣ TabController 직접 제어 #

탭 인덱스 감지·프로그래밍적 전환이 필요하면 TabController를 직접 만듭니다. SingleTickerProviderStateMixin을 함께 사용합니다.

 1class HomeScreen extends StatefulWidget {
 2  @override
 3  State<HomeScreen> createState() => _HomeScreenState();
 4}
 5
 6class _HomeScreenState extends State<HomeScreen>
 7    with SingleTickerProviderStateMixin {
 8  late final TabController _tab;
 9
10  @override
11  void initState() {
12    super.initState();
13    _tab = TabController(length: 3, vsync: this);
14  }
15
16  @override
17  void dispose() {
18    _tab.dispose();
19    super.dispose();
20  }
21
22  @override
23  Widget build(BuildContext context) {
24    return Scaffold(
25      appBar: AppBar(
26        bottom: TabBar(controller: _tab, tabs: const [
27          Tab(icon: Icon(Icons.home), text: 'Home'),
28          Tab(icon: Icon(Icons.search), text: 'Search'),
29          Tab(icon: Icon(Icons.person), text: 'Profile'),
30        ]),
31      ),
32      body: TabBarView(controller: _tab, children: const [
33        Center(child: Text('Home')),
34        Center(child: Text('Search')),
35        Center(child: Text('Profile')),
36      ]),
37    );
38  }
39}

💡 Tab(icon: ..., text: ...)처럼 아이콘과 텍스트를 함께 줄 수 있습니다.


3️⃣ 실전 옵션 #

스크롤 가능한 탭 — isScrollable #

탭이 많아 가로 폭을 넘으면 isScrollable: true로 스크롤되게 합니다(기본값 false).

1TabBar(
2  isScrollable: true,
3  tabs: [ /* 많은 Tab들 */ ],
4)

탭 상태 유지 — AutomaticKeepAliveClientMixin #

기본적으로 탭을 전환하면 각 탭의 상태가 초기화됩니다. 스크롤 위치·입력값 등을 유지하려면 탭 콘텐츠 위젯에 AutomaticKeepAliveClientMixin을 적용하고 wantKeepAlive를 true로 둡니다.

 1class KeepAlivePage extends StatefulWidget {
 2  @override
 3  State<KeepAlivePage> createState() => _KeepAlivePageState();
 4}
 5
 6class _KeepAlivePageState extends State<KeepAlivePage>
 7    with AutomaticKeepAliveClientMixin {
 8  @override
 9  bool get wantKeepAlive => true; // 상태 유지
10
11  @override
12  Widget build(BuildContext context) {
13    super.build(context); // 필수 호출
14    return const Center(child: Text('상태가 유지되는 탭'));
15  }
16}

⚠️ TabBarView에 내장 keep-alive 옵션은 (2025년 기준) 아직 없습니다. 위 mixin 방식이 현재 표준 해법입니다.

긴 콘텐츠 #

탭 내용이 길면 각 탭 위젯을 SingleChildScrollView/ListView로 감싸 세로 스크롤되게 합니다.


❓ 자주 묻는 질문 #

Q. DefaultTabController와 TabController 직접 생성, 무엇을 쓰나요? 단순 탭 UI는 DefaultTabController로 충분합니다. 현재 탭 인덱스 감지나 코드로 탭 이동이 필요하면 TabController를 직접 만드세요.

Q. 탭을 바꾸면 입력값이 사라져요. AutomaticKeepAliveClientMixin + wantKeepAlive = true로 해당 탭의 상태를 유지하세요.

Q. 탭이 화면 폭을 넘어가요. TabBar(isScrollable: true)로 가로 스크롤을 켜면 됩니다.


📚 참고 #

Advertisement