【Flutter/Dart】画面下にタブバーを表示して画面を切り替える

目次

概要

画面下にボタンを羅列して画面を切り替える方法がある。その方法が今回の記事。
まず、TabBarを表示するベースとなる画面を作る。
その画面とは別に、その表示する画面のファイルを作成して、ベースとなる画面で表示するという本心だ。
ファイルの数が多いが、「表示する画面」の4つファイルは同じような構成のため、数に圧倒されないで欲しい。
そして、今回は画像を表示しているが、画像でなくてもTextを表示するだけでも何も問題ない。

ソースコード

アプリ実行部分

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

import 'package:concentration/TabBar/MainTabBarView.dart';

void main() async{

  runApp(ProviderScope(child: MyApp()));
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
        useMaterial3: true,
      ),
      home: MainTabBarView(),
    );
  }
}

画面全体

import 'package:concentration/TabBar/Screens/FourthView.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

import 'MainTabBarViewModel.dart';
import 'Screens/FirstView.dart';
import 'Screens/SecondView.dart';
import 'Screens/ThirdView.dart';

class MainTabBarView extends ConsumerWidget {
  MainTabBarView({Key? key}) : super(key: key);
  MainTabBarViewModel _viewModel = MainTabBarViewModel();

  List<Widget> display = [
    const FirstView(),
    const SecondView(),
    const ThirdView(),
    const FourthView()
  ];

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    _viewModel = ref.watch(mainTabModelProvider);

    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(_viewModel.title),
      ),
      body: display[_viewModel.currentIndex],
      bottomNavigationBar: BottomNavigationBar(
        items: const [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: 'First'),
          BottomNavigationBarItem(icon: Icon(Icons.chat), label: 'Second'),
          BottomNavigationBarItem(icon: Icon(Icons.search), label: 'Third'),
          BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Fourth'),
        ],
        currentIndex: _viewModel.currentIndex,
        onTap: (int index) {
          _viewModel.selectedTab(index);
        },
        selectedItemColor: Colors.white,
        unselectedItemColor: Colors.white54,
        backgroundColor: Colors.blue,
        type: BottomNavigationBarType.fixed,
      ),
    );
  }
}
import 'package:flutter/widgets.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

import 'MainTabBarRepository.dart';

final mainTabModelProvider = ChangeNotifierProvider((ref) => MainTabBarViewModel(repository: ref.read(MainTabBarRepositoryProvider)));

class MainTabBarViewModel extends ChangeNotifier {
  MainTabBarRepository? repository;

  int currentIndex = 0;
  String title = "First";

  MainTabBarViewModel({this.repository});

  void selectedTab(int index) {
    currentIndex = index;
    switch (currentIndex) {
      case 0:
        title = "First";
      case 1:
        title = "Second";
      case 2:
        title = "Third";
      case 3:
        title = "Fourth";
    }
    notifyListeners();
  }
}
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'MainTabBarModel.dart';

final MainTabBarRepositoryProvider = Provider((ref) => MainTabBarRepositoryImpl(model: ref.read(mainTabBarModelProvider)));

abstract class MainTabBarRepository {
  
}

class MainTabBarRepositoryImpl implements MainTabBarRepository {
  MainTabBarRepositoryImpl({required MainTabBarModel model}): _model = model;

  final MainTabBarModel _model;
}
import 'package:flutter_riverpod/flutter_riverpod.dart';

final mainTabBarModelProvider = Provider((ref) => MainTabBarModel());

class MainTabBarModel {

}

表示する画面

libと同じ階層にあるimagesディレクトリに画像を以下の名前の画像を追加しておく。

  • 01_img.PNG
  • 02_img.PNG
  • 03_img.PNG
  • 04_img.PNG

画像の追加方法はこちら

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

class FirstView extends ConsumerWidget {
  const FirstView({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
        body: Container(
          alignment: Alignment.center,
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Container(
                  width: 200,
                  height: 200,
                  decoration: const BoxDecoration(
                      image: DecorationImage(
                        image: AssetImage('images/01_img.PNG'),
                        fit: BoxFit.contain,
                      )
                  )
              ),
            ],
          ),
        )
    );
  }
}
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

class SecondView extends ConsumerWidget {
  const SecondView({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
        body: Container(
          alignment: Alignment.center,
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Container(
                  width: 200,
                  height: 200,
                  decoration: const BoxDecoration(
                      image: DecorationImage(
                        image: AssetImage('images/02_img.PNG'),
                        fit: BoxFit.contain,
                      )
                  )
              ),
            ],
          ),
        )
    );
  }
}
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

class ThirdView extends ConsumerWidget {
  const ThirdView({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
        body: Container(
          alignment: Alignment.center,
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Container(
                  width: 200,
                  height: 200,
                  decoration: const BoxDecoration(
                      image: DecorationImage(
                        image: AssetImage('images/03_img.PNG'),
                        fit: BoxFit.contain,
                      )
                  )
              ),
            ],
          ),
        )
    );
  }
}
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

class FourthView extends ConsumerWidget {
  const FourthView({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
        body: Container(
          alignment: Alignment.center,
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Container(
                  width: 200,
                  height: 200,
                  decoration: const BoxDecoration(
                      image: DecorationImage(
                        image: AssetImage('images/04_img.PNG'),
                        fit: BoxFit.contain,
                      )
                  )
              ),
            ],
          ),
        )
    );
  }
}

デモ動画

詳細

今回の主役はこちらだ。

List<Widget> display = [
  const FirstView(),
  const SecondView(),
  const ThirdView(),
  const FourthView()
];
bottomNavigationBar: BottomNavigationBar(
  items: const [
    BottomNavigationBarItem(icon: Icon(Icons.home), label: 'First'),
    BottomNavigationBarItem(icon: Icon(Icons.chat), label: 'Second'),
    BottomNavigationBarItem(icon: Icon(Icons.search), label: 'Third'),
    BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Fourth'),
  ],
  currentIndex: _viewModel.currentIndex,
  onTap: (int index) {
    _viewModel.selectedTab(index);
  },
  selectedItemColor: Colors.white,
  unselectedItemColor: Colors.white54,
  backgroundColor: Colors.blue,
  type: BottomNavigationBarType.fixed,
),
引数名設定内容
bottomNavigationBarBottomNavigationBarオブジェクトを設定する
 itemsBottomNavigationBarItemをメニューの数だけ格納する
 currentIndex在の表示している画面の番号を設定する。(displayの添字)
 selectedItemColor選択されている時のボタンの色を設定する
 unselectedItemColor選択されていない時のボタンの色を設定する
 backgroundColorタブバーの背景色を設定する
 typeBottomNavigationBarTypeアイコンの表示方法を設定する

まず、こうすることで画面の下にタブバーを表示することができる。
では、今度は更新処理だ。今回はアーキテクチャのことも考えて、 Repositoryクラスなどを作成している。

まず、タブバーのボタンを押下すると以下の処理が行われる。

onTap: (int index) {
  _viewModel.selectedTab(index);
},

そして、viewModelのselectedTabの処理が行われる。

void selectedTab(int index) {
  currentIndex = index;
  switch (currentIndex) {
    case 0:
      title = "First";
    case 1:
      title = "Second";
    case 2:
      title = "Third";
    case 3:
      title = "Fourth";
  }
  notifyListeners();
}

こうすることで、以下で表示しているタイトルと表示するビューを変更している。

List<Widget> display = [
  const FirstView(),
  const SecondView(),
  const ThirdView(),
  const FourthView()
];
return Scaffold(
  appBar: AppBar(
    backgroundColor: Theme.of(context).colorScheme.inversePrimary,
    title: Text(_viewModel.title),
  ),
  body: display[_viewModel.currentIndex],

ただ、変数を更新しただけでは画面は更新されない。そのためにも以下のメソッドをタップした時に行われるviewModelクラスのメソッド内で呼び出す必要がある。

notifyListeners();

参考ページ