😉

【Flutter】Typography Widgetを自作すると便利

2021/08/08に公開

なるべく宣言的にText Widgetを記述したい

Text Widgetは出現回数が比較的多いので、コードから「文字の内容」「スタイル」を読み取るコストをなるべく下げたいなと思うことがよくあります。また、Text Widgetが持つUI上の意味は(誤解を恐れずに言えば)多くの場合「ただの文字」でしかないので、なるべく短く記述したいです。

ということで、例えばこんなふうに書けたら良いのではないかと思いました。Widgetの名前でスタイルが、第一引数で文字の内容がわかります。スタイルがWidget内部に隠蔽されている分、すっきり書けます。

// 見出し
H1('タイトル')

// 標準テキスト
Body1('内容')

// アクションテキスト
ActionText(
  'Sign in',
  onPressed: () {
    signIn();
  },
),

実装例

上記のような使い方をしたい場合、例えばこのように実装することができます。規格化されたTypography Widgetを使い回すことでUI上の統一感が強化されるという恩恵もあります。

このような見た目のTextの実装例を書いていきます。上から見出し、標準テキスト、アクションテキストです。

見出し

lib/ui/components/typography.dart
// 文字色を定義
const bodyColor = Color(0xFF222222);

class H1 extends StatelessWidget {
  const H1(
    this.text, {
    Key? key,
    this.maxLines = 1,
    this.textAlign,
    this.overflow,
    this.color = bodyColor,
  }) : super(key: key);

  final String text;
  final int maxLines;
  final TextAlign? textAlign;
  final TextOverflow? overflow;
  final Color color;

  
  Widget build(BuildContext context) {
   // デバイスの横幅を取得。これをもとに文字サイズを決める。
   final width = MediaQuery.of(context).size.width;

    return Text(
      text,
      style: TextStyle(
        fontSize: width * 0.05,
        fontWeight: FontWeight.bold,
        color: color,
      ),
      maxLines: 1,
      textAlign: textAlign,
    );
  }
}

使用例

const H1('ノーマル'),
const H1('真ん中寄せ', textAlign: TextAlign.center),
const H1('色付き', color: Colors.blue),

標準テキスト

lib/ui/components/typography.dart
// 文字色を定義
const bodyColor = Color(0xFF222222);

class Body1 extends StatelessWidget {
  const Body1(
    this.text, {
    Key? key,
    this.maxLines,
    this.textAlign,
    this.overflow,
    this.isBold = false,
  }) : super(key: key);

  final String text;
  final int? maxLines;
  final TextAlign? textAlign;
  final TextOverflow? overflow;
  final bool isBold;

  
  Widget build(BuildContext context) {
   // デバイスの横幅を取得。これをもとに文字サイズを決める。
   final width = MediaQuery.of(context).size.width;

    return Text(
      text,
      style: TextStyle(
        fontSize: width * 0.036,
        fontWeight: isBold ? FontWeight.bold : FontWeight.normal,
        color: bodyColor,
      ),
      maxLines: maxLines,
      textAlign: textAlign,
      overflow: overflow,
    );
  }
}

使用例

const Body1('ノーマル'),
const Body1('真ん中寄せ', textAlign: TextAlign.center),
const Body1('太字', isBold: true),

アクションテキスト

lib/ui/components/typography.dart
// 文字色を定義
const accentColor = Color(0xFFE29235);

class ActionText extends StatelessWidget {
  const ActionText(
    this.text, {
    Key? key,
    required this.onPressed,
    this.maxLines = 1,
    this.textAlign = TextAlign.start,
    this.overflow,
  }) : super(key: key);

  final String text;
  final void Function() onPressed;
  final int maxLines;
  final TextAlign? textAlign;
  final TextOverflow? overflow;

  
  Widget build(BuildContext context) {
   // デバイスの横幅を取得。これをもとに文字サイズを決める。
   final width = MediaQuery.of(context).size.width;

    return TextButton(
      onPressed: onPressed,
      child: Text(
        text,
        style: TextStyle(fontSize: width * 0.035, color: accentColor),
        maxLines: maxLines,
        textAlign: textAlign,
        overflow: overflow,
      ),
    );
  }
}

使用例

ActionText(
  'Sign in',
  onPressed: () {
    signIn();
  },
),

補足

  • この他にも、大きさを変えたH2やBody2、キャプションやグラデーション付きテキストなどを定義して使うことが多いです。
  • パフォーマンスにこだわる場合はTextではなくRichTextを使って定義すると良いかもしれません。筆者はそのパフォーマンスの差を測定したことがないのと、Textで書くほうがわかりやすいという理由からTextで定義しています

Discussion