最近,我一直在开发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规范。
使用方法:
- 在
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, ), ), ), )
在这里,我使用了codeblockPadding
和codeblockDecoration
。
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,
),
),
),
),
)
从下面的图片中,您可以清楚地看到哪些内容被选中。
🎉🎉🎉
剩余问题:
- 如何突出显示代码块。
- 如何在突出显示后选择代码块。
相关链接:
- https://github.com/gusibi/flutter_learn/tree/main/flutter_markdown_example (opens new window)
- flutter_markdown (opens new window)
- https://pub.dev/packages/flutter_markdown_selectionarea (opens new window)
- [[flutter_markdown] Selecting text works only paragraph by paragraph in markdow n widget when text is not indented (opens new window) 未缩进文本时的 n 小部件