【Flutter Web】 go_routerでのタブ実装(タブとURLパスを連動して切り替える)
はじめに
はじめまして、ヤスーです。
普段はメディア系のSIer、趣味でFlutterアプリ開発を行なっています。
今回はFlutter Webのタブ実装で、タブを切り替えた際にURLパスも連動して切り替える実装を記事にしてみました。
通常のモバイルアプリでは、タブの画面遷移でURLを意識する必要はありません。しかし、Webになるとタブを切り替えた際にURLも変更して画面遷移したい場面が出てくるので、その方法を載せています。
画面遷移はgo_routerを用いた実装になっています。
サンプルコードは以下に格納していますので、ぜひこちらも合わせて参考にしていただけると幸いです。
目次
- パッケージのインストール
- Flutter Webにおけるタブの実装
- おわりに
1. パッケージのインストール
画面遷移で必要になるgo_routerをインストールしていきます。
Flutterプロジェクトのpubspec.yamlに、go_routerを追加してpub get
します。
dependencies:
flutter:
sdk: flutter
+ go_router: ^6.0.1
2. Flutter Webにおけるタブの実装
今回は3つのタブを用意します。
各タブは「/myPage/tab1」「/myPage/tab2」「/myPage/tab3」のURLパスを持つようにして、タブを切り替えるとURLと画面が切り替わるような実装をおこないます。
※タブの実装と関係ない部分は省いて説明しますので、ご了承ください。
2.1 各タブで表示する画面を作成
各タブで表示する画面ページを用意する。
import 'package:flutter/material.dart';
///[/myPage/tab1]のパスで表示する画面
class Tab1Page extends StatelessWidget {
const Tab1Page({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return const Center(child: Text('1'));
}
}
///[/myPage/tab2]のパスで表示する画面
class Tab2Page extends StatelessWidget {
const Tab2Page({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return const Center(child: Text('2'));
}
}
///[/myPage/tab3]のパスで表示する画面
class Tab3Page extends StatelessWidget {
const Tab3Page({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return const Center(child: Text('3'));
}
}
2.2 タブ情報をリストデータで保持するクラスを作成
tab_items.dartに以下のようなタブに必要な情報を書く。
import 'package:flutter/material.dart';
import 'package:flutter_web_tab/tab.dart';
/// Tabの文字とクリックされた時表示するviewを格納するデータクラス
class TabItem {
const TabItem({required this.tabId, required this.tab, required this.view});
/// タブ固有のID
final String tabId;
/// 各タブのウィジェット
final Tab tab;
/// 各タブで表示する画面ページ
final Widget view;
}
/// MyPageのタブのリストデータ用クラス
class MyPageTabs {
static final data = [
// tab1のタブ情報
const TabItem(
tabId: "tab1",
tab: Tab(
child: Text(
'タブ1',
),
),
view: Tab1Page()),
// tab2のタブ情報
const TabItem(
tabId: "tab2",
tab: Tab(
child: Text(
'タブ2',
),
),
view: Tab2Page(),
),
// tab3のタブ情報
const TabItem(
tabId: "tab3",
tab: Tab(
child: Text(
'タブ3',
),
),
view: Tab3Page(),
),
];
}
TabItem
クラスは以下のフィールド要素を持つ。
tabId
:タブの固有ID
tab
:各タブのウィジェット
view
:各タブで表示する画面ウィジェット
MyPageTabs
クラスはTabItem
をリストで保持する。
tab
とview
は以下の画像で示す部分を表す。
2.3 タブのウィジェットを作成
tab_widget.dartを作成して、タブ切り替えができるウィジェットを以下のように作成します。
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:sukimachi/presentation/my_page/tabs/tab_item.dart';
import 'package:sukimachi/presentation/my_page/tabs/my_page_tabs_component.dart';
/// MyPageの表示情報を切り替えるタブ
///
/// 依頼情報や受注情報のみに切り替えるために使用する。
class MyPageBodyTab extends StatefulWidget {
const MyPageBodyTab({Key? key, required this.currentTab, required this.index})
: super(key: key);
final TabItem currentTab;
final int index;
_MyPageBodyTabState createState() => _MyPageBodyTabState();
}
class _MyPageBodyTabState extends State<MyPageBodyTab>
with SingleTickerProviderStateMixin {
late TabController _tabController;
void initState() {
super.initState();
_tabController = TabController(
length: MyPageTabs.data.length,
vsync: this,
initialIndex: widget.index,
);
}
void dispose() {
_tabController.dispose();
super.dispose();
}
void didUpdateWidget(MyPageBodyTab oldWidget) {
super.didUpdateWidget(oldWidget);
_tabController.index = widget.index;
}
Widget build(BuildContext context) {
return ListView(
shrinkWrap: true,
children: [
Center(
child: TabBar(
controller: _tabController,
indicatorColor: Colors.black,
indicatorSize: TabBarIndicatorSize.label,
labelColor: Colors.black,
unselectedLabelColor: Colors.grey.withOpacity(0.6),
isScrollable: true,
onTap: (int index) {
_onTap(context, index);
},
tabs: [for (TabItem t in MyPageTabs.data) t.tab],
),
),
// tabのボディ
IndexedStack(
alignment: Alignment.center,
index: widget.index,
children: [
for (final t in MyPageTabs.data)
Visibility(
child: t.view,
visible: widget.index ==
MyPageTabs.data
.indexWhere((tab) => tab.tabId == t.tabId)),
],
)
],
);
}
void _onTap(BuildContext context, int index) {
context.go('/myPage/${MyPageTabs.data[index].tabId}');
}
}
以下はtab_widget.dartの解説になります。
initStateで、TabControllerを作成します。
void initState() {
super.initState();
_tabController = TabController(
length: MyPageTabs.data.length,
vsync: this,
initialIndex: widget.index,
);
}
didUpdateWidgetは、親のウィジェットが再構築される時にコール関数になります。
この際に、tabController.index
をURLのパスで指定したタブ番号に随時変更してあげることで、タブ毎に表示する画面を変更しています。
@override
void didUpdateWidget(MyPageBodyTab oldWidget) {
super.didUpdateWidget(oldWidget);
_tabController.index = widget.index;
}
Widgetはスクロール可能なタブ画面とするためにListViewで囲っています。
また、URLのパスで指定されたウィジェットのみをvisibleで設定することでタブの切り替えを実現しています。
@override
Widget build(BuildContext context) {
return ListView(
shrinkWrap: true,
children: [
Center(
child: TabBar(
controller: _tabController,
indicatorColor: Colors.black,
indicatorSize: TabBarIndicatorSize.label,
labelColor: Colors.black,
unselectedLabelColor: Colors.grey.withOpacity(0.6),
isScrollable: true,
onTap: (int index) {
_onTap(context, index);
},
tabs: [for (TabItem t in MyPageTabs.data) t.tab],
),
),
// tabのボディ
IndexedStack(
alignment: Alignment.center,
index: widget.index,
children: [
for (final t in MyPageTabs.data)
Visibility(
child: t.view,
visible: widget.index ==
MyPageTabs.data
.indexWhere((tab) => tab.tabId == t.tabId)),
],
)
],
);
}
2.4 タブウィジェットを配置する画面を作成
今回はMyPageにタブ切り替えのウィジェットを導入する。
import 'package:flutter/material.dart';
import 'package:flutter_web_tab/tab_items.dart';
+ import 'package:flutter_web_tab/tab_widget.dart';
/// マイページ画面
class MyPage extends StatelessWidget {
const MyPage({Key? key, required this.currentTab, required this.index})
: super(key: key);
final TabItem currentTab;
final int index;
Widget build(BuildContext context) {
return Scaffold(
+ body: MyPageTabWidget(
+ currentTab: currentTab,
+ index: index,
+ ),
);
}
}
MyPageの引数には、現在表示しているタブ情報とタブの番号(何番目のタブであるかの情報)を設定して、外部から受け取れるようにする。これは、次のgo_routerの設定で外部から受け取るのに必要である。
2.5 go_routerでタブ切り替えするための画面遷移設定
router.dartに以下を追記する。
import 'package:flutter/material.dart';
import 'package:flutter_web_tab/home_page.dart';
import 'package:go_router/go_router.dart';
+ import 'package:flutter_web_tab/my_page.dart';
+ import 'package:flutter_web_tab/tab_items.dart';
/// 画面遷移の情報を定義する
final router = GoRouter(
initialLocation: "/",
routes: [
GoRoute(
path: "/",
pageBuilder: (context, state) => MaterialPage(
key: state.pageKey,
child: const HomePage(),
),
),
+ ///MyPageのタブに表示される情報の切り替えを行うためのGoRouteの設定
+ //[/myPage/1],[/myPage/2],[/myPage/3]のパスに切り替える。
+ //1,2,3はmyPageIdを示す。
+ GoRoute(
+ name: 'myPage',
+ path: '/myPage/:myPageId"',
+ pageBuilder: (context, state) {
+ final myPageId = state.params['myPageId'];
+ final currentTab =
+ MyPageTabs.data.firstWhere((tab) => tab.tabId == myPageId);
+ final index =
+ MyPageTabs.data.indexWhere((t) => t.tabId == currentTab.tabId);
+ return MaterialPage(
+ key: state.pageKey,
+ child: MyPage(
+ currentTab: currentTab,
+ index: index,
+ ),
+ );
+ }),
],
);
currentTab
は、URLパスのmyPageId
と一致するIDを持ったTabItem
が格納されています。
TabItem
はtab_items.dartのMyPageTabs
に格納されている情報になります。
また、index
はタブ順の番号が格納されている。
3. おわりに
最後までお読みいただきありがとうございました。
go_routerを用いたBottomNavigationBarの画面遷移実装の記事は幾つかあったのですが、Webでのタブ切り替えの記事はあまりみたことがなかったので書いてみた次第です。
参考になれば幸いです。
参考
Discussion