​【Flutter】Scrollbarの完全ガイド!

対象者

  • Flutterでのアプリ開発経験はあるが、Scrollbarのカスタマイズについて詳しく知らない方
  • ユーザーインターフェイスのユーザビリティを高めたいと考えている開発者の方
  • 新しいFlutterのウィジェットや機能について常に学びたいと思っている方

はじめに

FlutterのUIコンポーネントの1つである「Scrollbar」は、ユーザビリティを大きく左右する重要な部分です。しかし、あまり細かいカスタマイズの方法を知らないのではないでしょうか。この記事では、Scrollbarの基本的な使い方から、より進んだカスタマイズ方法までを詳しく解説します。

Scrollbarの基礎

Scrollbarの概要と特性

Scrollbarは、ユーザーがコンテンツをスクロールする際に利用するUI要素です。特に長いコンテンツを持つページやリストでよく見られます。

Scrollbar(
    child: ListView.builder(
        itemCount: 100,
        itemBuilder: (context, index) {
            return ListTile(title: Text('Item $index'));
        },
    ),
)

このソースコードは、FlutterでScrollbarを持つListViewを作成する一例です。100のアイテムを持つリストが表示され、ユーザーはScrollbarを使ってスクロールすることができます。

Scrollbarの主要なパラメータ

Scrollbarをカスタマイズする際には、さまざまなパラメータを調整することができます。
Flutterの公式ドキュメントには、Scrollbarをカスタマイズするための多くのパラメータが詳しく解説されています。これにより、アプリケーションのブランドやデザインに合わせて、Scrollbarを最適化することができます。

Scrollbar(
    thickness: 10.0,  // Scrollbarの太さ
    humbVisibility: true,  // Scrollbarを常に表示するかどうか
    radius: Radius.circular(8.0),  // Scrollbarの角の半径
    child: ListView.builder(...),
)

このソースコードは、Scrollbarのいくつかの主要なパラメータをカスタマイズしています。具体的には、太さ、常時表示の設定、角の半径を変更しています。
Scrollbarのパラメータを適切に設定することで、アプリケーションのデザインやユーザビリティを大きく向上させることができます。

CupertinoScrollbarとその特徴

CupertinoScrollbarは、iOSスタイルのScrollbarを提供するウィジェットです。iOS風のデザインを持つアプリにはCupertinoScrollbarを使用すると、より一貫性のあるUIを作成できます。

CupertinoScrollbar(
    child: ListView.builder(...),
)

このコードは、ListViewにiOSスタイルのScrollbarを適用する簡単な例です。

Scrollbarの応用例

ScrollViewやListViewとの組み合わせ方法

Scrollbarは、Flutterで長いリストやテキストを表示する際に頻繁に使用されるListViewやScrollViewとの組み合わせが一般的です。ScrollViewやListViewにScrollbarを組み合わせることで、ユーザーは内容をスクロールして確認することができます。
ユーザビリティの向上のため、長い内容を持つウィジェットにはスクロール機能が不可欠です。特に、モバイルデバイスではスクリーンのサイズが限られているため、スクロールが重要です。

Scrollbar(
    child: ListView(
        children: <Widget>[
            ListTile(title: Text('Item 1')),
            ListTile(title: Text('Item 2')),
            // ... 他のアイテム
        ],
    ),
)

このソースは、ListViewにScrollbarを組み合わせた基本的な例です。

スクロールバーの表示・非表示を制御する方法

Scrollbarは自動的に内容がオーバーフローすると表示され、不要な場合には非表示となります。しかし、Scrollbarウィジェットの thumbVisibilityプロパティをtrueに設定することで、常にスクロールバーを表示させることができます。

final _myContoller = ScrollController();
    
    
Scrollbar(
  thumbVisibility: true,
  controller: _myController,
  child: ListView.builder(
    controller: _myController,
    itemCount: 100,
    itemBuilder: (context, index) {
      return ListTile(title: Text('Item $index'));
    },
  ),
)

注意: thumbVisibility: true を設定する場合、ScrollbarListViewに同じScrollControllerを設定する必要があります。

scrollbarThemeを使ったScrollbarのカスタマイズ

ThemeDataの中にscrollbarThemeプロパティを使用することでScrollbarの見た目や動作を細かくカスタマイズすることができるようになりました。以下はその使用例です。

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // ここでscrollbarThemeを設定
        scrollbarTheme: ScrollbarThemeData(
          thickness: MaterialStateProperty.all(12.0),  // スクロールバーの太さ
          thumbColor: MaterialStateProperty.all(Colors.blue),  // スクロールバーの色
          radius: Radius.circular(8),  // スクロールバーの角丸
          // その他のカスタマイズプロパティ
        ),
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

このように、scrollbarThemeを使用することで、Scrollbarの太さや色、角丸などの見た目をカスタマイズできます。もちろん、他にもさまざまなプロパティが提供されているので、公式ドキュメントを参照して、必要に応じてカスタマイズを行ってください。

発生したエラー

The PrimaryScrollController is currently attached to more than one ScrollPosition.

ScrollbarとScrollbarのchildであるListView.builderにScrollbarを関連付けていなかった。ScrollbarとListView.builderの引数controller に同じScrollController を割り当てたら動作した。

The PrimaryScrollController is currently attached to more than one ScrollPosition.
The Scrollbar requires a single ScrollPosition in order to be painted.
When Scrollbar.thumbVisibility is true, the associated ScrollController must only have one ScrollPosition attached.If a ScrollController has not been provided, the PrimaryScrollController is used by default on mobile platforms for ScrollViews with an Axis.vertical scroll direction. More than one ScrollView may have tried to use the PrimaryScrollController of the current context. ScrollView.primary can override this behavior.

Q&A

Q1: スクロールバーとは何ですか?

A1: スクロールバーは、ウェブページやアプリケーション内の情報の長さや位置をユーザーに示すツールです。ユーザーが現在どの位置にいるのか、またページ全体の長さを一目で理解することができます。

Q2: スクロールバーのデザインの一貫性はなぜ重要なのですか?

A2: スクロールバーのデザインの一貫性は、ユーザーエクスペリエンスの向上に寄与します。一貫性があると、ユーザーはページ間でのナビゲーションが容易になり、操作性が向上します。

Q3: Flutterでのスクロールバーの実装にはどのような方法がありますか?

A3: Flutterでは、様々なウィジェットやプロパティを使用してスクロールバーを実装することができます。具体的な方法としては、Scrollbar ウィジェットや CupertinoScrollbar などを使用し、ブランドやデザインテーマに合わせてカスタマイズすることが可能です。

まとめ

スクロールバーは、ウェブページやアプリケーション内の情報の長さや位置をユーザーに示す重要なツールです。適切にデザインされたスクロールバーは、ユーザーエクスペリエンスの向上に寄与します。特に、スクロールバーのデザインの一貫性や適切なサイズは、その使いやすさを左右する要素として挙げられます。また、Flutterの例を取り上げ、その実装方法を示しました。

参考

ソース(main.dartにコピペして動作確認用)

import 'package:flutter/material.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(
        useMaterial3: true,
      ),
      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();

  @override
  Widget build(BuildContext context) {
    final height = MediaQuery.sizeOf(context).height;

    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          children: [
            const Text('Default'),
            Scrollbar(
              child: SizedBox(
                height: height / 5,
                child: ListView.builder(
                  itemCount: 10,
                  itemBuilder: (context, index) {
                    return ListTile(title: Text('Item $index'));
                  },
                ),
              ),
            ),
            const Text('Customize'),
            Scrollbar(
              controller: _scrollController,
              thickness: 10.0, // Scrollbarの太さ
              thumbVisibility: true, // Scrollbarを常に表示するかどうか
              radius: Radius.circular(8.0), // Scrollbarの角の半径
              child: SizedBox(
                height: height / 5,
                child: ListView.builder(
                  controller: _scrollController,
                  itemCount: 10,
                  itemBuilder: (context, index) {
                    return ListTile(title: Text('Item $index'));
                  },
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

上記のサンプルコードは、一つ目はデフォルトのScrollbar、2つ目はカスタマイズしたScrollbarの使用方法を説明しています。