前回はスプラッシュ表示→メインスクリーンでHelloWorldを表示するところまで進めました。
今回はアプリであるあるのボトムナビゲーションの実装をしていきます。
BottomNavigationBarクラスに関しては下記公式ドキュメントを読んでみてください。
https://api.flutter.dev/flutter/material/BottomNavigationBar-class.html
今回はPrivider(状態管理パッケージ)を利用してボトムナビゲーションのindexを制御してWidgetツリー配下全てからbottomnavigationのindexを監視できるようにしてみます。
package:provider とは
package:providerは、コミュニティによって開発されている DI (Dependency Injection) 及び 状態管理用のパッケージです。 実は Google 側でも似たようなパッケージ(provide)を開発していたのですが、provider の方がよいのでは?となり、今はproviderを推奨しているらしいです。
Flutterの状態管理に関しては下記が参考になりました。
https://ja.unflf.com/tech/flutter/provider/
私が採用した理由としてBlockパターンよりもProviderを利用したほうが学習コストも低いですし扱いやすいと感じた為です。
ではやっていきます。
Providerパッケージをget
まずパッケージを入れたいのでpubspec.yamlにprovider追加。
https://pub.dev/packages/provider
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^0.1.2
provider: ^4.0.2 //追加
dev_dependencies:
flutter_test:
sdk: flutter
状態クラスであるproviderファイルを作成する。
lib配下にproviderディレクトリを作成し、そこにbottom_navigation_bar_provider.dartを作成します。
import 'package:flutter/material.dart';
// ミックスイン:クラスのコードを複数のクラス階層で再利用する方法です。
// ミックスインするにはwithキーワードに続けて1つ以上のつ以上のクラスを指定します。
// ChangeNotifierをmixinするだけで、ViewModelとして機能します。
// ロジック実行後にnotifyListeners()を呼ぶことで、View側に変更を通知できます。
class BottomNavigationBarProvider with ChangeNotifier {
int _currentIndex = 0;
get currentIndex => _currentIndex;
set currentIndex(int index) {
_currentIndex = index;
// View側に変更を通知
notifyListeners();
}
}
main.dartで状態クラスをアクセスできるようにする。
MultiProviderメソッドで複数利用出来るようにしているのはどうせ他にもproviderをセットするからです。
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:my_app/splash.dart';
import 'package:my_app/provider/bottom_navigation_bar_provider.dart';
void main() async {
// main()の中で非同期処理を行う際には、下記を実行しなければいけないらしい
WidgetsFlutterBinding.ensureInitialized();
// iOS,androidともに縦向き固定
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp])
.then((_) {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) => BottomNavigationBarProvider()),
],
child: MyApp(),
),
);
});
}
AppScreenを修正する。
Provider.of(context) は動的な値の変化を自動的に監視して、 Widget ツリーをリビルドしてくれます。 リビルドは、Provider.of(context) に渡した BuildContext のスコープで行ってくれるので、最低限しか行われません。
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:my_app/provider/bottom_navigation_bar_provider.dart';
import 'package:my_app/screen/h_screen.dart';
import 'package:my_app/screen/o_screen.dart';
import 'package:my_app/screen/g_screen.dart';
import 'package:my_app/screen/e_screen.dart';
class AppScreen extends StatefulWidget {
static const String routeName = '/app';
@override
_AppScreenState createState() => _AppScreenState();
}
class _AppScreenState extends State<AppScreen> {
var currentTab = [
HScreen(title: '1番目'),
OScreen(title: '2番目'),
GScreen(title: '3番目'),
EScreen(title: '4番目'),
];
@override
Widget build(BuildContext context) {
final bottomNavigationBar =
Provider.of<BottomNavigationBarProvider>(context);
return Scaffold(
appBar: AppBar(
title: Text("provider"),
),
body: currentTab[bottomNavigationBar.currentIndex],
bottomNavigationBar: BottomNavigationBar(
backgroundColor: Colors.white,
currentIndex: bottomNavigationBar.currentIndex,
onTap: (index) {
bottomNavigationBar.currentIndex = index;
},
type: BottomNavigationBarType.fixed,
selectedItemColor: Colors.blue,
items: [
BottomNavigationBarItem(
icon: Tooltip(
message: 'ボトム1',
child: Image.asset(
'assets/images/logo.png',
),
),
activeIcon: Image.asset(
'assets/images/logo.png',
),
title: const Text(
'ボトム1',
style: TextStyle(
fontSize: 12,
color: Colors.black,
fontWeight: FontWeight.bold),
),
),
BottomNavigationBarItem(
icon: Tooltip(
message: 'ボトム2',
child: Image.asset(
'assets/images/logo.png',
),
),
activeIcon: Image.asset(
'assets/images/logo.png',
),
title: const Text(
'ボトム2',
style: TextStyle(
fontSize: 12,
color: Colors.black,
fontWeight: FontWeight.bold),
),
),
BottomNavigationBarItem(
icon: Tooltip(
message: 'ボトム3',
child: Image.asset(
'assets/images/logo.png',
),
),
activeIcon: Image.asset(
'assets/images/logo.png',
),
title: const Text(
'ボトム3',
style: TextStyle(
fontSize: 12,
color: Colors.black,
fontWeight: FontWeight.bold),
),
),
BottomNavigationBarItem(
icon: Tooltip(
message: 'ボトム4',
child: Image.asset(
'assets/images/logo.png',
),
),
activeIcon: Image.asset(
'assets/images/logo.png',
),
title: const Text(
'ボトム4',
style: TextStyle(
fontSize: 12,
color: Colors.black,
fontWeight: FontWeight.bold),
),
),
],
),
);
}
}
計4つのscreenを作成し紐付けます。
- lib/screen/h_screen.dart
- lib/screen/o_screen.dart
- lib/screen/g_screen.dart
- lib/screen/e_screen.dart
import 'package:flutter/material.dart';
class HScreen extends StatefulWidget {
HScreen({Key key, this.title}) : super(key: key);
final String title;
@override
_HScreenState createState() => _HScreenState();
}
class _HScreenState extends State<HScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text(widget.title),
),
body: SafeArea(
child: SingleChildScrollView(
child: Container(
child: const Text('index1のページです'),
)),
));
}
}
できました!!
画像が全部ハゼなのは気にしないでください。。