FlutterのThemeを可能な限り柔軟性を持たせてみた

# はじめに
Flutterのテーマはマテリアルデザインに100%準拠している場合はとても便利だと思うんですが、例えばbuttonThemeが一つしか設定できなくて柔軟性に欠けているように思えます。画面によってもっといろんなデザインに変えたい！（Buttonの形を変えたい、フォントスタイルもっと柔軟に変えたい）
ということで今回は、Themeをもっと柔軟に使えるようにしてみました。

# 最適化

## ファイル構成

```
└─lib
  └─theme
    ├─theme.dart
    ├─text_theme_custom.dart
    ├─color_theme_custom.dart
    └─style_theme_custom.dart
```

ファイル構成はこんな感じでText、Color、Styleそれぞれ別ファイルで定義するようにしました。

## theme.dart
theme.dartではThemeDataを返却するメソッドを定義します。

```dart:theme.dart
ThemeData createTheme() {
  
  return ThemeData(
      primarySwatch: ColorThemeCustom.customSwatch,
      primaryColor: ColorThemeCustom.customPrimaryColor,
      brightness: Brightness.light,
      primaryColorBrightness: Brightness.light,
      buttonTheme: StyleThemeCustom.createButtonTheme().copyWith(shape: StyleThemeCustom.round),
      textTheme: textTheme,
      primaryTextTheme: textTheme,
      accentTextTheme: textTheme,
    );
}
```

## text_theme_custom.dart
デフォルト値をcreateTextThemeとして定義。フォントサイズ、フォントカラーは柔軟に変更できるように別途定義します。

```dart:text_theme_custom.dart
class TextThemeCustom {
  TextThemeCustom._();

  static TextTheme createTextTheme() {
    return TextTheme(
      headline: TextStyle(
        color: ColorThemeCustom.theme["black"],
        fontWeight: FontWeight.w400,
        fontStyle: FontStyle.normal,
      ).merge(font_size_18),
      body2: TextStyle(
          color: ColorThemeCustom.theme["black"],
          height: 1.0
      ).merge(font_normal).merge(font_size_15),
      body1: TextStyle(
        color: Color(0x8A1A1512),
      ).merge(font_normal).merge(font_size_15),
      button: TextStyle(
        color: ColorThemeCustom.theme["white"],
      ).merge(font_medium).merge(font_size_17),
      title: TextStyle(
        color: ColorThemeCustom.theme["black"],
      ).merge(font_medium).merge(font_size_17),
      subtitle: TextStyle(
        color: ColorThemeCustom.theme["black"],
      ).merge(font_medium).merge(font_size_16),
    );
  }
  static const TextStyle font_normal = const TextStyle(
    fontSize: 16.0,
    fontFamily: 'NotoSansCJKjp',
    fontWeight: FontWeight.normal,
    fontStyle: FontStyle.normal,
  );
  static const TextStyle font_medium = const TextStyle(
    fontSize: 16.0,
    fontFamily: 'NotoSansCJKjp',
    fontWeight: FontWeight.w500,
    fontStyle: FontStyle.normal,
  );
  static const TextStyle font_bold = const TextStyle(
    fontSize: 16.0,
    fontFamily: 'NotoSansCJKjp',
    fontWeight: FontWeight.bold,
    fontStyle: FontStyle.normal,
  );
  static const TextStyle font_size_15 = const TextStyle(fontSize: 15.0);
  static const TextStyle font_size_16 = const TextStyle(fontSize: 16.0);
  static const TextStyle font_size_17 = const TextStyle(fontSize: 17.0);
  static const TextStyle font_size_18 = const TextStyle(fontSize: 18.0);
  static const TextStyle font_size_22 = const TextStyle(fontSize: 22.0);
  static const TextStyle font_size_40 = const TextStyle(fontSize: 40.0);

  static const TextStyle font_color_primary = const TextStyle(color: ColorThemeCustom.customPrimaryColor);
}
```

## color_theme_custom.dart
Primaryカラーや、Swatch、その他いろんな用途用にMapでカラーを定義します。

```dart:color_theme_custom.dart
class ColorThemeCustom {
  ColorThemeCustom._();

  static const int customPrimaryValue = 0xFFF5A076;
  static const Color customPrimaryColor = const Color(customPrimaryValue);
  static const MaterialColor customSwatch = const MaterialColor(
      customPrimaryValue,
      const <int, Color> {
        50: const Color(0xFFFFDDC1),
        100: const Color(0xFFFFD3B7),
        200: const Color(0xFFFFC9AE),
        300: const Color(0xFFFFBFA4),
        400: const Color(0xFFFFB59B),
        500: const Color(0xFFFFAB91),
        600: const Color(0xFFF29F86),
        700: const Color(0xFFE4937A),
        800: const Color(0xFFD7876F),
        900: const Color(0xFFC97B63),
      }
  );
  static const Map<String, Color> theme = const <String, Color>{
      "white": const Color(0xFFFFFFFF),
      "black": const Color(0xdd000000),
  };
}
```

## style_theme_custom.dart
ButtonのデフォルトテーマをcreateButtonThemeとして定義、それ以外にround型ボタンやsquare型のボタンを使いたかったので別途StadiumBorder、RoundedRectangleBorder型の定数を定義。

```dart:style_theme_custom.dart
class StyleThemeCustom {
  StyleThemeCustom._();

  static ButtonThemeData createButtonTheme() {
    return ButtonThemeData(
      minWidth: 88.0,
      height: 36.0,
      padding: EdgeInsets.only(top: 0.0, bottom: 0.0, left: 16.0, right: 16.0),
      alignedDropdown: false,
      disabledColor: ColorThemeCustom.theme["black"],
      highlightColor: ColorThemeCustom.theme["black"],
    );
  }

  static const StadiumBorder round = StadiumBorder(
    side: BorderSide(
      color: ColorThemeCustom.theme["black"],
      width: 0.0,
      style: BorderStyle.none,
    ),
  );

  static const RoundedRectangleBorder square = RoundedRectangleBorder(
    side: BorderSide(
      color: ColorThemeCustom.theme["black"],
      width: 0.0,
      style: BorderStyle.none,
    ),
    borderRadius: BorderRadius.all(Radius.circular(10.0)),
  );
}
```

## 使い方
まずは今回作成したThemeDataをmain.dartで適用してあげます。

```dart:main.dart
class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Startup Name Generator',
      theme: createTheme(),
      home: RandomWords(),
    );
  }
}
```


margeを使うことでthemeのマージ元をマージ先で上書きできます。

```dart
// テキスト
Text(
    "テキスト",
    style: Theme.of(context).textTheme.body1
        .merge(TextThemeCustom.font_size_22)
        .merge(TextThemeCustom.font_color_primary),
);
// or
Text(
    "テキスト",
    style: TextThemeCustom.font_normal
        .merge(TextThemeCustom.font_size_22)
        .merge(TextThemeCustom.font_color_primary),
);

// ボタン
RaisedButton(
    shape: StyleThemeCustom.square,
    onPressed: _pushSaved
);
```

# 終わりに
いかがでしたか？もっと便利な方法あったらぜひ教えて下さい！

# 参考
https://qiita.com/granoeste/items/352d19157c21cd35e21f