【Flutter】MaterialStatePropertyで状態によるUI更新

対象者

  • Flutterを使用してアプリ開発を行っているが、ウィジェットの状態に応じたスタイリングについてさらに学びたいと考えている開発者
  • ユーザーインターフェースのカスタマイズ性を高め、より反応的でユーザーフレンドリーなアプリケーションを作成したいと思っている方
  • 技術的な評価を上げ、キャリアアップを目指しているフロントエンド開発者

はじめに

Flutterを使ったアプリ開発において、ユーザーインターフェースのカスタマイズは、アプリの成功に不可欠な要素です。特に、ウィジェットが持つさまざまな状態に応じて見た目を変えることは、ユーザー体験を大きく向上させることができます。しかし、このような細かいカスタマイズを実現するには、MaterialStatePropertyのようなFlutterの高度な機能の理解が必要です。この記事では、MaterialStatePropertyを使って、ウィジェットのスタイルを状態に応じて動的に変更する方法を解説します。

MaterialStatePropertyの基本

MaterialStatePropertyとは何か

FlutterのUI開発において、ウィジェットの見た目はその状態によって変わることがよくあります。例えば、ボタンが押された時、ホバーされた時、無効化された時など、それぞれ異なるスタイルを適用したい場合があります。ここで、MaterialStatePropertyが登場します。これは、ウィジェットの状態に応じて値を動的に変更するためのFlutterの機能です。具体的には、色や形状などの属性を、ウィジェットの状態に基づいて柔軟に制御することができます。

この機能は、Flutterのマテリアルデザインコンポーネントで広く利用されており、開発者がウィジェットの状態に応じたスタイリングを簡単に実装できるように設計されています。例えば、ButtonStyleウィジェットでbackgroundColorを設定する際にMaterialStateProperty.resolveWithメソッドを使用することで、ボタンの状態(押された、ホバーされた、無効化されたなど)に応じて背景色を変更することができます。

MaterialStatePropertyの利用シナリオ

MaterialStatePropertyは、特にインタラクティブなウィジェットのスタイリングにおいてその真価を発揮します。ユーザーの操作に応じてウィジェットの見た目を変えたい場合、例えば以下のようなシナリオで有効です。

  • ボタン: 押された時、ホバーされた時、フォーカスされた時、無効化された時に異なる背景色やテキスト色を適用する。

これらのシナリオでは、ユーザーの操作に対する直感的なフィードバックを提供することが重要です。MaterialStatePropertyを利用することで、これらのインタラクションに対して柔軟かつ効果的に対応することができます。ウィジェットの状態に応じたスタイリングは、ユーザー体験を向上させる上で不可欠な要素であり、Flutterアプリケーションの品質を高めるためにも、この機能の適切な利用が推奨されます。

MaterialStatePropertyの設定方法

MaterialStatePropertyでの色の設定

Flutterの開発において、ウィジェットの見た目を状態に応じて変更することは一般的な要求です。MaterialStatePropertyを使用して色を設定することは、この要求に対する直接的な解決策を提供します。具体的には、ウィジェットが特定の状態(例えば、押された、ホバーされた、フォーカスされたなど)にあるときに、色を動的に変更することができます。

この機能の背後にある理由は、ユーザーインタラクションのフィードバックを視覚的に提供し、アプリケーションの使いやすさを向上させることにあります。例えば、ボタンが押されたときに色が変わることで、ユーザーはそのアクションが認識されたことを直感的に理解できます。

実例として、以下のコードスニペットは、ボタンが押された状態で背景色を赤に変更する方法を示しています。

ButtonStyle style = ButtonStyle(
  backgroundColor: MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) {
    if (states.contains(MaterialState.pressed)) {
      return Colors.red;
    }
    return Colors.blue; // デフォルトの色
  }),
);

この例では、MaterialStateProperty.resolveWithメソッドを使用して、ボタンの状態に応じて背景色を動的に決定しています。このようにMaterialStatePropertyを利用することで、アプリケーションのインタラクティブな要素の視覚的なフィードバックを簡単にカスタマイズできます。

MaterialStatePropertyを使ったスタイルのカスタマイズ

MaterialStatePropertyは色だけでなく、ウィジェットのさまざまなスタイル属性を状態に応じてカスタマイズするためにも使用できます。これにより、開発者はウィジェットの見た目をより細かく制御し、ユーザーに対してより豊かなインタラクション体験を提供することが可能になります。

このアプローチの根拠は、ユーザーの操作に対する即時の視覚的フィードバックが、アプリケーションの直感的な使用感を大きく向上させるという点にあります。例えば、ボタンがホバーされたときに影が強調されることで、そのボタンが操作可能であることをユーザーに伝えることができます。

以下のコードは、ボタンのエレベーション(影の高さ)をボタンの状態に応じて変更する方法を示しています。

ButtonStyle style = ButtonStyle(
  elevation: MaterialStateProperty.resolveWith<double>((Set<MaterialState> states) {
    if (states.contains(MaterialState.hovered)) {
      return 10.0; // ホバー時のエレベーション
    }
    return 4.0; // デフォルトのエレベーション
  }),
);

この例では、ボタンがホバーされたときにエレベーションを増加させることで、ボタンの操作可能性を視覚的に示しています。MaterialStatePropertyを使用することで、開発者はウィジェットの状態に応じてさまざまなスタイル属性を柔軟に調整することができます。これにより、ユーザーにとってより魅力的で理解しやすいUIを実現することが可能になります。

MaterialStatePropertyのベストプラクティス

MaterialStatePropertyを効果的に使用するためのベストプラクティスには、以下のようなものがあります。

  • 明確な条件分岐を使用する: 状態に基づいて異なる値を返す際には、明確で読みやすい条件分岐を使用して、コードの可読性を高めます。
  • デフォルト値を設定する: すべての可能な状態をカバーしていない場合に備えて、デフォルト値を設定します。これにより、予期しない状態が発生した場合でも、ウィジェットが適切に表示されることを保証できます。
  • パフォーマンスを考慮する: 計算が複雑になりすぎないように注意し、必要に応じてキャッシングや他の最適化手法を検討します。
  • テーマとの一貫性を保つ: アプリケーション全体で一貫した見た目を保つために、ThemeDataから値を取得して使用することを検討します。

これらのベストプラクティスを実践することで、MaterialStatePropertyを使用したウィジェットのスタイリングが、より効果的でパフォーマンスの面でも最適化されるようになります。

Q&A

Q1: MaterialStatePropertyとは何ですか?

MaterialStatePropertyは、Flutterのウィジェットが持つさまざまな状態(押された、ホバーされた、フォーカスされたなど)に応じて、スタイル属性(色、形状など)を動的に変更するための機能です。これにより、ウィジェットの見た目を状態に基づいて柔軟に制御することが可能になります。

Q2: MaterialStatePropertyの使用時の注意点は何ですか?

MaterialStatePropertyを使用する際の主な注意点は、すべてのウィジェット状態を考慮したスタイル定義を行う必要があることです。特定の状態を見落とすと、予期しないデフォルトのスタイルが適用される可能性があります。また、パフォーマンスへの影響を最小限に抑えるために、不必要に複雑な条件分岐は避けるべきです。

Q3: MaterialStatePropertyを使ったスタイルのカスタマイズ例を教えてください。

ボタンが押されたときに背景色を赤に変更する例は、MaterialStatePropertyの典型的な使用例です。以下のコードスニペットは、その実装方法を示しています。

ButtonStyle myButtonStyle = ButtonStyle(
  backgroundColor: MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) {
    if (states.contains(MaterialState.pressed)) {
      return Colors.red; // 押された状態での色
    }
    return Colors.blue; // 通常状態での色
  }),
);

このコードでは、MaterialStateProperty.resolveWithメソッドを使用して、ボタンの状態に応じて背景色を動的に変更しています。

まとめ

FlutterのMaterialStatePropertyを学び、その使用法と応用方法について理解しました。この機能を使うことで、ウィジェットの状態に応じたスタイルの動的な変更が可能になり、よりリッチでインタラクティブなユーザーインターフェイスを実現できることを学びました。

参考

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

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final materialStateColor = MaterialStateProperty.resolveWith<Color>(
      (Set<MaterialState> states) {
        if (states.contains(MaterialState.pressed)) {
          return Colors.deepPurple;
        } else if (states.contains(MaterialState.hovered)) {
          return Colors.deepPurpleAccent;
        }
        return Colors.blue;
      },
    );

    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('MaterialStateProperty Demo'),
        ),
        body: Center(
          child: Column(
            children: [
              FilledButton(
                style: ButtonStyle(
                  backgroundColor: MaterialStateProperty.resolveWith<Color>(
                    (Set<MaterialState> states) {
                      if (states.contains(MaterialState.pressed)) {
                        return Colors.deepPurple;
                      } else if (states.contains(MaterialState.hovered)) {
                        return Colors.deepPurpleAccent;
                      }
                      return Colors.blue;
                    },
                  ),
                  shape: MaterialStateProperty.resolveWith<OutlinedBorder>(
                    (Set<MaterialState> states) {
                      if (states.contains(MaterialState.pressed)) {
                        return RoundedRectangleBorder(
                            borderRadius: BorderRadius.circular(0));
                      }
                      return RoundedRectangleBorder(
                          borderRadius: BorderRadius.circular(10));
                    },
                  ),
                  elevation: MaterialStateProperty.resolveWith<double>(
                      (Set<MaterialState> states) {
                    if (states.contains(MaterialState.pressed)) {
                      return 10.0;
                    }
                    return 4.0;
                  }),
                ),
                onPressed: () {},
                child: const Text('Press Me'),
              ),
              TextButton(
                style: ButtonStyle(foregroundColor: materialStateColor),
                onPressed: () {},
                child: const Text('Press Me'),
              ),
              OutlinedButton(
                style: ButtonStyle(backgroundColor: materialStateColor),
                onPressed: () {},
                child: const Text('Press Me'),
              ),
              IconButton(
                style: ButtonStyle(backgroundColor: materialStateColor),
                onPressed: () {},
                icon: Icon(Icons.add),
              ),
              TextButton(
                style: ButtonStyle(backgroundColor: materialStateColor),
                onPressed: () {},
                child: const Text('Press Me'),
              ),
              Transform.scale(
                scale: 1.5,
                child: Checkbox(
                  fillColor: materialStateColor,
                  value: true,
                  onChanged: (bool? value) {},
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

このサンプルコードは、FlutterでMaterialStatePropertyを使用して、ウィジェットの状態に応じてスタイルを動的に変更する方法を示しています。

このデモでは、FilledButtonTextButtonOutlinedButtonIconButton、そしてCheckboxウィジェットにMaterialStatePropertyを適用しています。各ボタンは、押されたときに背景色を変更するように設定されています。また、FilledButtonには形状とエレベーションも状態に応じて変更されます。

しかし、Flutter 3.19.2での使用時には、CheckboxウィジェットのfillColorMaterialStatePropertyを適用した場合、期待通りに色が変更されませんでした。「Checkboxを押した」状態が「pressed」ではないのか、バグなのかはよくわからん、、、