【Flutter】TabBarを使って見やすいレイアウトを設計

対象者

  • Flutterを使った開発経験があり、より効率的で高度なUIを求めている方
  • TabBarの具体的な実装方法やカスタマイズの手法を学びたいと思っている方
  • 自己の開発スキルを向上させ、最新トレンドをキャッチアップしたい方

はじめに

TabBarをカスタマイズして、独自のUIを実装したいと考えたことはありますか?そんなあなたへ、この記事は、TabBarの更なる可能性を開く一歩となるでしょう。

TabBarというのは、日本語で「タブバー」または「タブのバー」という意味です。プログラムの世界では一般的に異なる画面や情報を切り替えるためのツールということを示します。
Flutterにおいては、ユーザーが容易に異なるビュー間を切り替えることができるウィジェットです。そのため、異なる内容や機能を一つの画面内で効率良く管理することができます。
実際のアプリとしては、SNSアプリケーションやニュースアプリケーションといったケースにおいて、異なるカテゴリーや機能に瞬時にアクセスできるというような機能を実現することができます。

あなたがFlutterを使用してアプリ開発を行っているなら、TabBarは、もはや見逃せない要素と言えるでしょう。しかし、これがあなたのUIを制約すると感じたことはありませんか?Flutterはその自由度と拡張性で知られています。この自由度と拡張性を最大限に活用して、TabBarをカスタマイズし、あなただけの個性的なTabBarを作り出しましょう。

TabBarの基本

TabBarの定義と役割

FlutterにおけるTabBarは、主要なコンテンツの移動先を表示する重要な役割を担っています。TabBarは、ユーザーがアプリケーションの異なるセクション間でナビゲートできるようにする役割を果たします。これにより、ユーザーは必要な情報にすばやくアクセスでき、アプリケーションの使いやすさが向上します。

実際には、以下のようなコードを使用してTabBarを定義することが可能です。

TabBar(
  tabs: [
    Tab(icon: Icon(Icons.directions_car)),
    Tab(icon: Icon(Icons.directions_transit)),
    Tab(icon: Icon(Icons.directions_bike)),
  ],
)

TabControllerとDefaultTabControllerの理解

TabBarを適切に機能させるためには、TabControllerまたはDefaultTabControllerの存在が必要です。これらはTabBarの振る舞いを管理し、タブの選択状態を調整します。TabControllerを直接使用することで、タブの選択やアニメーションなどの詳細な制御が可能となります。一方、DefaultTabControllerはTabControllerを階層的に提供し、複数のタブバーを連携させることが容易になります。

以下の例では、DefaultTabControllerを使用してTabBarとTabBarViewを連携させています。

DefaultTabController(
  length: 3,
  child: Scaffold(
    appBar: AppBar(
      bottom: TabBar(
        tabs: [
          Tab(icon: Icon(Icons.directions_car)),
          Tab(icon: Icon(Icons.directions_transit)),
          Tab(icon: Icon(Icons.directions_bike)),
        ],
      ),
    ),
    body: TabBarView(
      children: [
        Icon(Icons.directions_car),
        Icon(Icons.directions_transit),
        Icon(Icons.directions_bike),
      ],
    ),
  ),
)

TabBarのカスタマイズ可能なプロパティ

TabBarは多くのカスタマイズ可能なプロパティを持っています。これには、色、パディング、挙動などが含まれ、これにより開発者はアプリのデザインやユーザー体験を自由に設計することが可能となります。例えば、indicatorColorプロパティを使用すると、選択中のタブの下に表示されるインジケータの色を変更することができます。同様に、タブ自体の色や、ラベルのスタイルも変更することが可能です。

以下のコードスニペットは、いくつかのカスタマイズ可能なプロパティを使用してTabBarを定義する例です。

TabBar(
  indicatorColor: Colors.red,
  labelColor: Colors.black,
  tabs: [
    Tab(icon: Icon(Icons.directions_car)),
    Tab(icon: Icon(Icons.directions_transit)),
    Tab(icon: Icon(Icons.directions_bike)),
  ],
)

以上が、FlutterでのTabBarの基本的な理解と使用法になります。これを理解し活用することで、より直感的で使いやすいアプリケーションを作成することが可能になります。

TabBarを用いたレイアウトの作成

TabControllerの作成方法

TabControllerは、FlutterアプリケーションのTabBarの挙動を管理します。TabControllerの作成は非常にシンプルで、基本的にはコントローラが管理するタブの数を指定します。

以下の例では、TabControllerを直接作成し、3つのタブを管理します。

late final TabController _tabController = TabController(length: 3, vsync: this);

ここでvsync: thisというオプションは、アニメーションの同期を保つために使用されます。このオプションを設定するためには、通常、ミックスインSingleTickerProviderStateMixinを使用して親ウィジェットをアニメーションのvsyncに設定します。

TabBarとTabBarViewウィジェットの使用方法

TabBarとTabBarViewウィジェットは、一緒に使用することで、ユーザーがタブをタップすると表示されるコンテンツを切り替えることができます。

以下のコードスニペットでは、TabBarとTabBarViewをTabControllerとともに使用しています。この例では、3つのタブが作成され、それぞれに対応するTabBarViewが設定されています。

DefaultTabController(
  length: 3,
  child: Scaffold(
    appBar: AppBar(
      bottom: TabBar(
        tabs: [
          Tab(icon: Icon(Icons.directions_car)),
          Tab(icon: Icon(Icons.directions_transit)),
          Tab(icon: Icon(Icons.directions_bike)),
        ],
      ),
    ),
    body: TabBarView(
      children: [
        Icon(Icons.directions_car),
        Icon(Icons.directions_transit),
        Icon(Icons.directions_bike),
      ],
    ),
  ),
)

各タブのコンテンツの設定

TabBarViewウィジェットのchildrenプロパティを用いて各タブのコンテンツを設定します。これはウィジェットのリストであり、リストの各ウィジェットが各タブに対応します。ユーザーがタブを切り替えると、対応するウィジェットが表示されます。

上記の例では、各タブのコンテンツとして、異なるアイコンを表示しています。これらのウィジェットは、任意の複雑さを持つことができ、例えば画像、テキスト、リスト、またはカスタムウィジェットを含むことが可能です。

以上の内容を理解し適用することで、ユーザーがタブをタップしてアプリケーションの異なるセクション間を移動する、直感的なレイアウトを作成することが可能になります。

Tabウィジェットの活用

アイコンとテキストを持つTabの作成

Flutterでのタブ作成は、非常にシンプルかつ柔軟性が高いです。アイコンやテキストなどを持つタブはTabウィジェットを使用して簡単に作成できます。これはユーザーがタブの内容を一目で理解できるようにするための重要な要素であり、ユーザビリティの向上に直結します。

具体的な実装は以下の通りです:

Tab(
  icon: Icon(Icons.directions_car),
  text: 'Car',
)

このコードスニペットは、車のアイコンとテキスト’Car’を持つタブを作成します。非常に直感的なコードであり、アイコンやテキストを任意に組み合わせることができます。

TabとTabBar、TabBarView、TabControllerの連携

FlutterのTabBar、TabBarView、TabControllerは、それぞれが連携することで一体となって動作します。TabはTabBar内で使用され、どのタブがアクティブなのかを示します。TabBarViewはタブを切り替えると表示が切り替わるエリアを提供し、TabControllerはTabBarとTabBarView間の状態を管理します。

以下はその一例です:

DefaultTabController(
  length: 3,
  child: Scaffold(
    appBar: AppBar(
      bottom: TabBar(
        tabs: [
          Tab(icon: Icon(Icons.directions_car), text: 'Car'),
          Tab(icon: Icon(Icons.directions_transit), text: 'Transit'),
          Tab(icon: Icon(Icons.directions_bike), text: 'Bike'),
        ],
      ),
    ),
    body: TabBarView(
      children: [
        Icon(Icons.directions_car),
        Icon(Icons.directions_transit),
        Icon(Icons.directions_bike),
      ],
    ),
  ),
)

この例では、3つのタブ(Car、Transit、Bike)がTabBar内に定義されています。ユーザーがこれらのタブを切り替えると、TabBarView内の表示も対応するアイコンに切り替わります。この全ての動作はTabControllerによって制御されます。

このように、Tab、TabBar、TabBarView、TabControllerの連携はFlutterでタブレイアウトを実現する上で重要であり、これらのウィジェットを適切に組み合わせることで、ユーザビリティの高いアプリケーションを開発することが可能となります。

TabBarのカスタマイズ

タブのインジケータのカスタマイズ方法

Flutterにおけるタブインジケータのカスタマイズは、ユーザーのアプリ体験を強化する重要な手段です。デフォルトでは、選択中のタブを示す線が表示されますが、indicatorプロパティを利用して、色、厚み、長さなどを自由に変更できます。

TabBar(
  indicatorColor: Colors.red,
  indicatorWeight: 5.0,
  tabs: myTabs,
)

この例では、赤色で5.0の厚さのインジケータが設定されます。アプリのブランドイメージやデザインに合わせて、このようなカスタマイズが可能です。

スクロール可能なタブの作成

タブの数が多くなり、画面に収まりきらない場合、スクロール可能なタブの作成が有効です。これは、isScrollableプロパティをtrueに設定することで可能です。

TabBar(
  isScrollable: true,
  tabs: myTabs,
)

これにより、ユーザーは画面をスワイプしてさまざまなタブを表示でき、アプリケーションの操作性が大幅に向上します。

プログラム的にタブを変更する方法

ユーザーが直接タブを選択するだけでなく、アプリケーション内の他の操作に応じてプログラム的にタブを切り替えることも可能です。これは、TabControlleranimateTo()メソッドを使用して行います。

_tabController.animateTo(2);

この例では、タブを3番目の位置にプログラム的に切り替えています。この機能を使えば、アプリケーションのユーザー体験をさらに拡張できます。

タブの変更をリッスンする方法

タブの変更は重要なイベントであり、それに応じて特定のアクションをトリガすることがよくあります。Flutterでは、TabControllerを使用してタブの変更をリッスンすることができます。

_tabController.addListener(() {
  if (_tabController.indexIsChanging) {
    // Perform some action here on tab change
  }
});

このように、TabBarのカスタマイズと適切な利用により、アプリケーションの柔軟性とユーザービリティを大幅に向上させることができます。Flutter開発者として、これらのテクニックを身につけ、効果的に活用することが重要です。

Q&A

Q1: TabBarとDefaultTabControllerの主な違いは何ですか?

TabBarはFlutterでタブ切り替えUIを作るためのウィジェットです。TabBar自体は、タブの見た目と動作を提供します。それぞれのタブはTabウィジェットで作成し、TabBarのtabsプロパティにリストとして設定します。

一方、DefaultTabControllerはTabControllerを提供するウィジェットで、TabBarとTabBarViewを同期させる役割を果たします。特に、自身でTabControllerを管理せずにタブの切り替え機能を実装したい場合に便利です。

Q2: スクロール可能なタブを作成するにはどうすればよいですか?

TabBarのisScrollableプロパティをtrueに設定することで、スクロール可能なタブを作成できます。これはタブが多く、画面に収まりきらない場合などに有用です。

Q3: プログラム的にタブを切り替える方法は?

TabControllerのanimateToメソッドを使用することで、プログラム的にタブを切り替えることができます。引数には切り替えたいタブのインデックスを指定します。このメソッドを用いると、ユーザが直接タブを選択しなくても、例えば特定のボタンが押されたときなどにタブを切り替えることができます。

まとめ

私たちはFlutterのTabBarの基本を学びました。TabBarの定義と役割、そしてTabControllerとDefaultTabControllerの理解について深く探求しました。さらに、TabBarのカスタマイズ可能なプロパティについても見てきました。

次に、TabBarを用いたレイアウトの作成に焦点を当て、TabControllerの作成方法、TabBarとTabBarViewウィジェットの使用方法、そして各タブのコンテンツの設定について詳しく見ました。
TabBarのカスタマイズにおいては、タブのインジケータのカスタマイズ方法、スクロール可能なタブの作成、プログラム的にタブを変更する方法、そしてタブの変更をリッスンする方法について理解を深めました。
最後に、アクティブページの追跡とハイライト表示についても見てきました。

参考

ソース(main.dartにコピペして動作確認用)

基本(DefaultTabController使用)

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: DefaultTabController(
        length: 3,
        child: Scaffold(
          appBar: AppBar(
            bottom: TabBar(
              tabs: [
                Tab(icon: Icon(Icons.directions_car), text: "Car"),
                Tab(icon: Icon(Icons.directions_transit), text: "Transit"),
                Tab(icon: Icon(Icons.directions_bike), text: "Bike"),
              ],
            ),
            title: Text('TabBar Demo'),
          ),
          body: TabBarView(
            children: [
              Icon(Icons.directions_car),
              Icon(Icons.directions_transit),
              Icon(Icons.directions_bike),
            ],
          ),
        ),
      ),
    );
  }
}

応用(TabController 使用)

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
  late final _tabController = TabController(length: 9, vsync: this);
  var _title = 'title';
  @override
  void initState() {
    super.initState();
    _tabController.addListener(() {
      if (_tabController.indexIsChanging) {
        setState(() {
          _title = '${_tabController.previousIndex} -> ${_tabController.index}';
        });
      }
    });
  }

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text(_title),
          bottom: TabBar(
            controller: _tabController,
            isScrollable: true,
            tabs: [
              Tab(icon: Icon(Icons.directions_car), text: "Car"),
              Tab(icon: Icon(Icons.directions_transit), text: "Transit"),
              Tab(icon: Icon(Icons.directions_bike), text: "Bike"),
              Tab(icon: Icon(Icons.directions_car), text: "Car"),
              Tab(icon: Icon(Icons.directions_transit), text: "Transit"),
              Tab(icon: Icon(Icons.directions_bike), text: "Bike"),
              Tab(icon: Icon(Icons.directions_car), text: "Car"),
              Tab(icon: Icon(Icons.directions_transit), text: "Transit"),
              Tab(icon: Icon(Icons.directions_bike), text: "Bike"),
            ],
          ),
        ),
        body: TabBarView(
          controller: _tabController,
          children: [
            Icon(Icons.directions_car),
            Icon(Icons.directions_transit),
            Icon(Icons.directions_bike),
            Icon(Icons.directions_car),
            Icon(Icons.directions_transit),
            Icon(Icons.directions_bike),
            Icon(Icons.directions_car),
            Icon(Icons.directions_transit),
            Center(
              child: FilledButton(
                  onPressed: () => _tabController.animateTo(0),
                  child: Text('First car')),
            ),
          ],
        ),
      ),
    );
  }
}