対象者
- Flutterを使ってアプリ開発を行っているが、UIの動的な挙動についてさらに深く理解したい方
- ユーザーの操作に応じてインターフェースが動的に変化するアプリを設計したい開発者
- BottomNavigationBarを消して、少しでも表示領域を増やしたいアプリの開発者
はじめに
スクロール時に隠れるナビゲーションバーを実装して、アプリの操作感を向上させたいと考えたことはありますか?この記事では、Flutterの標準機能を使って、スムーズで洗練されたUI動作を実現する方法を詳しく解説します。特に、ScrollController
を用いてスクロールの方向を感知し、AnimatedBuilder
とAnimatedContainer
で動的な高さ調整を行うナビゲーションバーの作成方法を学びます。これにより、ユーザーのスクロール操作に応じてインターフェースが自然に反応し、アプリの使い勝手が改善されます。
実装
下へのスクロールを検知したら、BottomNavigationBarの高さが0になるようにします。
スクロールの設定
Flutterでスクロールに基づいてUIの動きを制御するには、ScrollController
を使用します。以下のコードは、スクロールの方向を検出し、その情報を基にUIの変化を行う方法を示しています。
final _scrollController = ScrollController();
bool get isMovingToBottom =>
_scrollController.position.userScrollDirection == ScrollDirection.reverse;
@Override
void dispose() {
_scrollController.dispose();
super.dispose();
}
ScrollController
:スクロールイベントをリッスンし、スクロール位置や方向などの情報を提供します。isMovingToBottom
:ユーザーが下にスクロールしているかどうかを判断します。ScrollDirection.reverse
は下向きのスクロールを意味します。dispose
メソッド:不要になったScrollController
を破棄し、メモリリークを防ぎます。
BottomNavigationBarの実装
スクロールに基づいてBottomNavigationBar
の表示を動的に変更するために、AnimatedBuilder
とAnimatedContainer
を使用します。これにより、スムーズなアニメーション効果が得られます。
bottomNavigationBar: AnimatedBuilder(
animation: _scrollController,
builder: (BuildContext context, Widget? child) {
return AnimatedContainer(
duration: const Duration(milliseconds: 200),
height: isMovingToBottom ? 0 : 60,
child: child,
);
},
child: NavigationBar(
destinations: const <NavigationDestination>[
NavigationDestination(icon: Icon(Icons.home), label: 'Home'),
NavigationDestination(icon: Icon(Icons.person), label: 'Profile'),
],
),
),
AnimatedBuilder
:指定されたアニメーションに基づいてウィジェットを再構築します。ここでは_scrollController
をアニメーションソースとして使用。AnimatedContainer
:高さの変更をアニメーション化し、スクロールに応じてナビゲーションバーを表示または非表示にします。NavigationBar
:アプリの主要なナビゲーションオプションを表示します。
表示本体
スクロール可能なリストを表示するために、ListView.builder
を使用します。これは、リスト内の各項目を動的に生成し、スクロール時にナビゲーションバーの挙動を確認するのに役立ちます。
ListView.builder(
controller: _scrollController,
itemCount: 50,
itemBuilder: (context, index) => Text(
'$index',
style: const TextStyle(fontSize: 32),
),
),
ListView.builder
:項目数が多いリストを効率的に表示するためのウィジェット。各項目はitemBuilder
によって定義され、ここでは単純なテキストを使用しています。itemCount
:リスト内の項目数を指定します。
これらのコンポーネントを組み合わせることで、ユーザーがスクロール時に体験するインタラクティブなナビゲーションバーを実装できます。
まとめ
この記事では、Flutterを使用してスクロールに応じて隠れるBottomNavigationBar
を実装する方法について学びました。スクロールの方向を検出するScrollController
の基本的な使い方から始め、AnimatedBuilder
とAnimatedContainer
を活用してナビゲーションバーの高さを動的に変更する技術を理解しました。最終的に、ListView.builder
を用いてスクロール可能なリストを設置し、これらの技術がどのように連携して機能するかを確認しました。
参考
- How to Hide Bottom Navigation Bar on Scroll in Flutter
元ネタ。よりわかりやすい、はず。
ソース(main.dartにコピペして動作確認用)
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final _scrollController = ScrollController();
bool get isMovingToBottom =>
_scrollController.position.userScrollDirection == ScrollDirection.reverse;
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
bottomNavigationBar: AnimatedBuilder(
animation: _scrollController,
builder: (BuildContext context, Widget? child) {
return AnimatedContainer(
duration: const Duration(milliseconds: 200),
height: isMovingToBottom ? 0 : 60,
child: child,
);
},
child: NavigationBar(
destinations: const <NavigationDestination>[
NavigationDestination(
icon: Icon(Icons.home),
label: 'Home',
),
NavigationDestination(
icon: Icon(Icons.person),
label: 'Profile',
),
],
),
),
body: SafeArea(
child: ListView.builder(
controller: _scrollController,
itemCount: 50,
itemBuilder: (context, index) => Text(
'$index',
style: const TextStyle(fontSize: 32),
),
),
),
);
}
}
NavigationBarはテーマでないので、動作しません(笑)。実装はいる場合は以下を参考にどうぞ。