FlutterのButton系Widgetが置き換えられて変わったところ

Flutteについては紹介もいらないほどには知名度も上がってきたのですが、日々改善が続けられており消えゆく機能も多くあります
その中でもボタンとして使用されるウィジェットに変更があったのをご存知でしょうか

今回ありがたくもカレンダーに呼ばれ冗談半分にこんなことを言っていたのですが

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Qiita書けって言われてるけど「FlutterのOutlineButtonはOutlinedButtonに置き換わったって一文字変わっただけかよ」って内容でいいかな</p>&mdash; hoshi-kun (@etimesatwo) <a href="https://twitter.com/etimesatwo/status/1356200956241666050?ref_src=twsrc%5Etfw">February 1, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

実際あまり触れられている場が少なく、古いままのコードが多いようなのでその変更点についてまとめました

#3つのボタンウィジェット
Flutterではボタンとして利用できるWidgetがいくつかありますが、最も基本的な機能のみを有するものが3つあります。2020年8月からこれらは別名のクラスに置き換えるべきという方針になりました。

| 機能 | 古いクラス | 新しいクラス |
|:-:|:-:|:-:|
|  テキストなどの子要素をボタン化するもの | `FlatButton`  | `TextButton`  |
|影がついた箱型で 〃   |`RaisedButton`   |`ElevatedButton`   |
|外枠付きで 〃   |`OutlineButton`   |`OutlinedButton`   |

些細なことですが、上記のツイートはこの変更にあたり外枠ボタンの名前が似過ぎという旨でした。実際にこのせいで適切なプロパティと認識されず時間を無駄にしたりしたので気をつける必要があります。

このような状況にもかかわらず[Widget Catalogのボタンの項目](https://flutter.dev/docs/development/ui/widgets/material#Buttons)にはobsoleteのButtonたちが並んでいます。

追記2021/03/13：改善され新しいボタンのみが掲載されています！

実際にそれぞれのガイドを見ると
![01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185791/f2522d45-a68b-742e-22f1-854280f1257f.png)
[FlatButton class - material library - Dart API](https://api.flutter.dev/flutter/material/FlatButton-class.html)より（2021/02/09時点）

という風に使わないよう指示されています。しかしdeprecatedとも言われてないし、実際コードで使用してもなにも言われないのですが、公式が変えろと言ってるので変えた方が良さそうです。なにが変わったのでしょうか？

追記2021/03/13：こちらも正式にdeprecated扱いで警告がちゃんと出るようになりました！Flutter 2とともにかもしれません。

※画像内のリンクは[Migrating to the New Material Buttons and their Themesというドキュメント](https://docs.google.com/document/d/1yohSuYrvyya5V1hB6j9pJskavCdVq9sVeTqSoEPsWH0/edit#)に続いており、英語で詳しく説明されているのでそこから最低限の概要を日本語でまとめます

#各クラスの変更点
この変更について最も詳しいのは公式の移行ガイドかと思うのですが、それでも網羅的に各クラスを解説するのではなく、Flutterアプリを作る際に支障を起こさない程度の要件という内容です。
新しいクラスが存在するわけですから詳しいことは[そ](https://api.flutter.dev/flutter/material/TextButton-class.html) [れ](https://api.flutter.dev/flutter/material/ElevatedButton-class.html) [ら](https://api.flutter.dev/flutter/material/OutlinedButton-class.html)のリファレンスに載っており、そもそも移行のために全てを把握する必要はないと言えるでしょう。

そうした前提の上で、移行のために抑えたいところと、新しいウィジェットクラスを使うために知りたいところ、という双方の観点から見ていきます

###Visual Changes
わかりやすい点ではデフォルトの見た目が以下のように変更されたようです（左：旧、右：新）
![02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185791/8ea6c05f-8ba6-a7c8-3374-0b1040feeaef.png)
[Migrating to the New Material Buttons and their Themes - Google ドキュメント](https://docs.google.com/document/d/1yohSuYrvyya5V1hB6j9pJskavCdVq9sVeTqSoEPsWH0/edit#)より

これはFlutterが原則的にしたがっているMaterial Designに適合させるためのようです。
その他にパディングや角丸や押されたときなどの挙動など小さな変更もあるとのことですが、この辺りは気にせずとも使用できるでしょう。

ボタンのカラーが元から適用されるようになったため、Flutterアプリで使用される[ThemeData](https://api.flutter.dev/flutter/material/ThemeData-class.html)を参照してそれを決定するようになりました。

カラーと同じように**ボタンのスタイルそのものがThemeDataに組み込まれています**。
従来のようにボタンに対して個別に設定することもできますが可能ならMaterial Design的にも単純な手間としてもThemeDataに任せる方がいいでしょう。

###API Changes - 個別プロパティからButtonStyleに
以前のボタンのスタイルは各種のプロパティで設定していましたが、新しいものは`ButtonStyle`というクラスにまとめられており、styleという単独のプロパティに指定します。リファレンスに掲載されているコンストラクタを見てみましょう。

旧来のウィジェットは、何も指定せず使用するときには問題ありませんが、各種スタイルを設定すると非常に冗長でわかりにくくなります

```古いテキスト系ボタンのコンストラクタ.dart
FlatButton(
  {
    Key key,
    @required VoidCallback onPressed,
    VoidCallback onLongPress,
    ValueChanged<bool> onHighlightChanged,
    MouseCursor mouseCursor,
    ButtonTextTheme textTheme,
    Color textColor,
    Color disabledTextColor,
    Color color,
    Color disabledColor,
    Color focusColor,
    Color hoverColor,
    Color highlightColor,
    Color splashColor,
    Brightness colorBrightness,
    EdgeInsetsGeometry padding,
    VisualDensity visualDensity,
    ShapeBorder shape,
    Clip clipBehavior: Clip.none,
    FocusNode focusNode,
    bool autofocus: false,
    MaterialTapTargetSize materialTapTargetSize,
    @required Widget child,
    double height,
    double minWidth
 }
)
```

新しいウィジェットはかなりシンプルになっています。

```新しいテキスト系ボタンのコンストラクタ.dart
TextButton(
  {
    Key key,
    @required VoidCallback onPressed,
    VoidCallback onLongPress,
    ButtonStyle style,
    FocusNode focusNode,
    bool autofocus: false,
    Clip clipBehavior: Clip.none,
    @required Widget child
  }
)
```

尤もButtonStyleに細かく設定できるので全てを指定すると長くなるのは避けられません。というのもThemeDataで一度基本的なスタイルを指定すればほとんどそれだけで済むからです。

旧来のものも継承クラスを定義して汎用できるようにすれば繰り返しスタイルを定義する必要はありませんが、ボタンのラベルに用いるテキストの内容や、テキスト以外にも多くのウィジェットをラベルに指定できるため、使う場面によってそれらが変わることを考えると**必要なスタイルのみを単一のプロパティで指定してインスタンス化する方が効率的**ですね

これに際してButtonStyleというクラスが登場しますが、単なるボタンの見た目だけではなく「押されたとき」「押されている最中」「離されたとき」「無効なとき」など様々な状態（MaterialPropertyとして扱われる）と共に定義される物です。そのため割と煩雑な記述が必要なのですが、一部のみのショートハンドも存在するので実際にはそこまで面倒にならないでしょう。

```上図で紫色の部分を変更する例.dart
TextButton(
  style: TextButton.styleFrom(primary: Colors.green),
  child: Text('TextButton'),
  onPressed: () {},
)
```

詳しくは[リファレンス](https://api.flutter.dev/flutter/material/ButtonStyle-class.html)にガイドがあります。また[こちら](https://zenn.dev/coka01/articles/cb0b632766138e9858e7)の記事もお勧めです。

#旧クラスの今後
移行ガイドによると
>The original button classes - FlatButton, RaisedButton, OutlineButton, ButtonTheme, as well as their supporting classes MaterialButton and RawMaterialButton, have not yet been deprecated or removed.  Eventually they will be deprecated, and after a suitably long period of time, removed.

と、*"最終的には非推奨になり、十分に長い時間をかけて無くす"*そうなので今のうちから移行しておくべきでしょう

スタイル関連のプロパティを使用していなければ機能に変わりはないのでクラス名のみ変更すれば対応できます。
使用していれば移行ガイドには旧プロパティそれぞれに対応した書き換えがあります。
また、「旧クラスのスタイルは指定していないが新クラスの見た目の軽微な変更が問題となる」といった場合にも、以前の見た目にするための指定も掲載されています。

これらに従い、繰り返し使われているなら`ThemeData`に`ButtonStyle`を追加、個別に指定したいならそれぞれにstyleプロパティを追加して対応できます