FlutterでのMaterial Designのカスタマイズ ~ThemeDataはどこに影響するのか~

Flutterは、Material Designを標準サポートしています。そして、テーマを自分で設定できます。
そのために利用するのが、ThemeDataです。
これについて色々な記事があります。

が、日本語はもちろん、英語でも、ThemeDataのプロパティのどこを変更すれば、Widgetのどこに影響するのかを纏めてある記事が見つかりませんでした。

そこで、この記事では自分が愚直にコードを書いて調べたものを記述します。

基本的にここに書いてあるものだけ変更すればそれなりに統一的なデザインになるものを書いてます。

# 前提

カスタムテーマは ```MaterialApp``` の themeプロパティで設定します。
```ThemeData()``` のプロパティにカスタマイズしたい値を設定していきます。

```dart
void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // TODO ここにカスタマイズしてく
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}
```

# 基本

Material Designの基本の色設定です。

## primarySwatch

```dart
  static const MaterialColor _primary = Colors.indigo;
・・・
    primarySwatch: _primary,
```

ここに書いた色がアプリの基本の色になります。

* AppBarやbackdropなどの色
* BottomNavigationのselected(Brightness.lightの場合)
* TextFieldの選択した時の枠線やアイコンの色(InputDecorationの色)
* CircleAvatarの色
* DialogのButtonの色、

<img width="300" alt="prymary1.png" src="https://qiita-image-store.s3.amazonaws.com/0/10494/878da8fc-bf26-51cb-7f1f-38a3acc841f2.png"> <img width="300" alt="prymary2.png" src="https://qiita-image-store.s3.amazonaws.com/0/10494/0163658f-e9a7-ebdb-2233-dc527e53fcb4.png">
<img width="339" alt="prymary3.png" src="https://qiita-image-store.s3.amazonaws.com/0/10494/d012594b-6e9b-ae34-4beb-f416864584b7.png"> <img width="239" alt="prymary4.png" src="https://qiita-image-store.s3.amazonaws.com/0/10494/4398b3d9-4ed0-8aef-7175-feed40e970dd.png">

これだけだとボタンやアイコンなどの色は変わりません。

## accentColor

アクセントカラーです。

```dart
  static const MaterialAccentColor _accent = Colors.amberAccent;
・・・
    accentColor: _accent,
```

* FloatingButtonの色
* checkbox/radioのselected(Brightness.lightの場合)
* indicatorの矢印の色
* DialogのButtonの文字色
* BottomNavigationのselected(Brightness.darkの場合)

<img width="81" alt="accent.png" src="https://qiita-image-store.s3.amazonaws.com/0/10494/5969ad1b-c784-f458-25a1-65a97dd984ed.png"> <img width="140" alt="accent2.png" src="https://qiita-image-store.s3.amazonaws.com/0/10494/a8f67de9-82f3-4799-ecd7-14daf86fdf2e.png"> <img width="74" alt="accent3.png" src="https://qiita-image-store.s3.amazonaws.com/0/10494/08a4a554-40e6-9319-9fd2-135a699f4808.png">

# ダイアログ

この状態でのDialogはこんな感じです。
なので、次はダイアログを変えます。

<img width="257" alt="base-dialog.png" src="https://qiita-image-store.s3.amazonaws.com/0/10494/ca2ee144-4ef7-6075-ece1-79c4a0aee5ba.png">

## colorScheme

ダイアログのボタンの文字色を変えられます。
ただし、RaisedButtonは変わらないので、textStyleの色を変えたほうが良い。
以下のbuttonTheme.textThemeは、ダイアログ以外のその他のボタンのテーマを変えればなおります。

```dart
  Button(
    textTheme: Theme.of(context).buttonTheme.textTheme,
```



### onSurface

* OutlineButtonの枠線の色。注意すべきは、この色に ```.withOpacity(0.12)``` をした色になるので薄い。
* disableボタンもこの色に合わせてくれる

```dart
    colorScheme: ColorScheme.fromSwatch(
      primarySwatch: _primary,
    ).copyWith(
      onSurface: _primary,
    ),
```

<img width="311" alt="button_disable.png" src="https://qiita-image-store.s3.amazonaws.com/0/10494/9c75d80c-c66a-7f2b-f0e6-3011a3d1e337.png">

## dialogTheme

* Dialogのボタン以外のテーマを変えれます。

```dart
  dialogTheme: DialogTheme(
    titleTextStyle: TextStyle(
    color: _primary,
  )),
```

ここまでのダイアログです。

<img width="262" alt="dialog.png" src="https://qiita-image-store.s3.amazonaws.com/0/10494/cc89a987-8fd4-d092-c593-1e934eacdca9.png">

# ボタン/アイコン

## buttonTheme textTheme

* Dialog以外のボタンの文字色に影響を与える。
RaisedButtonの場合、```ButtonTextTheme.primary```を与えるとよしなにprimayの色にあった白や黒に変えてくれる

## buttonTheme shape

* ボタンの角に影響を与える。

```dart
  buttonTheme: ButtonThemeData(
    textTheme: ButtonTextTheme.primary,
    shape: RoundedRectangleBorder(
      /// Dialog以外のボタンの角に影響を与えることができる
      borderRadius: const BorderRadius.all(Radius.circular(24)),
      ),
    ),
```
<img width="284" alt="button.png" src="https://qiita-image-store.s3.amazonaws.com/0/10494/050269c4-499d-d339-ef03-9f215324fbff.png"> <img width="251" alt="button2.png" src="https://qiita-image-store.s3.amazonaws.com/0/10494/1c0b2b9a-f8d2-4aa6-ad59-7aebf348c475.png">

### OutlineButtonの枠線の色をつけたい場合 

ThemeDataだけではできません。直接指定する必要があります。

```dart
  OutlineButton.icon(
    borderSide: BorderSide(
    color: Theme.of(context).primaryColor.withOpacity(0.5),
  ),
```

## iconTheme

* 普通のアイコンのテーマを変えられる。
* IconButton,Iconのどちらも変わる

```dart
  iconTheme: const IconThemeData.fallback().copyWith(
    color: _accent,
  ),
```
<img width="134" alt="icon1.png" src="https://qiita-image-store.s3.amazonaws.com/0/10494/456ff016-c83b-34a6-85ce-e5c1a2dab921.png">


## accentIconTheme

* FloatingActionButtonのアイコン色

```dart
  accentIconTheme: const IconThemeData.fallback().copyWith(
    color: Colors.red,
  ),
```

<img width="96" alt="icon2.png" src="https://qiita-image-store.s3.amazonaws.com/0/10494/0f80f9df-73e3-d555-0554-32e3508b5b8f.png">

### FloatingActionButtonの色

ThemeDataだけでは変えられません。
直接指定する必要があります。ただし、 ```Theme.of(context)```を使いましょう。

```dart
  floatingActionButton: FloatingActionButton(
    backgroundColor: Theme.of(context).primaryColor,
```

## primaryIconTheme

* AppBarなどで利用しているアイコン

```
  primaryIconTheme: const IconThemeData.fallback().copyWith(
    color: Colors.greenAccent,
  ),
```

## primaryTextTheme

AppBarのアイコンを変えたら、こちらも変えたいでしょう。

* appBarのタイトルの色が変わる

```
  primaryTextTheme: const TextTheme().copyWith(
    title: TextStyle().copyWith(
      color: Colors.teal,
    ),
  ),
```

<img width="337" alt="appbar.png" src="https://qiita-image-store.s3.amazonaws.com/0/10494/b74a4e9d-c678-06a3-caae-1e4a40da4b78.png">


# フォーム

TextFieldなどの枠線の変更です。
動きがないとわからないかもしれないので、下の動画を見てください。

## inputDecorationTheme enabledBorder

* TextFieldなどで設定する枠線の設定を変えられる
* 利用しているWidgetで ```InputBorder``` を指定してもこちらの値が使われる

```dart
  inputDecorationTheme: InputDecorationTheme(
    enabledBorder: UnderlineInputBorder(
      borderSide: BorderSide(color: _primary),
    ),
```

## inputDecorationTheme focusedBorder

* TextFieldなどでonFocusの時の枠線のstyleを設定

```dart
    focusedBorder: OutlineInputBorder(
      borderSide: BorderSide(color: _accent),
    ),
```

## inputDecorationTheme labelStyle

* InputDecorationのlabelTextの色が変えられる

```dart
    labelStyle: const TextStyle(        
      color: Colors.orange,
    ),
```

## labelStyle fillColor

* filed:trueの場所の色を設定できる。

```dart
    fillColor: Colors.orange.shade50,
```

![deco.gif](https://qiita-image-store.s3.amazonaws.com/0/10494/5ad277b8-4631-441d-2116-27363edd206f.gif)

# コード

コードです。これのそれぞれの場所を変更すれば、統一的なテーマになるはずです。
(FloatingActionButtonの色とOutlineButtonの枠の色を変えたい場合はそれぞれのWidgetに直接書く必要があります。)

```dart
import 'dart:ui';

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

class MyTheme {
  const MyTheme._internal();

  static const MaterialColor _primary = Colors.indigo;
  static const MaterialAccentColor _accent = Colors.amberAccent;

  static final ThemeData data = ThemeData(
    primarySwatch: _primary,
    accentColor: _accent,
    buttonTheme: ButtonThemeData(
      textTheme: ButtonTextTheme.primary,
      shape: RoundedRectangleBorder(
        borderRadius: const BorderRadius.all(Radius.circular(24)),
      ),
    ),
    primaryTextTheme: const TextTheme().copyWith(
      title: TextStyle().copyWith(
        color: Colors.teal,
      ),
    ),
    inputDecorationTheme: InputDecorationTheme(
      enabledBorder: OutlineInputBorder(
        borderSide: BorderSide(color: Colors.green),
      ),
      focusedBorder: OutlineInputBorder(
        borderSide: BorderSide(color: Colors.greenAccent),
      ),
      fillColor: Colors.orange.shade50,
      labelStyle: const TextStyle(
        color: Colors.orange,
      ),
    ),
    iconTheme: const IconThemeData.fallback().copyWith(
      color: _accent,
    ),
    primaryIconTheme: const IconThemeData.fallback().copyWith(
      color: Colors.greenAccent,
    ),
    accentIconTheme: const IconThemeData.fallback().copyWith(
      color: Colors.red,
    ),
    colorScheme: ColorScheme.fromSwatch(
      primarySwatch: _primary,
    ).copyWith(
      onSurface: _primary,
    ),
    dialogTheme: DialogTheme(
        titleTextStyle: TextStyle(
      color: Colors.deepOrange,
    )),
  );
}
```

# ツール

最後に、この記事を書いてる途中で知ったツールを紹介します。
[panache](https://github.com/rxlabz/panache)です。

これはGUIでポチポチ選択していくとThemeDataのソースを吐き出してくれます。

Androidアプリがあるので、これでThemeDataを作るのも良いかもしれません。
https://play.google.com/store/apps/details?id=bz.rxla.panache&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1