【Flutter】スクロールに応じて隠れるBottomNavigationBar

  • 2024年4月15日
  • 2024年4月15日
  • 小物

対象者

  • Flutterを使ってアプリ開発を行っているが、UIの動的な挙動についてさらに深く理解したい方
  • ユーザーの操作に応じてインターフェースが動的に変化するアプリを設計したい開発者
  • BottomNavigationBarを消して、少しでも表示領域を増やしたいアプリの開発者

はじめに

スクロール時に隠れるナビゲーションバーを実装して、アプリの操作感を向上させたいと考えたことはありますか?この記事では、Flutterの標準機能を使って、スムーズで洗練されたUI動作を実現する方法を詳しく解説します。特に、ScrollControllerを用いてスクロールの方向を感知し、AnimatedBuilderAnimatedContainerで動的な高さ調整を行うナビゲーションバーの作成方法を学びます。これにより、ユーザーのスクロール操作に応じてインターフェースが自然に反応し、アプリの使い勝手が改善されます。

実装

下へのスクロールを検知したら、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の表示を動的に変更するために、AnimatedBuilderAnimatedContainerを使用します。これにより、スムーズなアニメーション効果が得られます。

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の基本的な使い方から始め、AnimatedBuilderAnimatedContainerを活用してナビゲーションバーの高さを動的に変更する技術を理解しました。最終的に、ListView.builderを用いてスクロール可能なリストを設置し、これらの技術がどのように連携して機能するかを確認しました。

参考

ソース(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はテーマでないので、動作しません(笑)。実装はいる場合は以下を参考にどうぞ。

【Flutter】NavigationBarでスムーズな画面遷移