flutter_markdownでcode spans(コードスパン)を柔軟なスタイルにする方法

はじめに

今回もまたまたflutter_markdownについてのブログです。
今回はflutter_markdownでcode spans(コードスパン)の柔軟なデザインを実現するための方法について紹介します。

code span(コードスパン)とは

コードスパンとは下記のようにバッククウォートで囲んだ時のスタイルのことです。

テキスト `code spans` テキスト

スタイル適用時の課題

flutter_markdownでは通常、スタイルを指定する際に MarkdownStyleSheet を利用します。 コードスパンのスタイルは、その中の codeTextStyle を設定します。

MarkdownStyleSheet.fromTheme(Theme.of(context)).copyWith(
  // ...
  code: TextStyle(fontSize: 14, color: Colors.orange),
  // ...
);

この設定を適用した状態で、以下のMarkdownを表示させた結果を見てみます。

Markdown例

## h2 `code span` h2

テキスト `code span` テキスト

表示結果

通常のテキストに code span を表示させる場合には問題ありませんが、見出し(例: h2)など、通常のテキスト以外でコードスパンを表示すると、TextStyle のフォントサイズが固定で 14 に設定されているため、不自然に見えてしまいます。

この問題を解決するためには、親要素(この例では h2)の TextStyle を考慮し、コードスパンのフォントサイズやスタイルが親要素に応じて柔軟に適用される方法が必要です。

以下では、その具体的な方法を共有します。

MarkdownBodyのinlineSyntaxesとbuildersでスタイルを上書きする

以前、以下のブログ記事でカスタムインラインタグの実装方法について紹介しました。

Flutter Markdownでカスタムのインラインタグを実装する方法

今回のテーマでは、この方法を活用して、コードスパンのスタイルをカスタマイズする方法を解説します。

flutter_markdown では、 MarkdownBodyinlineSyntaxesbuilders を使うことで、インライン要素に対してスタイルを柔軟に適用できます。

syntax と builder を実装していきます。

Syntaxの実装

import 'package:markdown/markdown.dart' as md;

class CodeSpanSyntax extends md.InlineSyntax {
  CodeSpanSyntax() : super(r'`([^`]+)`');

  @override
  bool onMatch(md.InlineParser parser, Match match) {
    final content = match.group(1) ?? '';

    final element = md.Element('code_span', []);
    element.attributes['content'] = content;

    parser.addNode(element);

    return true;
  }
}

実装のポイント

その他は下記のブログで説明しているためご覧ください。

flutter_markdownでカスタムのインラインタグを実装する方法

これで、InlineSyntax 側の実装は完了です。 次に、スタイルやビジュアル面でのカスタマイズについて説明します。

builderの実装

import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart' as fmd;
import 'package:markdown/markdown.dart' as md;

class CodeSpanBuilder extends fmd.MarkdownElementBuilder {
  @override
  bool isBlockElement() => false;

  @override
  Widget? visitElementAfterWithContext(
    BuildContext context,
    md.Element element,
    TextStyle? preferredStyle,
    TextStyle? parentStyle,
  ) {
    return Text.rich(
      TextSpan(
        text: ' ${element.attributes['content']} ',
        style: _textStyle(context, parentStyle),
      ),
    );
  }

  TextStyle? _textStyle(BuildContext context, TextStyle? parentStyle) {
    if (parentStyle != null) {
      return parentStyle.copyWith(
        color: Colors.orange,
        fontSize: parentStyle.fontSize != null
            ? parentStyle.fontSize! * 3 / 4
        decoration: parentStyle.decoration,
      );
    } else {
      return TextStyle(
        color: Colors.orange,
        fontSize: 14
      );
    }
  }
}

実装のポイント

parentStyle を使用してスタイルを適用するのがポイントです。 parentStyle は親のスタイルになります。つまり下記のMarkdownの場合h2の TestyStyle になります。今回の場合は親TextStyleのフォントサイズの 3/4 になるように設定しています。

## h2 `code span` h2

その他は下記のブログで説明しているためご覧ください。

flutter_markdownでカスタムのインラインタグを実装する方法

これで MarkdownElementBuilder 側の実装は完了です。

MarkdownBodyにCodeSpanSyntaxとCodeSpanBuilderを適用する

最後に、 MarkdownBody に対して CodeSpanSyntaxCodeSpanBuilder を設定します。以下のように記述することで、カスタムのコードスパンスタイルが適用されます。

return MarkdownBody(
  inlineSyntaxes: [
    CodeSpanSyntax(),
  ],
  builders: {
    'code_span': CodeSpanBuilder(),
  },
);

これで実装は完了です!
以下の画像のように、code spans のフォントサイズが親の TextStyle によって柔軟に変化していること、また通常の code spans のスタイルが上書きされていることを確認できます。

ちなみに・・・

return MarkdownBody(
  builders: {
    'code': CodeSpanBuilder(),
  },
);

実は InlineSyntax を実装しなくても、 builders'code' を指定するだけで code spans のスタイルを上書きすることが可能です。

この方法を採用すると、 code spans のスタイルだけでなく、 コードブロックのスタイル も同時に変更されてしまいます。そのため、 code spans のみをカスタマイズしたい場合には、このアプローチは適していません。

最後に

Markdownの記法でもあるcode spansのスタイルの上書きができることを確認しました。
inline要素で親のスタイルによって変化させたい時は便利だと思います。是非参考にしていただければと思います。