サンダーボルト

相手モンスターを全て破壊する。

Flutterでカラーテーマを動的に変更する

実現したいこと

こんな感じのことをしたい

f:id:nao_666:20190501184509g:plain
カラーテーマの動的変更

説明とかいいからサンプルよこせという方はこちらから。cloneして実行して実際にいじりながら試してみてください。

ライブラリ等を探してみた

とりあえず探してみました。すると、

proandroiddev.com

いい感じの記事を見つけた!しかも配布されている😊

pub.dartlang.org

これで終わった〜😊 と、思ったらちょっと機能が足りませんでした。

このライブラリができることは

  • 動的にbrightness(明暗)を変更できる
  • 変更したbrightnessshared_preferencesに保存される
  • 次回起動時に自動的に保存したbrightnessが適用される

というものでした。しかし、今自分がやりたいことはbrightnessだけでなく、primarySwatchも変更/保存したい、ということだったため少し機能不足でした。

FlutterのThemeに関する補足

ここで急にbrightnessとかprimarySwatchとか出てきて、チョ・マテヨ👩ってなってる人に向けて補足説明をします。自分も最初FlutterでThemeを扱ったとき、どういうクラス構造になっているのか分からず道に迷ってしまいました。ThemeData, primarySwatch, brightness, MaterialColor等のクラスや引数の関係性がだいたい分かっている方は読み飛ばしてOKだと思います。

まず、Flutterで起動時にテーマを指定するサンプルコードを見てみましょう。

return MaterialApp(
  title: 'Sample App',
  theme: ThemeData(
    primarySwatch: Colors.blue,
    brightness: Brightness.light,
  ),
  home: SomePage(),
);

こう書くと、アプリ全体が青い配色になって、背景は白くなります。
上記サンプルコードではthemeというパラメータにThemeDataオブジェクトを渡しているのが分かると思います。これを見ながら語句の解説をしましょう。

解説
ThemeData Flutterのデザインテーマのデータを全て持つ。明るさ、プライマリカラー、アクセントカラーやその他ボタンの色やAppBarの色なんかも持っている
primarySwatch 一言で言えば、「色見本」(swatchは見本という意味)。型はMaterialColor
brightness 明るさ。これもThemeDataのコンストラクタに渡すことができる。lightとdarkしかない。
MaterialColor クラス。dartにはColorというクラスもあるが、それとは別。Colorは1つの色しか保持できないのに対し、MaterialColorは「同じ系統の複数の色」(濃い青、普通の青、薄い青等)を保持できる。

もう少し付け加えると、まずThemeDataのコンストラクタには直接primaryColoraccentColor等が渡せるようにもなっています。しかし、このprimarySwatchを渡しておけばそれらの値はprimarySwatchから取り出されます。

github.com

また、上記説明から分かるとは思いますが、引数primarySwatchの値はColors.blueで、このColors.blueの型はMaterialColorです。つまり、blueと言っても、実際には濃い青から薄い青まで様々な値を保持しています。

ここでThemeDataの特徴なのですが、 brightnessがdarkの場合はprimarySwatchが何であろうとダークテーマになります。先程のソースコードを見てもらえれば分かると思いますが、例えばprimaryColorに関しては

primaryColor ??= isDark ? Colors.grey[900] : primarySwatch;

となっていて、darkのときはColors.grey[900]が適用されるようになっています。primarySwatchがblueであろうとredであろうと関係ないというわけです。

カスタマイズする

見つけたライブラリではうまくprimarySwatchを変更/保持できなかったのでそれができるように変更していきます。見つけたライブラリですが、実は中身はdynamic_theme.dart1ファイルだけという非常にシンプルな作りになっています。

今回はこのライブラリのソースをまるっと自分のプロジェクト内にコピーして好きなように変更していきます。要点だけ説明します。

主な変更箇所

元のソースコードこちら

1. L34 shared preferenceのキー

static const String _sharedPreferencesKey = 'isDark';

brightnessだけを保存するのではなく、テーマ名を保存することにするので、このkeyは変更します

static const String _themeTypeKey = 'theme_type';

2. L67 setBrightnessメソッド

setBrightnessからsetThemeに変更します。

void setTheme(ThemeType theme) async {
  setState(() {
    this._data = themeMap[theme];
  });
  SharedPreferences prefs = await SharedPreferences.getInstance();
  await prefs.setString(_themeTypeKey, theme.toString());
}

3. L83 loadBrightnessメソッド

loadBrightnessからsetThemeに変更します。

Future<ThemeType> loadThemeType() async {
  SharedPreferences prefs = await SharedPreferences.getInstance();
  return (ThemeType.of(prefs.getString(_themeTypeKey)) ?? ThemeType.BLUE);
}

フォルダ構成

今回は、dynaic_theme.dartの他、テーマの種類を保持するenumのようなクラスであるtheme_type.dartと実際のテーマの色やbrightnessを定義したapp_theme.dartを追加します。サンプルでは、home画面でテーマを選択できるRadioListTileのリストを表示するので、そのためのtheme_select.dartも追加します。その結果、フォルダ構成は以下のようになります。

lib/
┣ theme/
┃ ┣ app_theme.dart
┃ ┣ dynamic_theme.dart
┃ ┗ theme_type.dart
┣━ main.dart
┗━ theme_select.dart

使い方

使い方は本家dynamic_themeと変わりません。MaterialAppを生成するときにラップしてあげてください。

return DynamicTheme(
  themedWidgetBuilder: (context, theme) {
    return MaterialApp(
      title: 'Flutter Theme Example',
      theme: theme,
      home: ThemeSelectPage(),
    );
  },
);

ピンク濃すぎ問題

さて、ここで問題が発生しました。app_theme.dartに様々なMaterialColorでテーマを定義したのですが、なんかピンク(Colors.pink)が濃い...

f:id:nao_666:20190502215203p:plain:w300
ピンクがなんだか濃い

ということで、独自のピンク色のテーマを定義することにしました。元のピンク色の定義はこんな感じでした。

static const MaterialColor pink = MaterialColor(
    _pinkPrimaryValue,
    <int, Color>{
       50: Color(0xFFFCE4EC),
      100: Color(0xFFF8BBD0),
      200: Color(0xFFF48FB1),
      300: Color(0xFFF06292),
      400: Color(0xFFEC407A),
      500: Color(_pinkPrimaryValue),
      600: Color(0xFFD81B60),
      700: Color(0xFFC2185B),
      800: Color(0xFFAD1457),
      900: Color(0xFF880E4F),
    },
  );

ソース

50の方が徐々に薄くなるようグラデーションをつけて色が定義されています。今回は、最も濃くても700くらいの濃さがあれば十分だと思ったので元の700を900に使って以下のようにあらたなピンク色を定義します。50,100,200が同じ色になってしまっていますが、単なる妥協です😊

static const int _pinkPrimaryValue = 0xFFF06292;
static const MaterialColor softPink = MaterialColor(
  _pinkPrimaryValue,
  <int, Color>{
    50: Color(0xFFFCE4EC),
    100: Color(0xFFFCE4EC),
    200: Color(0xFFFCE4EC),
    300: Color(0xFFF8BBD0),
    400: Color(0xFFF48FB1),
    500: Color(_pinkPrimaryValue),
    600: Color(0xFFEC407A),
    700: Color(0xFFE91E63),
    800: Color(0xFFD81B60),
    900: Color(0xFFC2185B),
  },
);

これで(個人的には)いい感じのピンクになりました。

f:id:nao_666:20190502215203p:plain:w300
もとの濃いピンク
f:id:nao_666:20190502215242p:plain:w300
いい感じのピンク

実際に製品として動くものを見たい

この機能は自分が作った以下アプリに実装されています。(プロフィール→設定→テーマ)

Wishes 〜みんなの夢リスト〜

Wishes 〜みんなの夢リスト〜

Wishes 〜みんなの夢リスト〜

  • Naoya Kimura
  • Lifestyle
  • Free
play.google.com

実際に触ってみたい方は是非〜!