본문 바로가기
flutter/Widget of the Week

Flutter[플러터] / TabBar 를 사용하여 스와이프 시 화면이 넘어가는 탭 만들기 (탭 바, TabBarView, DefaultTabBarController, 드래그, 네비게이션 바, 페이지, 앱바, 툴바) TabBar (Flutter Widget of the Week)

by ch5c 2025. 6. 23.
반응형

TabBar class

Material Design의 기본 탭 바.

기본 탭은 콘텐츠 창 상단, 상단 앱 바 아래에 배치됩니다. 주요 콘텐츠 목적지를 표시합니다.

일반적으로 AppBar 의 AppBar.bottom 부분 으로 생성되고 TabBarView 와 함께 사용됩니다.

https://youtu.be/POtoEH-5l40

공식 문서 코드

 

 

 


앱의 상단에 앱바가 위치해 있는 부분에서 카테고리 기능이 동작되거나 툴바 동작이 진행돼야 하는 상황이 오기 마련이다. 이럴 때 우리는 어떻게 제작을 해야 할까? 그 질문에 대한 대답은 TabBar 위젯을 쓰라는 것이다.

이 TabBar 위젯은 카테고리나 화면을 나누어 사용자 경험을 단순하고 직관적으로 구성하고 싶을 때 사용하는데 탭 기반의 내비게이션을 구현할 때 주로 TabBar와 함께 TabBarView, 그리고 이를 감싸는 DefaultTabController를 함께 사용하곤 한다.

이는 각각의 탭을 눌렀을 때 다른 콘텐츠 화면을 보여주고 싶을 때 유용하게 사용된다. 한번 알아보자.

하위 속성
속성명 타입 기본값 설명
tabs List<Widget> 탭으로 사용할 위젯들의 리스트로, 최소 두 개 이상 필요
controller TabController? null 탭 전환을 제어하는 컨트롤러, 생략 시 DefaultTabController를 사용
isScrollable bool false 탭이 넘칠 경우 수평 스크롤 여부를 결정
padding EdgeInsetsGeometry? null 탭 바 전체에 적용되는 패딩
indicatorColor Color? Theme 선택된 탭 하단 표시선의 색상
indicatorWeight double 2.0 선택된 탭 하단 표시선의 두께
indicatorPadding EdgeInsetsGeometry EdgeInsets.zero indicator에 적용되는 패딩
indicator Decoration? null indicator의 모양을 정의하는 데코레이션
indicatorSize TabBarIndicatorSize? null indicator의 크기를 탭 전체 또는 라벨 기준으로 지정
labelColor Color? null 선택된 탭 라벨의 색상
labelStyle TextStyle? null 선택된 탭 라벨의 텍스트 스타일
labelPadding EdgeInsetsGeometry? null 탭 라벨에 적용되는 패딩
unselectedLabelColor Color? null 선택되지 않은 탭 라벨의 색상
unselectedLabelStyle TextStyle? null 선택되지 않은 탭 라벨의 텍스트 스타일
onTap ValueChanged<int>? null 탭이 탭될 때 호출되는 콜백 함수
physics ScrollPhysics? null 스크롤 동작 방식을 제어
splashFactory InteractiveInkFeatureFactory? null Ink 효과의 렌더링 방식을 지정
splashBorderRadius BorderRadius? null splash 효과에 적용할 테두리 반경
tabAlignment TabAlignment? null 탭 내 정렬 방식 (start, center, fill 등)
textScaler TextScaler? null 텍스트 스케일링 동작 정의
indicatorAnimation TabIndicatorAnimation? null indicator 애니메이션 동작을 지정

사용하기 전에 TabBar 를 컨트롤하기 위해선 TabController 속성의 controller 가 필요한데 먼저 컨트롤러를 지정해줘야 한다.

TabController 는 필수 파라미터로 length 와 vsync 를 받는데 vsync 를 넣기 위해서 먼저 믹스인을 넣어줘야 한다.

class _TestState extends State<Test> with TickerProviderStateMixin {
  late TabController tabController = TabController(length: 3, vsync: this);

탭 컨트롤러를 만들 때는 late 로 지정하고 만들어주면 편하게 사용할 수 있다. length: 에는 자신이 만들 탭의 개수를 입력해 주면 된다. 본인이 간단한 두 화면을 만들고 싶다 하면 2를, 5개의 화면을 만들고 싶다- 하면 5를 넣어주면 되겠다.

이렇게 탭 바 위젯이 상태 부모 위젯이나 다른 부모 위젯에 의해 생성되어 명시적으로 생성된 탭 컨트롤러를 공유하는 것이 불편하다면 조금 아래에서 소개할 DefaultTabController 를 사용해도 괜찮다.

TabBar(
  controller: tabController,
  tabs: [
    Tab(icon: Icon(Icons.home), text: '홈',),
    Tab(icon: Icon(Icons.star), text: '즐겨찾기',),
    Tab(icon: Icon(Icons.settings), text: '설정',),
  ],
)

이제 빌드 위젯 안에서 바로 사용해 주면 된다.

controller 에는 위에서 만든 컨트롤러를 넣어주면 되고 이제 tabs 안에는 우리가 배치할 아이템을 넣어주면 된다. 아이템은 Tab 이라는 위젯을 통해서 배치되게 된다. 이 tabs 파라미터는 List<Widget> 타입을 갖고 있어서 Tab 이 아닌 위젯을 배치해도 아무런 문제 없이 잘 작동한다. 또한 Tab 의 파라미터인 icon 도 타입이 Widget 이기 때문에 넣고 싶은 위젯 아무거나 넣어줘도 된다.

TabBar 라는 위젯은 조금 특이한 편에 속한다. 어째서 특이하냐고 한다면 이렇게 우리가 조작할 수 있는 TabBar 위젯과 그 TabBar 안에 속해있는 TabBarView 를 제작해야 하기 때문이다.

TabBarView 에는 이제 위젯을 배치해줘야 하는데 현재 코드에서는 탭바를 3개로 설정해 놨으니 3개의 위젯을 배치해 주면 되겠다.

TabBarView(
  controller: tabController,
 children: [
   Center(child: Text('홈 화면')),
   Center(child: Text('즐겨찾기 화면')),
   Center(child: Text('설정 화면')),
 ],
)

얼추 비슷한 위젯이 하나 생각나지 않는가? 바로 IndexedStack 위젯이다. 이 탭바 위젯은 인덱시드스택 위젯과 거의 유사하다고 생각하면 편하다.

지금 나는 코드를 이런 형식으로 짜놨다.

Scaffold(
  appBar: TabBar(...),
  body: TabBarView(...),
)

이렇게 하면 아주 간단하게 탭바를 사용할 수 있게 되지만 실제로 제작을 하게 되면 Sacffold 에 바로 탭바를 위치시키는 경우는 잘 없어 필연적으로 Column 같은 위젯 안에 배치하게 되는데 그럴 때면 TabBarView 의 사이즈를 무조건 지정해줘야 한다.

Column(
  children: [
    TabBar(...),
    Expanded(
      child: TabBarView(...),
    )
  ],
)

이런 식으로 Expanded 를 하여 화면 전체를 차지하게 하는 방식을 난 선호하는 편이다. 아니면 SizedBox 를 사용하여 직접적으로 지정해 줘도 된다.

이번에는 DefaultTabContoller 에 대해 알아보자. 이 위젯은 위에서 알려준 것보다 더 간단하게 탭바를 바로 구현할 수 있게 도와준다. 놀랍게도 컨트롤러를 선언하지 않아도 된다.

DefaultTabController(
  length: 3,
  child: Scaffold(
    appBar: TabBar(
      tabs: [
        Tab(icon: Icon(Icons.home), text: '홈',),
        Tab(icon: Icon(Icons.star), text: '즐겨찾기',),
        Tab(icon: Icon(Icons.settings), text: '설정',),
      ],
    ),
    body: TabBarView(
     children: [
       Center(child: Text('홈 화면')),
       Center(child: Text('즐겨찾기 화면')),
       Center(child: Text('설정 화면')),
     ],
    ),
  ),
)

이렇게 만들면 너무나도 간단하게 컨트롤러를 만들지도 않고 바로 탭 동작을 할 수 있게 된다. 정말 화면에서 탭바가 내비게이션의 역할만 수행한다면 이 위젯을 쓰는 것을 추천한다. 다만 네비게이션 외의 콜백함수를 실행해야 한다든가 하는 그런 것들은 당연지사 controller 를 만들어서 사용하는 것이 좋다.

TabBar.secondary()

TabBar 에는 생성자가 하나 있는데 바로 TabBar.secondary 이다. 이름에서도 알 수 있듯이 탭이 두 개밖에 없다면 사용하기 좋은 위젯인데 그냥 TabBar 와 동작에는 전혀 차이가 없다. 다만 UI 적인 부분이 조금 달라지게 된다.

보이는 것과 같이 하단의 인디케이터가 달라지게 된다. 일반적인 탭바(왼쪽)은 그냥 자그마 낳게 인디케이터로 표시가 되지만 탭바 세컨더리는 인디케이터가 화면의 반을 꽉 채우는 모습이다.

당연하겠지만 이 생성자를 사용하는 데에 있어서 주의해야 할 점은 탭의 개수가 2개여야 한다는 점이다. 3개 막 이렇게 되면 바로 오류가 나버린다.

탭바도 디자인을 먹여줄 수 있다. unsunselected 가 앞에 붙은 파라미터를 사용하면 선택되지 않은 탭의 속성을, label 이 붙은 파라미터를 사용하면 전체적인 스타일과 선택되었을 때의 색상을 지정해 줄 수 있다.

unselectedLabelStyle: TextStyle(
  color: Colors.blue,
  fontWeight: FontWeight.w100
),
labelStyle: TextStyle(
  color: Colors.black,
  fontWeight: FontWeight.w900
)

여기서 unselectedLabelStyle 속성을 사용해 줬기에 labelStyle 과 구분되어 적용되게 된다. labelStyle 만을 사용했다면 전체가 그 스타일을 먹게 될 것이다.

위에서 secondary 생성자를 사용하면 인디케이터가 늘어난다고 말했는데 사실 그냥 설정으로 조절할 수 있다.

또한 색상과 패딩도 다 먹여줄 수 있다.

indicatorColor: Colors.black,
indicatorSize: TabBarIndicatorSize.tab,
indicatorPadding: EdgeInsets.symmetric(horizontal: 15)

indicatorSize 를 TabBarIndicatorSize.tab 으로 해놓게 되면 인디케이터의 크기가 탭의 크기를 다 차지하게 된다. 

여기서 주의해야 할 점은 indicator 속성 사용 시 Color 라든가 Size 라던가 전부 무시되어 버리니 가급적이면 인디케이터는 내가 사용한 속성들로 사용하는 것이 좋다.

 

이렇게 TabBar 위젯에 대해서 알아보았다. 이 위젯은 놀랍게도 탭 아이템을 클릭해도 다른 화면으로 넘어가지지만 TabBatView 가 위치해 있는 곳을 스와이프 하면 다른 화면으로 그대로 넘어가는 것을 알 수 있다. 이 위젯은 정말 이러한 탭을 만드는 시간 자체와 애니메이션을 또 집어넣은 시간 이런 것을 전부 줄여주기 때문에 꼭 사용해야 하는 위젯이 되겠다. 암튼 도움이 되었길 바라며 마치겠다.

 

반응형