🖍️

【Flutter】自動生成を駆使してデザインやテーマをまとめて管理できるパッケージを作った

2022/11/19に公開約7,000字

Katana Theme

こんにちは。広瀬マサルです。

今回はテーマやデザイン周りの整備を行いました。

flutter_genのようなアセットやフォントの自動生成を行ないつつ

色やテキストスタイルなどを体系的に管理可能なパッケージ

を作成しました。

使い方をまとめたので興味ある方はぜひ使ってみてください!

katana_theme

https://pub.dev/packages/katana_theme

https://pub.dev/packages/katana_theme_builder

はじめに

Flutterのテーマの扱いは複雑怪奇です。

FlutterではThemeDataにアプリのテーマがまとめられていますが、ThemeData内に様々なテーマが入っており、どのテーマがそもそも存在するのか、どのテーマを変更すればウィジェット内のどこが変わるのか非常に分かりづらいです。

Flutterは基本的にMaterialDesignをベースにしています。

そのためMaterialDesign用のカラースキームやテキストサイズを用いてデザインをシンプルに定義できるようなパッケージを作成しました。

合わせてflutter_genなどで使用されているassetsfontsのファイル構成からコードを自動生成する仕組みを導入し体系的に管理できるようにしています。

下記の機能が利用可能です。

  • MaterialDesing3のColorSchemeで色を指定
  • MaterialDesign3のTypeScaleで文字サイズを指定
  • 上記をThemeDataに適用してアプリに指定できる機能
  • assetsフォルダから画像へのパスおよびImageProviderを取得できるコードの自動生成
  • fontsフォルダからフォントファミリーの指定が可能なコードの自動生成
  • テーマからのグラデーションカラーの指定
  • その他簡易的なデザイン変換用メソッド

下記のように記載可能です


final theme = AppTheme(
  primary: Colors.blue,
  secondary: Colors.cyan,
);

Text(
  "test text",
  style: theme.text.bodyMedium.withColor(theme.color.primary),
)

インストール

build_runnerを用いたコードジェネレーションを行うため下記のパッケージをインポートします。

flutter pub add katana_theme
flutter pub add --dev build_runner
flutter pub add --dev katana_theme_builder

実装

テーマ作成

まず@appThemeのアノテーションをつけて、AppThemeDataを作成します。

@appThemeを利用するファイルにはpart ‘元のファイル名.theme.dart’を追加してください。

// theme.dart

part 'theme.theme.dart';


final theme = AppThemeData();

AppThemeScopeの作成とMaterialAppへのテーマ適用

MaterialAppなどの上にAppThemeScopeを作成し、先程定義したAppThemeDataを渡します。

MaterialAppのthemeにはtheme.toThemeData()を渡すことでアプリ内にAppThemeData内で定義したテーマを適用することができます。

// main.dart

AppThemeScope(
  theme: theme,
  child: MaterialApp(
    home: const MyHomePage(
      title: "Flutter Demo",
    ),
    title: "Flutter Demo",
    theme: theme.toThemeData(),
  ),
);

アセットの定義

下記の公式サイトを参考にpubspec.yamlを編集し、アプリ内でアセットを読み込めるようにしてください。

https://docs.flutter.dev/development/ui/assets-and-images

// pubspec.yaml

flutter:
  assets:
    - assets/images/

フォントの定義

下記の公式サイトを参考にpubspec.yamlを編集し、アプリ内でフォントを読み込めるようにしてください。

https://docs.flutter.dev/cookbook/design/fonts

// pubspec.yaml

flutter:
  fonts:
    - family: RobotoMono
      fonts:
        - asset: fonts/RobotoMono-Regular.ttf
        - asset: fonts/RobotoMono-Bold.ttf
          weight: 700

コードジェネレーション

下記のコマンドを入力することで自動でコード生成を行います。

flutter pub run build_runner build --delete-conflicting-outputs

テーマの指定

AppThemeDataにテーマを指定することができます。

マテリアルデザインのカラースキームに従い下記の色を指定可能です。

https://m3.material.io/styles/color/the-color-system/key-colors-tones

  • primary
  • secondary
  • tertiary
  • primaryContainer
  • secondaryContainer
  • tertiaryContainer
  • disabled
  • outline
  • error
  • surface
  • background
  • onPrimary
  • onSecondary
  • onTertiary
  • onPrimaryContainer
  • onSecondaryContainer
  • onTertiaryContainer
  • onDisabled
  • onSurface
  • onBackground
  • onError

さらに追加色として下記を設定可能です。

  • weak
    • 薄字の色。あまり目立たせたくない色に使用
  • warning
    • 注意の色。
  • info
    • ちょっとした情報を表示する際の色
  • success
    • 成功時の色。
  • onWeak
    • weakを背景色にしたときの文字色。
  • onInfo
    • infoを背景色にしたときの文字色。
  • onSuccess
    • successを背景色にしたときの文字色。
  • onWarning
    • warningを背景色にしたときの文字色。
  • splashColor
    • ボタンをタップしたときのエフェクトカラー。
  • shadow
    • 影の色。
  • inverseSurface
    • surfaceの反転色。
  • onInverseSurface
    • inverseSurfaceを背景色にしたときの文字色。

マテリアルデザインのTypographyに従い下記のTypeScaleを指定可能です。

  • displayLarge
  • displayMedium
  • displaySmall
  • headlineLarge
  • headlineMedium
  • headlineSmall
  • titleLarge
  • titleMedium
  • titleSmall
  • bodyLarge
  • bodyMedium
  • bodySmall
  • labelLarge
  • labelMedium
  • labelSmall

使い方

AppThemeDataはグローバルで定義されているのでどこからでも参照可能です。

AppThemeDataの中からは下記のテーマが取得可能です。

  • color
    • AppThemeData作成時に定義したColorScheme。
  • text
    • AppThemeData作成時に定義したTypeScale
  • asset
    • コードジェネレーションで作成したassetsフォルダ以下のアセット
  • font
    • コードジェネレーションで作成したFontFamily。
  • widget
    • インジケーターなどの固定ウェジェット定義。
    • 後述のテーマ拡張で定義します。
class TestPage extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Title"), backgroundColor: theme.color.secondary),
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children:[
          Center(child: CircleAvatar(backgroundImage: theme.asset.userIcon.provider)),
          Text("User Name", style: theme.text.displayMedium)
        ]
      )
    );
  }
}

テーマ拡張

アプリによっては特殊な色や文字サイズを使う場合もあります。

1箇所でよければ直接ウィジェットに指定してもよいのですが、アプリ内の複数箇所にまたがる場合はテーマで指定したほうが効率的です。

このパッケージではextensionを用いることにより新しいテーマを追加します。

下記のクラスをonに指定してextensionを作成可能です。

  • ColorThemeData
    • 色を追加します。
  • TextThemeData
    • 文字サイズなどのTextStyleを追加します。
  • AssetThemeData
    • 画像などのアセットを追加します。
  • WidgetThemeData
    • インジケーターなどのウェジェットを追加します。
extension ColorThemeDataExtensions on ColorThemeData {
  Color get myColor => Colors.red;
}

extension WidgetThemeDataExtensions on WidgetThemeData {
  Widget get indicator => const LinearProgressIndicator();
}

応用した使い方

グラデーション

テーマにGradientColorを指定することでグラデーションを適用することが可能です。

GradientColorはなにもしないと通常色として認識されますが、toLinearGradient()でグラデーションカラーとして利用可能です。

// theme.dart


final theme = AppThemeData(
  primary: GradientColor(
    Colors.red,  // こちらが通常色として認識される
    Colors.white,
  )
);

// test_page.dart

class TestPage extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Title")),
      body: Container(
        decoration: BoxDecoration(
          gradient: theme.color.primary.toLinearGradient(),
        ),
      )
    );
  }
}

変換用メソッド

実際に使用する際にテーマのデータに若干修正を加えて出力可能です。

final Color darkenColor = theme.color.primary.darken(); // すこし色を暗くして出力
final Color lightenColor = theme.color.primary.lighten(); // すこし色を明るくして出力

final TextStyle smallizeText = theme.text.bodyMedium.smallize(); // すこし文字サイズを小さくして出力
final TextStyle largizeText = theme.text.bodyMedium.largize(); // すこし文字サイズを大きくして出力
final TextStyle changedFontSizeText = theme.text.bodyMedium.withSize(18); // フォントサイズを18にして出力

final TextStyle darkenText = theme.text.bodyMedium.darken(); // すこし文字色を暗くしてして出力
final TextStyle lightenText = theme.text.bodyMedium.lighten(); // すこし文字色を明るくして出力
final TextStyle changedFontColorText = theme.text.bodyMedium.withColor(theme.color.primary); // すこし文字色をプライマリーカラーにして出力

final TextStyle changedOpacityText = theme.text.bodyMedium.withOpacity(0.5); // 文字の透明度を半分にして出力
final TextStyle changedFontWeightText = theme.text.bodyMedium.withBold(); // 文字を太字にして出力

おわりに

自分で使う用途で作ったものですが実装の思想的に合ってそうならぜひぜひ使ってみてください!

また、こちらにソースを公開しているのでissueやPullRequestをお待ちしてます!

また仕事の依頼等ございましたら、私のTwitterWebサイトで直接ご連絡をお願いいたします!

https://mathru.net/

Discussion

ログインするとコメントできます