Flutter文本处理插件flutter_sanity_portable_text的使用
Flutter文本处理插件 flutter_sanity_portable_text
的使用
简介
flutter_sanity_portable_text
是一个用于渲染 Sanity.io 的 Portable Text 格式的 Flutter 插件。它支持多种样式和标记,并且可以高度定制。
特性
- 完整的 Portable Text 支持:渲染所有标准样式和标记
- 块渲染:支持多个不同样式的块
- 自定义:
- 自定义块和块容器
- 自定义样式和标记,包括复杂的注释
- 自定义所有默认样式、块和容器
- 开发者体验:
- 显示未注册的块、标记和样式的内联错误
- 调试时显示有用的错误信息
- 类型安全的 API
安装
在你的 pubspec.yaml
文件中添加以下依赖:
dependencies:
flutter_sanity_portable_text: ^1.0.0
使用示例
使用简单的 TextBlockItem
import 'package:flutter/material.dart';
import 'package:flutter_sanity_portable_text/flutter_sanity_portable_text.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: Scaffold(
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: PortableText(
blocks: [
TextBlockItem(
children: [
Span(
text: 'Sanity Portable Text',
),
],
style: 'h1',
),
],
),
),
),
),
);
}
}
直接从 JSON 渲染
final text = PortableText(
blocks: [
TextBlockItem.fromJson(jsonDecode('''
{
"_type": "block",
"style": "h3",
"children": [
{
"_type": "span",
"text": "Rendered in "
},
{
"_type": "span",
"text": "Flutter",
"marks": ["em", "strong", "underline"]
}
]
}
''')),
],
);
使用多个块和不同的样式
final text = PortableText(
blocks: [
TextBlockItem(
children: [
Span(
text: 'Sanity Portable Text',
),
],
style: 'h1',
),
// 引用块
TextBlockItem(
children: [
Span(
text: '"The best way to predict the future is to invent it."',
),
Span(
text: '\n- Steve Jobs',
),
],
style: 'blockquote',
),
TextBlockItem(
children: [
Span(
text: 'Supports all standard marks and styles, including support for:',
),
],
),
_listItem('Bulleted text', ListItemType.bullet),
_listItem('Numbered text', ListItemType.number),
_listItem('Square bullet text', ListItemType.square),
TextBlockItem(
children: [
Span(text: 'Strong text, ', marks: ['strong']),
Span(text: 'Emphasized text, ', marks: ['em']),
Span(text: 'Underlined text, ', marks: ['underline']),
Span(text: 'Strike through text, ', marks: ['strike-through']),
Span(text: 'All combined', marks: ['strong', 'em', 'underline', 'strike-through']),
Span(text: '.'),
],
),
for (final index in [1, 2, 3, 4, 5, 6])
_textBlock('H$index', style: 'h$index'),
],
);
TextBlockItem _textBlock(String text, {String? style}) {
return TextBlockItem(
children: [
Span(text: text),
],
style: style ?? 'normal',
);
}
TextBlockItem _listItem(String text, ListItemType type) {
return TextBlockItem(
children: [
Span(text: text),
],
listItem: type,
);
}
使用自定义块
import 'package:flutter/material.dart';
import 'package:flutter_sanity_portable_text/flutter_sanity_portable_text.dart';
// 自定义块项
final class CustomBlockItem implements PortableBlockItem {
CustomBlockItem({
required this.text,
required this.foregroundColor,
required this.backgroundColor,
});
@override
String get blockType => 'custom';
final String text;
final Color foregroundColor;
final Color backgroundColor;
}
void main() {
// 注册自定义块
PortableTextConfig.shared.blocks['custom'] = (context, item) {
final theme = Theme.of(context);
final custom = item as CustomBlockItem;
final style =
theme.textTheme.bodyMedium?.apply(color: custom.foregroundColor);
return Container(
decoration: BoxDecoration(
color: custom.backgroundColor,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.blueAccent, width: 2),
boxShadow: const [
BoxShadow(
offset: Offset(0, 4),
blurRadius: 2,
color: Colors.black26,
)
]),
margin: const EdgeInsets.only(bottom: 8),
padding: const EdgeInsets.all(8),
child: Text(custom.text, style: style),
);
};
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: Scaffold(
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: PortableText(
blocks: [
TextBlockItem(
children: [
Span(
text: 'Sanity Portable Text',
),
],
style: 'h1',
),
CustomBlockItem(
text: 'We can also do custom blocks!',
foregroundColor: Colors.white,
backgroundColor: Colors.primaries[4]),
],
),
),
),
),
);
}
}
使用未注册的块会显示错误
import 'package:flutter/material.dart';
import 'package:flutter_sanity_portable_text/flutter_sanity_portable_text.dart';
// 未注册的块项
final class UnregisteredBlockItem implements PortableBlockItem {
@override
String get blockType => 'unregistered';
}
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: Scaffold(
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: PortableText(
blocks: [
TextBlockItem(
children: [
Span(
text: 'Sanity Portable Text',
),
],
style: 'h1',
),
// 这个会显示错误
UnregisteredBlockItem(),
],
),
),
),
),
);
}
}
使用自定义标记
final class CustomMarkDef implements MarkDef {
CustomMarkDef({
required this.color,
required this.key,
});
@override
final String key;
final Color color;
@override
String get type => 'custom-mark';
}
void main() {
// 注册自定义标记
_registerCustomMark();
runApp(const MyApp());
}
void _registerCustomMark() {
PortableTextConfig.shared.markDefs['custom-mark'] = MarkDefDescriptor(
schemaType: 'custom-mark',
styleBuilder: (context, markDef, textStyle) {
final mark = markDef as CustomMarkDef;
final style = textStyle.apply(
decoration: TextDecoration.underline,
decorationColor: mark.color,
);
return style;
},
fromJson: (json) => CustomMarkDef(color: json['color'], key: json['key']),
);
}
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: Scaffold(
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: PortableText(
blocks: [
TextBlockItem(
children: [
Span(
text: 'Sanity Portable Text',
),
],
style: 'h1',
),
TextBlockItem(
children: [
Span(
text: 'We can also do ',
),
Span(text: 'custom marks!', marks: ['custom-key']),
],
markDefs: [
CustomMarkDef(color: Colors.red, key: 'custom-key'),
],
),
],
),
),
),
),
);
}
}
当自定义标记未注册时显示错误
final class UnregisteredMarkDef implements MarkDef {
UnregisteredMarkDef({
required this.key,
});
@override
final String key;
@override
String get type => 'unregistered-mark';
}
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: Scaffold(
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: PortableText(
blocks: [
TextBlockItem(
children: [
Span(
text: 'Sanity Portable Text',
),
],
style: 'h1',
),
TextBlockItem(
children: [
Span(
text: ' and report when a custom mark is not registered, such as:'),
Span(text: ' this.', marks: ['missing-key']),
],
markDefs: [
UnregisteredMarkDef(key: 'missing-key')
],
),
],
),
),
),
),
);
}
}
使用自定义列表构建器来渲染 PortableText
Widget columnBuilder(BuildContext context, List<PortableBlockItem> blocks) {
return Column(
children: blocks
.map((block) => PortableTextConfig.shared.buildBlock(context, block))
.toList(),
);
}
final text = PortableText(blocks: [...], listBuilder: columnBuilder);
更多探索
还有其他一些特性没有在上面的例子中展示,例如:
- 自定义块样式
- 自定义块容器
- 在
PortableText
小部件内的项目填充 - 列表项缩进
- 更改基本样式
- 更改默认块和标记样式
你可以查看 PortableConfig
的属性以获得更多自定义机会。
贡献
欢迎贡献!请随意提交 Pull Request。对于重大更改,请先打开一个问题讨论你想要更改的内容。
学习更多
- 访问 docs.vyuh.tech 获取详细文档
- 查看 GitHub 仓库 获取源代码
- 在 问题跟踪器 上报告问题
许可证
该项目根据 LICENSE 文件中指定的条款进行许可。
更多关于Flutter文本处理插件flutter_sanity_portable_text的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter文本处理插件flutter_sanity_portable_text的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是一个关于如何在Flutter项目中使用flutter_sanity_portable_text
插件来处理文本的示例代码。这个插件通常用于从Sanity CMS(一个headless CMS)获取Portable Text格式的内容,并在Flutter应用中渲染它。
首先,你需要在你的pubspec.yaml
文件中添加依赖:
dependencies:
flutter:
sdk: flutter
flutter_sanity_portable_text: ^最新版本号 # 请替换为当前最新版本号
然后运行flutter pub get
来安装依赖。
接下来是一个完整的示例代码,展示如何使用flutter_sanity_portable_text
插件来渲染Portable Text内容:
import 'package:flutter/material.dart';
import 'package:flutter_sanity_portable_text/flutter_sanity_portable_text.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'FlutterSanityPortableText Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
// 假设这是从Sanity CMS获取的Portable Text内容
final String portableText = `
{
"_type": "block",
"children": [
{
"_type": "heading1",
"children": [
{
"_type": "span",
"text": "Hello, World!",
"marks": []
}
]
},
{
"_type": "paragraph",
"children": [
{
"_type": "span",
"text": "This is a paragraph with some ",
"marks": []
},
{
"_type": "span",
"text": "bold",
"marks": [
{
"_type": "strong"
}
]
},
{
"_type": "span",
"text": " and ",
"marks": []
},
{
"_type": "span",
"text": "italic",
"marks": [
{
"_type": "em"
}
]
},
{
"_type": "span",
"text": " text.",
"marks": []
}
]
}
],
"markDefs": [],
"style": "normal"
}
`;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('FlutterSanityPortableText Demo'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: PortableTextWidget(
blocks: portableText, // 这里传入Portable Text内容
serializers: {
'type': 'default', // 你可以根据需要配置不同的序列化器
'serializers': DefaultSerializers(),
},
styleOverrides: {}, // 你可以在这里覆盖默认样式
),
),
);
}
}
在这个示例中,我们创建了一个简单的Flutter应用,它包含一个从Sanity CMS获取的Portable Text内容,并使用PortableTextWidget
来渲染它。portableText
变量包含了一个示例Portable Text JSON字符串。
请注意,serializers
参数需要配置为适当的序列化器,这里使用了DefaultSerializers()
作为示例。在实际应用中,你可能需要根据你的内容结构和样式需求进行配置。
此外,styleOverrides
参数允许你覆盖默认样式,如果你需要自定义渲染样式,可以在这里进行配置。
这个示例代码展示了基本的用法,你可以根据实际需求进一步扩展和自定义。