【Flutter】MaterialStatePropertyってなんだ？

## はじめに
MaterialStatePropertyとは何なのか疑問に思ったのは、**[ElevatedButton](https://api.flutter.dev/flutter/material/ElevatedButton-class.html)**に背景色をつける実装をしようとしている時でした。

ElevatedButtonには**[ButtonStyle](https://api.flutter.dev/flutter/material/ButtonStyle-class.html)**を設定でき、このButtonStyleのプロパティ**backgroundColor**にColorの値を設定してあげれば背景色をつけられると思っていたのですが、backgroundColorの型はColorではなく、``MaterialStateProperty<Color>``でした。

この`MaterialStateProperty`ってなんだ？

## 動作環境

* Flutter 1.22.4
* Dart 2.10.4

## MaterialStatePropertyとは
結論から書くと、MaterialStatePropertyは、**MaterialState**の状態によって決まる値のことです。
例えば上記で挙げたButtonStyleのbackgroundColorの型は、`MaterialStateProperty<Color>`ですが、これは**MaterialStateの状態によってColorの値が決まる**ということです。
MaterialStatePropertyの値の型はジェネリクスを使って動的に決まるようになっています。

## MaterialStateとは
MaterialStateはユーザーの入力によって変化するMaterialWidgetの状態を表します。

MaterialStateはenumで定義されており、例えば`MaterialState.disabled`はWidgetの非活性な状態を表します。

![スクリーンショット 2020-12-12 14.08.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263659/23755c77-dc88-44f0-fca7-f3f656229db2.png)

MaterialStateのその他の状態については[公式ドキュメント](https://api.flutter.dev/flutter/material/MaterialState-class.html)をご参照ください。

## MaterialStatePropertyの使い方
MaterialStatePropertyではMaterialStateによって値を変えることができます。
ここではMaterialStatePropertyの使い方を下記の2パターンに分けて紹介します。

* **全てのMaterialStateで同じ値を設定したい**
* **MaterialStateによって設定する値を変えたい**

### 全てのMaterialStateで同じ値を設定したい
冒頭で挙げたElevatedButtonに背景色をつけたいというケースでは、ElevatedButtonがどんな状態であれ指定したColorの背景色になっていれば良いというケースでした。
このような全てのMaterialStateで同じ値を設定したいというケースでは、[MaterialStateProperty.all](https://api.flutter.dev/flutter/material/MaterialStateProperty/all.html)メソッドを使います。

MaterialStateProperty.allメソッドを使ってElevatedButtonに背景色を設定した実行結果とコードはこちらです。

![スクリーンショット 2020-12-12 15.08.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263659/8a3457a0-00f1-e40c-515c-b2c1378c1c4a.png)

``` dart
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      style: ButtonStyle(
        backgroundColor: MaterialStateProperty.all<Color>(Colors.red),
      ),
      child: Text('TextButton'),
      onPressed: () {},
    );
  }
```
どんなMaterialStateであれColors.redが設定されるので、ボタンを押下するなどで状態を変更しても背景色は変わりません。

### MaterialStateによって設定する値を変えたい
MaterialStateによって設定する値を変えたい場合は、[MaterialStateProperty.resolveWith](https://api.flutter.dev/flutter/material/MaterialStateProperty/resolveWith.html)メソッドを使います。

例えばボタンが押されている状態とそうでない状態で背景色を変えたい場合は下記のように実装します。

![ezgif.com-gif-maker.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263659/d6055139-3512-ca4f-4ea3-7539f5f604dd.gif)

``` dart
  Color backgroundColor(Set<MaterialState> states) {
    // 場合分けする状態のリストを定義
    const Set<MaterialState> interactiveStates = <MaterialState>{
      MaterialState.pressed,
    };
    if (states.any(interactiveStates.contains)) {
      return Colors.blue;
    }
    return Colors.red;
  }

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      style: ButtonStyle(
        backgroundColor:
            MaterialStateProperty.resolveWith(this.backgroundColor),
      ),
      child: Text('TextButton'),
      onPressed: () {},
    );
  }
```
MaterialStateProperty.resolveWithメソッドでは引数に、MaterialStateによって値を返すメソッドを渡しています。
そしてそのメソッド(backgroundColorメソッド)では、ボタンが押された状態(MaterialState.pressed)とそうでない時でreturnする背景色を変えています。

## 所感
インタラクティブなWidgetのプロパティがMaterialStatePropertyで統一されればより汎用的にレイアウトを実装できるので良いと思いました。

## まとめ

* MaterialStatePropertyとはMaterialStateによって決まる値
* MaterialStateとはユーザーの入力によって変化するMaterialWidgetの状態(ボタンが押された、非活性になった、など)
* MaterialStatePropertyでは全ての状態で同じ値にする**allメソッド**と、状態によって値を変更する**resolveWithメソッド**がある

以上です。