Flutterで高性能なMarkdown表示!「flutter_md」パッケージ

FlutterアプリでMarkdownテキストを美しく表示したい、AI生成コンテンツをリッチに表現したいと思ったことはありませんか? そんなときに最適なのが flutter_md パッケージです。高性能で軽量なMarkdownパーサー・レンダラーとして、特にAIアシスタントのコンテンツ表示に最適化されています。


1. flutter_mdとは?

flutter_md は、Flutter専用に設計された高性能で軽量なMarkdownパーサー・レンダラーです。ChatGPT、Gemini、その他のLLM(大規模言語モデル)からのフォーマット済みテキスト表示に最適化されています。

主な特徴:

  • 高性能:最適化された解析で最小限のメモリフットプリント
  • 完全カスタマイズ可能:テーマベーススタイリング
  • Flutter ネイティブ:カスタムレンダーオブジェクトを使用
  • インタラクティブ要素:クリック可能なリンク
  • クロスプラットフォーム:全Flutterプラットフォーム対応

2. インストール方法

pubspec.yaml に以下を追加します:

dependencies:
  flutter_md: ^0.0.5

インストール後に実行:

flutter pub get

3. 基本的な使い方

import 'package:flutter/material.dart';
import 'package:flutter_md/flutter_md.dart';

class MarkdownDisplayWidget extends StatelessWidget {
  final String markdownContent = """# サンプルMarkdown

これは **太字** で、これは *斜体* です。

## リスト
- 項目1
- 項目2
  - ネストした項目
- 項目3

## リンク
[Flutter公式サイト](https://flutter.dev)

> これは引用文です。複数行にわたって表示できます。

## コードサンプル
print('Hello, Markdown!');
""";

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Markdown表示')),
      body: Padding(
        padding: EdgeInsets.all(16.0),
        child: MarkdownWidget(
          markdown: Markdown.fromString(markdownContent),
        ),
      ),
    );
  }
}

4. テーマとカスタマイズ

基本的なテーマ設定

class CustomMarkdownWidget extends StatelessWidget {
  final String markdownText;
  
  CustomMarkdownWidget({required this.markdownText});

  @override
  Widget build(BuildContext context) {
    return MarkdownTheme(
      data: MarkdownThemeData(
        textStyle: TextStyle(
          fontSize: 16.0, 
          color: Colors.black87,
          height: 1.6,
        ),
        h1Style: TextStyle(
          fontSize: 28.0,
          fontWeight: FontWeight.bold,
          color: Colors.blue.shade800,
        ),
        h2Style: TextStyle(
          fontSize: 24.0,
          fontWeight: FontWeight.bold,
          color: Colors.blue.shade600,
        ),
        quoteStyle: TextStyle(
          fontSize: 14.0,
          fontStyle: FontStyle.italic,
          color: Colors.grey[600],
        ),
        // リンククリック処理
        onLinkTap: (title, url) {
          print('リンクがタップされました: $title -> $url');
          // URL起動やナビゲーション処理
        },
      ),
      child: MarkdownWidget(
        markdown: Markdown.fromString(markdownText),
      ),
    );
  }
}

5. AI チャット風アプリでの活用

class ChatMessage extends StatelessWidget {
  final String message;
  final bool isUser;
  
  ChatMessage({required this.message, required this.isUser});

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          if (!isUser) ...[
            CircleAvatar(
              backgroundColor: Colors.blue,
              child: Icon(Icons.smart_toy, color: Colors.white),
            ),
            SizedBox(width: 12),
          ],
          Expanded(
            child: Container(
              padding: EdgeInsets.all(12.0),
              decoration: BoxDecoration(
                color: isUser ? Colors.blue.shade100 : Colors.grey.shade100,
                borderRadius: BorderRadius.circular(12.0),
              ),
              child: isUser
                  ? Text(message, style: TextStyle(fontSize: 16.0))
                  : MarkdownTheme(
                      data: MarkdownThemeData(
                        textStyle: TextStyle(fontSize: 16.0),
                        h3Style: TextStyle(
                          fontSize: 18.0,
                          fontWeight: FontWeight.bold,
                        ),
                        // コードブロックの背景色設定
                        codeBlockDecoration: BoxDecoration(
                          color: Colors.grey.shade200,
                          borderRadius: BorderRadius.circular(8.0),
                        ),
                      ),
                      child: MarkdownWidget(
                        markdown: Markdown.fromString(message),
                      ),
                    ),
            ),
          ),
          if (isUser) ...[
            SizedBox(width: 12),
            CircleAvatar(
              backgroundColor: Colors.green,
              child: Icon(Icons.person, color: Colors.white),
            ),
          ],
        ],
      ),
    );
  }
}

6. パフォーマンス最適化

大きなMarkdownドキュメントや頻繁に変更されるコンテンツの場合:

class OptimizedMarkdownWidget extends StatefulWidget {
  final String markdownContent;
  
  OptimizedMarkdownWidget({required this.markdownContent});

  @override
  _OptimizedMarkdownWidgetState createState() => _OptimizedMarkdownWidgetState();
}

class _OptimizedMarkdownWidgetState extends State<OptimizedMarkdownWidget> {
  late final Markdown _parsedMarkdown;

  @override
  void initState() {
    super.initState();
    // 初期化時に一度だけMarkdownを解析
    _parsedMarkdown = Markdown.fromString(widget.markdownContent);
  }

  @override
  Widget build(BuildContext context) {
    return MarkdownWidget(markdown: _parsedMarkdown);
  }
}

7. 高度なカスタマイズ

ブロックフィルタリング

MarkdownTheme(
  data: MarkdownThemeData(
    // 段落とヘッダーのみ表示
    blockFilter: (block) {
      return block is MD$Paragraph || block is MD$Heading;
    },
    // 画像とスポイラーを除外
    spanFilter: (span) {
      return !span.style.contains(MD$Style.image) &&
             !span.style.contains(MD$Style.spoiler);
    },
  ),
  child: MarkdownWidget(markdown: parsedMarkdown),
)

カスタムスタイル処理

class AdvancedMarkdownWidget extends StatelessWidget {
  final String markdownText;
  
  AdvancedMarkdownWidget({required this.markdownText});

  @override
  Widget build(BuildContext context) {
    return MarkdownTheme(
      data: MarkdownThemeData(
        textStyle: TextStyle(fontSize: 16.0),
        // カスタムビルダー
        builder: (block, theme) {
          if (block is MD$Code && block.language == 'dart') {
            return Container(
              padding: EdgeInsets.all(12.0),
              decoration: BoxDecoration(
                color: Colors.blue.shade50,
                border: Border.all(color: Colors.blue.shade200),
                borderRadius: BorderRadius.circular(8.0),
              ),
              child: Text(
                block.text,
                style: TextStyle(
                  fontFamily: 'monospace',
                  color: Colors.blue.shade800,
                ),
              ),
            );
          }
          return null; // デフォルトのペインターを使用
        },
      ),
      child: MarkdownWidget(
        markdown: Markdown.fromString(markdownText),
      ),
    );
  }
}

8. 対応しているMarkdown記法

記法サンプル説明
太字**text** または __text__テキストを太字にする
斜体*text* または _text_テキストを斜体にする
取り消し線~~text~~テキストに取り消し線を追加
インラインコード`code`インラインコードの表示
ハイライト==text==テキストのハイライト
スポイラー`
ヘッダー# H1###### H6見出しレベル1-6
リスト- item または 1. item順序なし・順序ありリスト
引用> quote引用ブロック
コードブロック```language構文ハイライト付きコード
テーブル`Col1
リンク[text](url)クリック可能リンク

9. パフォーマンス指標

項目性能備考
解析速度~300μs典型的なAI応答の場合
他パッケージとの比較15倍高速markdownパッケージ比
レンダリング120 FPSチャット風インターフェース
メモリ使用量最小限効率的なスパンフィルタリング

10. 実用例:ドキュメントビューア

class DocumentViewer extends StatefulWidget {
  @override
  _DocumentViewerState createState() => _DocumentViewerState();
}

class _DocumentViewerState extends State<DocumentViewer> {
  String documentContent = "";
  
  @override
  void initState() {
    super.initState();
    loadDocument();
  }
  
  Future<void> loadDocument() async {
    // ドキュメントの読み込み(AssetやHTTP)
    final content = await rootBundle.loadString('assets/document.md');
    setState(() {
      documentContent = content;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('ドキュメント'),
        actions: [
          IconButton(
            icon: Icon(Icons.share),
            onPressed: () {
              // 共有機能
            },
          ),
        ],
      ),
      body: documentContent.isEmpty
          ? Center(child: CircularProgressIndicator())
          : SingleChildScrollView(
              padding: EdgeInsets.all(16.0),
              child: MarkdownTheme(
                data: MarkdownThemeData(
                  textStyle: TextStyle(fontSize: 16.0, height: 1.6),
                  onLinkTap: (title, url) {
                    // URLを開く処理
                    print('Opening: $url');
                  },
                ),
                child: MarkdownWidget(
                  markdown: Markdown.fromString(documentContent),
                ),
              ),
            ),
    );
  }
}

まとめ

特徴内容
高性能設計Flutter専用のカスタムレンダーオブジェクト使用
豊富なMarkdown対応標準記法からスポイラーまで幅広くサポート
完全カスタマイズテーマ、フィルター、カスタムビルダー対応
AI最適化ChatGPTやGeminiのコンテンツ表示に特化
クロスプラットフォーム全Flutter対応プラットフォームで動作

flutter_md パッケージは、Flutter専用に最適化された高性能Markdownレンダラーとして、特にAI生成コンテンツの表示に威力を発揮します。 HTMLレンダリングやWebViewに依存せず、Flutterのレンダリングパイプラインに完全統合されているため、一貫したパフォーマンスと見た目を提供できます!


参考リンク

タイトルとURLをコピーしました