Foggy day

[Flutter] TabBar Widget - 사용법 본문

Flutter/Flutter widget

[Flutter] TabBar Widget - 사용법

jinhan38 2023. 3. 26. 21:47

 

 

이번 포스팅에서는 TabBar 위젯에 대해 알아보겠습니다.

 

TabBar위젯은 주로 여러 페이지를 동시에 사용할 때 페이지 전환을 위해 사용합니다. 때문에 혼자 사용되는 경우는 거의 없고, PageView와 같이 사용할 때가 많습니다.

 

 

 

1. 기본 사용법과 TabController

2. Label style

3. Splash Effect

4. IndicatorWeight, indicator color

5. Indicator Type

6. Paddings

7. Custom indicator

8. Tab scrollable

9. OnTap callback

10. TabController 

 

 

 

1. 기본 사용법과 TabController

TabBar는 각각의 탭 역할을 할 Tab 위젯들이 필요합니다. tabs속성에 Tab위젯들을 넣어줄 수 있습니다. 그런데 TabBar를 사용하려면 TabController가 필수입니다. TabController가 없다면 오류가 발생합니다. TabController를 사용하기 위해서는 TickerProvider가 필요합니다. 

TickerProvider는 Ticker 클래스를 생성하는 클래스로 애니메이션 프레임당 한 번씩 콜백을 호출할 수 있게 해 줍니다. Ticker가 필요한 애니메이션이 한 개라면 SingleTickerProviderStateMixin, 여러 개 라면 TickerProviderStateMixin을 사용합니다. 이번 예제에서는 SingleTickerProviderStateMixin을 사용했습니다. mixin클래스이기 때문에 with를 통해 사용할 수 있습니다. 그리고 TabController는 프레임당 콜백을 받는 클래스이기 때문에 dispose 함수에서 반드시 dispose를 해줘야 합니다. TabController의 자세한 사용법은 페이지 마지막 부분에서 다루겠습니다.

 

TabBar와 TabController 생성 코드 

import 'package:flutter/material.dart';

class TabBarScreen extends StatefulWidget {
  const TabBarScreen({Key? key}) : super(key: key);

  @override
  State<TabBarScreen> createState() => _TabBarScreenState();
}

class _TabBarScreenState extends State<TabBarScreen> with SingleTickerProviderStateMixin {
  late TabController tabController = TabController(
    length: 3,
    vsync: this,
    initialIndex: 0,

    /// 탭 변경 애니메이션 시간
    animationDuration: const Duration(milliseconds: 800),
  );

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("TabBarScreen"),
      ),
      body: _tabBar(),
    );
  }

  Widget _tabBar() {
    return TabBar(
      controller: tabController,
      tabs: const [
        Tab(text: "Tab 1"),
        Tab(text: "Tab 2"),
        Tab(text: "Tab 3"),
      ],
    );
  }

}

 

위 코드로 빌드를 했더나 파란색 인디케이터와 비어있는 탭의 모습을 볼 수 있습니다. 사진에서 가느다란 파란색 막대를 탭 인디케이터라고 합니다. 

Tab으로 3개를 넣어줬는데 화면에서는 볼 수가 없습니다. 정상적으로 그려지긴 했지만 text컬러가 white라서 보이지 않는 것입니다. 이제 탭의 속성들을 이용해서 꾸며볼 차례입니다. 

 

 

 

 

 

 

 

 

 

 

 

2. Label style

먼저 탭에 들어갈 Label(text)들을 꾸며보겠습니다. 선택된 탭은 검은색 컬러에 fontSize = 20, FontWidget.bold로 설정했습니다. 선택되지 않은 탭은 회색, fontSize = 16으로 설정했습니다. 

  labelColor: Colors.black,
  labelStyle: const TextStyle(
    fontSize: 18,
    fontWeight: FontWeight.bold,
  ),
  unselectedLabelColor: Colors.grey,
  unselectedLabelStyle: const TextStyle(
    fontSize: 16,
  ),

 

 

 

3. Splash Effect

그리고 탭바를 클릭할 때 나오는 splash의 컬러와 round 처리도 추가했습니다. 만약에 effect를 보고싶지 않다면 컬러를 white나 transparent로 설정해 주면 됩니다.

  /// 탭바 클릭시 나오는 splash effect 컬러
  overlayColor: MaterialStatePropertyAll(
    Colors.blue.shade100,
  ),

  /// 탭바 클릭할 때 나오는 splash effect의 radius
  splashBorderRadius: BorderRadius.circular(20),

 

 

 

 

 

 

4. IndicatorWeight, indicator color

인디캐이터의 두께와 컬러도 변경할 수 있습니다. 두께를 5, 컬러는 black로 했습니다. 

  /// 기본 인디캐이터의 컬러
  indicatorColor: Colors.black,

  /// 0으로 설정할 것
  indicatorWeight: 5,

 

 

 

 

 

5. Indicator Type

그런데 인디캐이터의 사이즈를 수정해야 되는 경우가 생길 수도 있습니다. 그럴 땐 indicatorSize 특성을 수정해야합니다. 두 가지 타입이 있는데 TabBarIndicatorSize.tab과 TabBarIndicatorSize.label입니다. tab은 기존의 모습처럼 인디케이터가 탭의 가로 사이즈와 동일합니다. label은 indicator가 라벨의 가로 사이즈와 동일해집니다.

  /// 인디캐이터의 기본 사이즈를 label에 맞출지, 
  /// 탭 좌우 사이즈에 맞출지 설정
  indicatorSize: TabBarIndicatorSize.label,

 

 

 

 

6. Paddings

탭바에는 3개의 패딩이 있습니다. 탭바 자체의 상하좌우에 적용하는 패딩, 라벨에만 적용하는 패딩, 인디케이터에만 적용하는 패딩입니다. 패딩의 설정에 따라서 splash effect의 영역도 커지게 됩니다. 디자인에 맞춰서 적절히 설정하는 것이 필요합니다. 

  /// 탭바의 상하좌우에 적용하는 패딩
  padding: const EdgeInsets.symmetric(
    horizontal: 20,
    vertical: 20,
  ),

  // 라벨에 주는 패딩
  labelPadding: const EdgeInsets.symmetric(
    horizontal: 10,
    vertical: 10,
  ),

  // 인디캐이터의 패딩
  indicatorPadding: const EdgeInsets.all(5),

 

 

 

7. Custom indicator

이번에는 아예 다른 모양의 인디캐이터를 만들었습니다. 하단에서 이동하는 인디케이터가 아니라 decoration클래스를 이용해서 원 형태의 indicator를 만들었습니다. 

  /// 커스텀 인디캐이터
  indicator: BoxDecoration(
    color: Colors.green.shade400,
    shape: BoxShape.circle,
    border: Border.all(
      color: Colors.orange,
      width: 5,
    ),
  ),

 

 

 

8. Tab scrollable

지금까지의 에제에서는 탭의 개수가 3개였습니다. 하지만 더 많은 탭을 넣고 싶을 수도 있고, 그럴 경우 탭의 사이즈가 너무 작아집니다. 이럴 땐 isScrollable특성을 true로 입력하면 됩니다. 탭을 8개를 사용했습니다

  isScrollable: true,

  tabs: const [
    Tab(text: "Tab 1"),
    Tab(text: "Tab 2"),
    Tab(text: "Tab 3"),
    Tab(text: "Tab 4"),
    Tab(text: "Tab 5"),
    Tab(text: "Tab 6"),
    Tab(text: "Tab 7"),
    Tab(text: "Tab 8"),
  ],

 

이 때 중요한 것은 tabController에서 length를 반드시 Tab의 개수와 맞춰주는 것입니다.

  late TabController tabController = TabController(
    length: 8,
    vsync: this,
    initialIndex: 0,

    // 탭 변경 애니메이션 시간
    animationDuration: const Duration(milliseconds: 800),
  );

 

 

 

9. OnTap callback

마지막으로 onTap함수를 사용해서 탭을 선택했을 때 인덱스를 알 수 있는 콜백을 받을 수 있습니다.

  onTap: (index) {

  },

 

 

 

10. TabController 

TabController를 사용하면 TabBar의 여러 정보들을 얻을 수 있습니다. 

 

addListener를 추가하면 탭바의 index가 변경될 때마다 콜백을 받습니다.

tabController.addListener(() {
  /// 프레임당 콜백
});

 

현재 탭바의 index가 변경중인지 아닌지를 체크할 수 있습니다. 

tabController.indexIsChanging;

 

탭바의 개수를 알 수 있습니다. 

tabController.length;

 

탭바가 변경되기 전의 index를 알 수 있습니다. 탭 바가 여러 번 변경된다면 변경되기 바로 전의 index값이 됩니다.

tabController.previousIndex;

 

탭바의 index를 변경시킬 수도 있습니다. tabController.index에 바로 값을 입력하면 애니메이션 없이 탭이 변경됩니다. 

tabController.index = 2;

 

탭바의 index를 변경시키는 방법은 하나 더 있습니다. 위의 코드가 애니메이션 없이 변경시킨다면 animateTo(index)는 탭을 직접 터치하는 것처럼 애니메이션과 함께 탭의 index를 변경시킬 수 있습니다. 

tabController.animateTo(5);

 

 

 

 

 


 

 

 

 

 

최동 동영상

 

 

 

Full code

import 'package:flutter/material.dart';

class TabBarScreen extends StatefulWidget {
  const TabBarScreen({Key? key}) : super(key: key);

  @override
  State<TabBarScreen> createState() => _TabBarScreenState();
}

class _TabBarScreenState extends State<TabBarScreen>
    with SingleTickerProviderStateMixin {
  late TabController tabController = TabController(
    length: 8,
    vsync: this,
    initialIndex: 0,

    /// 탭 변경 애니메이션 시간
    animationDuration: const Duration(milliseconds: 800),
  );

  @override
  void initState() {
    tabController.addListener(() {
      /// 프레임당 콜백
    });

    /// 탭바의 index가 변경되고 있는지 체크
    /// true or false
    tabController.indexIsChanging;

    /// 탭바의 개수
    tabController.length;

    /// 변경 전의 index
    tabController.previousIndex;

    super.initState();
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("TabBarScreen"),
      ),
      body: Column(
        children: [
          _tabBar(),
          ElevatedButton(
            onPressed: () {
              /// 애니메이션 없이 바로 변경
              tabController.index = 2;
            },
            child: const Text("이동"),
          ),
          ElevatedButton(
            onPressed: () {
              /// 애니메이션 없이 바로 변경
              tabController.animateTo(5);
            },
            child: const Text("이동 animateTo"),
          ),
        ],
      ),
    );
  }

  Widget _tabBar() {
    return TabBar(
      controller: tabController,
      labelColor: Colors.black,
      unselectedLabelColor: Colors.grey,
      labelStyle: const TextStyle(
        fontSize: 18,
        fontWeight: FontWeight.bold,
      ),
      unselectedLabelStyle: const TextStyle(
        fontSize: 16,
      ),

      /// 탭바 클릭시 나오는 splash effect 컬러
      overlayColor: MaterialStatePropertyAll(
        Colors.blue.shade100,
      ),

      /// 탭바 클릭할 때 나오는 splash effect의 radius
      splashBorderRadius: BorderRadius.circular(20),

      /// 기본 인디캐이터의 컬러
      indicatorColor: Colors.black,

      /// indicator에서  UnderlineTabIndicator를 사용하지 않을 경우
      /// 0으로 설정할 것
      indicatorWeight: 5,

      /// 인디캐이터의 기본 사이즈를 label에 맞출지,
      /// 탭 좌우 사이즈에 맞출지 설정
      indicatorSize: TabBarIndicatorSize.label,

      /// 탭바의 상하좌우에 적용하는 패딩
      padding: const EdgeInsets.symmetric(
        horizontal: 20,
        vertical: 20,
      ),

      /// 라벨에 주는 패딩
      labelPadding: const EdgeInsets.symmetric(
        horizontal: 10,
        vertical: 10,
      ),

      /// 인디캐이터의 패딩
      indicatorPadding: const EdgeInsets.all(5),

      /// 커스텀 인디캐이터
      indicator: BoxDecoration(
        color: Colors.green.shade400,
        shape: BoxShape.circle,
        border: Border.all(
          color: Colors.orange,
          width: 5,
        ),
      ),

      isScrollable: true,

      onTap: (index) {},
      tabs: const [
        Tab(text: "Tab 1"),
        Tab(text: "Tab 2"),
        Tab(text: "Tab 3"),
        Tab(text: "Tab 4"),
        Tab(text: "Tab 5"),
        Tab(text: "Tab 6"),
        Tab(text: "Tab 7"),
        Tab(text: "Tab 8"),
      ],
    );
  }
}