Foggy day

[Flutter] DefaultTabController, TabBar, TabBarView Widget - 사용법 본문

Flutter/Flutter widget

[Flutter] DefaultTabController, TabBar, TabBarView Widget - 사용법

jinhan38 2023. 3. 27. 20:00

 

 

이번 포스팅에서는 DefaultTabController, TabBar, TabBarView에 대해 알아보겠습니다. TabBar는 따로 상세히 다룬 페이지가 있으니 확인해 주시기 바랍니다.

 

 

DefaultTabController, TabBar, TabBarView는 좌우 스와이프가 가능한 탭 페이지 형식의 UI를 만들어줍니다. 아래 영상으로 먼저 어떤 형태인지 확인해보겠습니다. 

 

 

 

 

많은 앱들에서 볼 수 있는 구조입니다. 중요한 것은 TabBar와 하위 페이지들을 어떻게 연결시킬건가 입니다. 그런데 DefaultTabController와 TabBarView를 사용하면 손쉽게 연결할 수 있습니다. TabBarView는 PageView와 동일한 기능을 하지만 TabBar와 연결이 수월합니다. TabBar와 PageView를 따로 사용한다면 각각의 이벤트를 캐치해서 연결시켜줘야 하는데 TabBarView, TabCotnroller, DefaultTaController를 사용하면 간단히 구현할 수 있습니다.

 

 

 

 

1. TabBar

2. TabBarView

3. DefaultTabController

4. TabController

 

 

 

 

1. TabBar

먼저 TabBar를 만들겠습니다. TabBar에 대한 자세한 내용은 따로 포스팅했으니 확인해주시기 바랍니다. 이번 포스팅에서는 설명 없이 넘어가겠습니다.

 

https://jinhan38.tistory.com/140

 

[Flutter] TabBar Widget - 사용법

이번 포스팅에서는 TabBar 위젯에 대해 알아보겠습니다. TabBar위젯은 주로 여러 페이지를 동시에 사용할 때 페이지 전환을 위해 사용합니다. 때문에 혼자 사용되는 경우는 거의 없고, PageView와 같

jinhan38.com

 

  Widget _tabBar() {
    return const TabBar(
      labelColor: Colors.black,
      unselectedLabelColor: Colors.grey,
      labelStyle: TextStyle(
        fontSize: 18,
        fontWeight: FontWeight.bold,
      ),
      unselectedLabelStyle: TextStyle(
        fontSize: 16,
      ),
      tabs: [
        Tab(text: "Tab 1"),
        Tab(text: "Tab 2"),
        Tab(text: "Tab 3"),
      ],
    );
  }

 

 

 

2. TabBarView

TabBarView는 PageView와 비슷한 위젯이라고 이해하면 됩니다. 대신 사용할 수 있는 특성이 적습니다.

예제에서는 children에 3개의 Container를 넣었고, 컬러와 텍스트만 다르게 했습니다.

  Widget _tabBarView() {
    return TabBarView(
      children: [
        Container(
          color: Colors.red,
          alignment: Alignment.center,
          child: const Text(
            "Page 1",
            style: TextStyle(fontSize: 40, color: Colors.white),
          ),
        ),
        Container(
          color: Colors.blue,
          alignment: Alignment.center,
          child: const Text(
            "Page 2",
            style: TextStyle(fontSize: 40, color: Colors.white),
          ),
        ),
        Container(
          color: Colors.green,
          alignment: Alignment.center,
          child: const Text(
            "Page 3",
            style: TextStyle(fontSize: 40, color: Colors.white),
          ),
        ),
      ],
    );
  }

 

 

 

3. DefaultTabController

DefaultTabController는 TabBar와 TabBarView를 연결하는 역할을 합니다. DefaultTabController는 자식 위젯으로 child를 받습니다. 그래서 주로 Column위젯을 사용해 그 안에 TabBar와 TabBarView를 넣습니다. 두 위젯이 DefaultTabController의 하위에 있기만 하면 정상적으로 연결됩니다. 

Column 위젯에 대해서는 따로 자세히 다뤘으니 확인해 보시기 바랍니다.
https://jinhan38.tistory.com/127

 

[Flutter] Column - 사용법

Flutter에서 가장 많이 사용하는 위젯 중 하나인 Column 위젯에 대해 알아보겠습니다. Column은 자식 위젯들을 세로로 배치시킬 수 있는 LayoutWidget입니다. 1. 기본 red, blue 컬러의 컨테이너 두개를 Column

jinhan38.com

 

  Widget _body() {
    return DefaultTabController(
      animationDuration: const Duration(milliseconds: 300),
      length: 3,
      child: Column(
        children: [
          _tabBar(),

          /// Expanded 없으면 오류 발생
          /// Horizontal viewport was given unbounded height.
          Expanded(child: _tabBarView()),
        ],
      ),
    );
  }

animationDuration은 탭을 클릭해서 TabBarView의 화면을 변경할 때의 속도입니다. 

 

 

 

여기까지 잘 따라왔다면 오른쪽 화면이 그려집니다. 앞선 영상처럼 Tab을 클릭하거나 TabBarView를 스와이프 하면 화면이 전환됩니다. 그리고 TabBar와 TabBarView가 순서에 맞게 연결됩니다. 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

4. TabController

그런데 위의 예제에서 보면 TabBar와 TabBarView에 controller를 사용하지 않고 있습니다. DefaultTabController가 TabController를 사용하지 않아도 두 위젯을 연결시켜 주기 때문입니다. 하지만 프로그래밍적으로 페이지를 이동시키거나 현재 상태를 알아야 할 경우 TabController를 사용해야 합니다. 

 

TabBar와 TabBarView는 둘 다 TabController를 사용합니다. 그리고 연결시킬 TabBar와 TabBarView에 동일한 TabController를 사용해야 합니다. 그런데 재미있는 것은 동일한 TabController를 사용하면 DefaultTabController가 없어도 두 위젯이 연결된다는 것입니다. 

 

아래 코드에서는 DefaultTabController를 제거 했습니다. 하지만 TabController를 사용했기 때문에 TabBar와 TabBarView Widget이 연결 됐고, tabController를 사용해서 페이지 전환도 가능합니다. 

 

TabController를 사용하려면 with SingleTickerProviderStateMixin을 추가해야 합니다. 설명은 TabBar 페이지에서 좀 더 자세히 설명하고 있으니 확인해 보시기 바랍니다.

 

class _DefaultTabControllerScreenState extends State<DefaultTabControllerScreen>
    with SingleTickerProviderStateMixin {
    
  late TabController tabController = TabController(length: 3, vsync: this);
    

   ...



  
  // TabController를 사용하면 DefaultTabController 없이도 연결이 된다.
  // 반대로 DefaultTabController 사용하면 TabController 없이 연결이 된다.
  Widget _body() {
    return Column(
      children: [
        _pageMoveButton(),
        _tabBar(),

        // Expanded 없으면 오류 발생
        // Horizontal viewport was given unbounded height.
        Expanded(child: _tabBarView()),
      ],
    );
  }

  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,
      ),
      tabs: const [
        Tab(text: "Tab 1"),
        Tab(text: "Tab 2"),
        Tab(text: "Tab 3"),
      ],
    );
  }

  Widget _tabBarView() {
    return TabBarView(
      controller: tabController,
      children: [
        Container(
          color: Colors.red,
          alignment: Alignment.center,
          child: const Text(
            "Page 1",
            style: TextStyle(fontSize: 40, color: Colors.white),
          ),
        ),
        Container(
          color: Colors.blue,
          alignment: Alignment.center,
          child: const Text(
            "Page 2",
            style: TextStyle(fontSize: 40, color: Colors.white),
          ),
        ),
        Container(
          color: Colors.green,
          alignment: Alignment.center,
          child: const Text(
            "Page 3",
            style: TextStyle(fontSize: 40, color: Colors.white),
          ),
        ),
      ],
    );
  }
  

  Widget _pageMoveButton() {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      children: [
        ElevatedButton(
          onPressed: () {
            /// TabBar는 애니메이션 없이 바로 이동하지만
            /// TabBarView는 화면 전환 애니메이션 발생
            tabController.index = 2;
          },
          child: const Text("페이지 이동"),
        ),
        ElevatedButton(
          onPressed: () {
            /// TabBar와 TabBarView 화면 전환 애니메이션 발생
            tabController.animateTo(2);
          },
          child: const Text("페이지 이동(애니메이션)"),
        ),
      ],
    );
  }

 

 

 

 

 

full code

import 'package:flutter/material.dart';

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

  @override
  State<DefaultTabControllerScreen> createState() =>
      _DefaultTabControllerScreenState();
}

class _DefaultTabControllerScreenState extends State<DefaultTabControllerScreen>
    with SingleTickerProviderStateMixin {
  late TabController tabController = TabController(length: 3, vsync: this);

  @override
  void initState() {
    tabController.addListener(() {});
    super.initState();
  }

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

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

  // TabController 의 여부가 중요하다
  // TabController를 사용하면 DefaultTabController 없이도 연결이 된다.
  // 반대로 DefaultTabController 사용하면 TabController 없이 연결이 된다.
  Widget _body() {
    return Column(
      children: [
        _pageMoveButton(),
        _tabBar(),

        // Expanded 없으면 오류 발생
        // Horizontal viewport was given unbounded height.
        Expanded(child: _tabBarView()),
      ],
    );
    return DefaultTabController(
      animationDuration: const Duration(milliseconds: 300),
      length: 3,
      child: Column(
        children: [
          // _pageMoveButton(),
          _tabBar(),

          /// Expanded 없으면 오류 발생
          /// Horizontal viewport was given unbounded height.
          Expanded(child: _tabBarView()),
        ],
      ),
    );
  }

  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,
      ),
      tabs: const [
        Tab(text: "Tab 1"),
        Tab(text: "Tab 2"),
        Tab(text: "Tab 3"),
      ],
    );
  }

  Widget _tabBarView() {
    return TabBarView(
      controller: tabController,
      children: [
        Container(
          color: Colors.red,
          alignment: Alignment.center,
          child: const Text(
            "Page 1",
            style: TextStyle(fontSize: 40, color: Colors.white),
          ),
        ),
        Container(
          color: Colors.blue,
          alignment: Alignment.center,
          child: const Text(
            "Page 2",
            style: TextStyle(fontSize: 40, color: Colors.white),
          ),
        ),
        Container(
          color: Colors.green,
          alignment: Alignment.center,
          child: const Text(
            "Page 3",
            style: TextStyle(fontSize: 40, color: Colors.white),
          ),
        ),
      ],
    );
  }

  Widget _pageMoveButton() {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      children: [
        ElevatedButton(
          onPressed: () {
            /// TabBar는 애니메이션 없이 바로 이동하지만
            /// TabBarView는 화면 전환 애니메이션 발생
            tabController.index = 2;
          },
          child: const Text("페이지 이동"),
        ),
        ElevatedButton(
          onPressed: () {
            /// TabBar와 TabBarView 화면 전환 애니메이션 발생
            tabController.animateTo(2);
          },
          child: const Text("페이지 이동(애니메이션)"),
        ),
      ],
    );
  }
}