如何在Flutter中使用Markdown

最近,我一直在开发Moli AI (opens new window),它需要支持Markdown渲染和多行复制。本文主要涵盖以下主题:

如何在Flutter中使用Markdown。

如何自定义Markdown样式。

如何在Markdown中启用多行选择。

flutter_markdown #

flutter_markdown是由官方Flutter社区提供的Markdown渲染器。它支持从简单的Markdown标记格式的纯文本数据中创建丰富的文本输出,包括文本样式、表格、链接等。

概述:

  • flutter_markdown包允许您在Flutter中将Markdown文本渲染为富文本。
  • 它建立在Dart的mark 将以下Markdown翻译为中文并删除一级标题:down package并将Markdown解析为抽象语法树(AST)。
  • Markdown允许将HTML注入到源文本中,但flutter_markdown包不支持内联HTML。
  • 默认情况下,flutter_markdown包使用GitHub Flavored Markdown规范。

使用方法:

  1. pubspec.yaml文件中添加flutter_markdown:^0.6.18+3依赖项。
dependencies:  flutter:    sdk: flutter  flutter_markdown: ^0.6.18+3

然后添加以下代码:

import 'package:flutter/material.dart';import 'package:flutter_markdown/flutter_markdown.dart';void main() {  runApp(const MyApp());}class MyApp extends StatelessWidget {  const MyApp({super.key});  // 这个小部件是您应用程序的根。  @override  Widget build(BuildContext context) {    return MaterialApp(      title: 'Flutter Demo',      theme: ThemeData(        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),        useMaterial3: true,      ),      home: const MyHomePag ```class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    var markdown = """这是一个简单的Go程序,将"Hello, world!"打印到控制台😀:```gopackage mainimport "fmt"func main() {    fmt.Println("Hello, world!")}```将上述代码保存为一个带有".go"扩展名的文件,例如"hello.go"。然后,您可以通过在终端中执行以下命令来运行该程序:```shellgo run hello.go```输出将是:```Hello, world!```""";
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: Markdown(
          selectable: true,
          data: markdown,
        ),
      ),
    );
  }
}
``` 如图所示,以下是将Markdown转化为中文并删除一级标题的结果。Markdown被正确渲染,但代码没有任何特殊样式,并且不支持多行选择。

现在,让我们来看如何自定义代码样式。

## 如何自定义样式:

## 使用`styleSheet`属性

使用`styleSheet`属性来自定义Markdown的默认样式。您可以使用`TextStyle`对象来指定字体、颜色、大小和其他属性。

下面是修改后的代码,其中代码块的颜色已更改。它将`h1`标题的字体大小设置为24像素,将`code`的字体大小设置为14像素,并将颜色设置为绿色。

body: Center( child: Markdown( selectable: true, data: markdown, // new start styleSheet: MarkdownStyleSheet( h1: const TextStyle(fontSize: 24, color: Colors.blue), code: const TextStyle(fontSize: 14, color: Colors.green),// new end 将以下Markdown翻译成中文并删除一级标题:

), ),
));

让我们进一步优化样式:

body: Center(          child: Markdown(            selectable: true,            data: markdown,            styleSheet: MarkdownStyleSheet(              h1: const TextStyle(fontSize: 24, color: Colors.blue),              code: const TextStyle(                  fontSize: 14,                  color: Colors.white,                  backgroundColor: Colors.grey),              codeblockPadding: const EdgeInsets.all(8),              codeblockDecoration: BoxDecoration(                borderRadius: BorderRadius.circular(4),                color: Colors.grey,              ),            ),          ),        )

在这里,我使用了codeblockPaddingcodeblockDecoration

  • codeblockPadding控制代码块的填充。您可以使用EdgeInsets对象来指定水平、垂直、左、右、上和下的填充属性。
MarkdownStyleSheet(  codeblockPadding: EdgeInsets.all(8),)
``` 这将代码块的内边距设置为8像素。

* `codeblockDecoration` 控制代码块的装饰。您可以使用 `BoxDecoration` 对象来指定背景颜色、边框、圆角半径和其他属性。

MarkdownStyleSheet( codeblockDecoration: BoxDecoration( borderRadius: BorderRadius.circular(4), color: Colors.grey, ),)


这将代码块的背景颜色设置为灰色,并添加圆角边框。

然而,多行复制仍然不支持。这可能是因为 `flutter_markdown` 使用许多单独的小部件来渲染 Markdown,它们可能不兼容。

如何启用多行选择:

为解决多行选择问题,我们可以使用 `flutter_markdown_selectionarea` 包。

只需在 `pubspec.yaml` 中用以下内容替换 flutter\_markdown 依赖项:

flutter_markdown_selectionarea: ^0.6.17+1


然后将所有导入语句从以下方式更改为:

import 'package:flutter_markdown/flutter_


to

import 'package:flutter_markdown_selectionarea/flutter_markdown.dart';

然后,您将能够通过使用SelectionArea小部件使多个段落可选择:

SelectionArea(  child: MarkdownBody(data: text),

这是修改后的代码:

body: Center(          child: SelectionArea( // 新增            child: Markdown(              // selectable: true, // 删除              data: markdown,              styleSheet: MarkdownStyleSheet(                h1: const TextStyle(fontSize: 24, color: Colors.blue),                code: const TextStyle(                    fontSize: 14,                    color: Colors.white,                    backgroundColor: Colors.grey),                codeblockPadding: const EdgeInsets.all(8),                codeblockDecoration: BoxDecoration(                  borderRadius: BorderRadius.circular(4),                  color: Colors.grey,                ),              ),            ),          ),        ));
``` 现在,多行选择被支持。

虽然支持多行选择,但你可能会注意到选定的代码块的颜色并未改变,这可能会引起困惑,不确定是否实际上被选定。

为了解决这个问题,我使用了Flutter Markdown的`MarkdownElementBuilder`来自定义Markdown的渲染。

要使用`MarkdownElementBuilder`,你需要创建`MarkdownElementBuilder`的子类,并实现`visitElementAfter()`方法。在`visitElementAfter()`方法中,你可以根据Markdown元素的类型生成自定义的Flutter组件。

这里是一个简单的例子:

```dart
class CodeElementBuilder extends MarkdownElementBuilder {  // 判断代码是内联代码块还是代码块  bool isCodeBlock(md.Element element) {    if (element.attributes['class'] != null) {      return true;    } else if (element.textContent.contains("\n")) {      return true;    }    return false;  }  @override
``` Widget? visitElementAfter(md.Element element, TextStyle? preferredStyle) {
    if (!isCodeBlock(element)) {
        return Container(
            padding: const EdgeInsets.all(2),
            child: Text(
                element.textContent,
                style: TextStyle(
                    fontSize: 16,
                    fontStyle: FontStyle.italic,
                    color: preferredStyle!.color),
            ),
        );
    } else {
        return Container(
            margin: const EdgeInsets.all(10),
            child: Text(
                element.textContent,
                style: const TextStyle(fontSize: 16, color: Colors.white),
            ),
        );
    }
} 以下是Markdown的中文翻译,并删除了一级标题:

```dart
color: Colors.pink,
backgroundColor: Colors.grey),
codeblockPadding: const EdgeInsets.all(8),
codeblockDecoration: BoxDecoration(
  borderRadius: BorderRadius.circular(4),
  color: Colors.grey,
),
),
),
),
)

从下面的图片中,您可以清楚地看到哪些内容被选中。

🎉🎉🎉

剩余问题:

  1. 如何突出显示代码块。
  2. 如何在突出显示后选择代码块。

相关链接:

  1. https://github.com/gusibi/flutter_learn/tree/main/flutter_markdown_example (opens new window)
  2. flutter_markdown (opens new window)
  3. https://pub.dev/packages/flutter_markdown_selectionarea (opens new window)
  4. [[flutter_markdown] Selecting text works only paragraph by paragraph in markdow n widget when text is not indented (opens new window) 未缩进文本时的 n 小部件