Fullter 第三方常用组件之UI增强
Flutter 第三方UI增强组件:SVG与Markdown
一、flutter_svg - SVG图片渲染
简介
flutter_svg 是Flutter中渲染SVG(Scalable Vector Graphics)矢量图形的解决方案,相比位图,SVG具有无损缩放、文件体积小等优点。
安装
dependencies:
flutter_svg: ^2.0.9
基础使用
1. 基本加载方式
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
class SvgBasicPage extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('SVG基础使用')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 1. 从Assets加载SVG
SvgPicture.asset(
'assets/icons/home.svg',
height: 100,
width: 100,
),
SizedBox(height: 20),
// 2. 从网络加载SVG
SvgPicture.network(
'https://raw.githubusercontent.com/flutter/website/main/examples/assets/svg/camera.svg',
height: 100,
width: 100,
placeholderBuilder: (context) => CircularProgressIndicator(),
),
SizedBox(height: 20),
// 3. 从字符串加载SVG
SvgPicture.string(
'''
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<circle cx="50" cy="50" r="45" stroke="blue" stroke-width="3" fill="lightblue"/>
</svg>
''',
height: 100,
width: 100,
),
],
),
),
);
}
}
2. SVG高级用法
class SvgAdvancedPage extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('SVG高级用法')),
body: ListView(
padding: EdgeInsets.all(16),
children: [
// 1. 颜色滤镜 - 改变SVG颜色
Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
Text('颜色滤镜', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
SvgPicture.asset(
'assets/icons/star.svg',
height: 60,
colorFilter: ColorFilter.mode(Colors.red, BlendMode.srcIn),
),
SvgPicture.asset(
'assets/icons/star.svg',
height: 60,
colorFilter: ColorFilter.mode(Colors.green, BlendMode.srcIn),
),
SvgPicture.asset(
'assets/icons/star.svg',
height: 60,
colorFilter: ColorFilter.mode(Colors.blue, BlendMode.srcIn),
),
SvgPicture.asset(
'assets/icons/star.svg',
height: 60,
colorFilter: ColorFilter.mode(Colors.amber, BlendMode.srcIn),
),
],
),
],
),
),
),
SizedBox(height: 20),
// 2. 语义标签 - 为SVG添加描述(无障碍功能)
Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
Text('语义标签', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
SizedBox(height: 10),
Semantics(
label: '五角星图标,表示收藏功能',
child: SvgPicture.asset(
'assets/icons/star.svg',
height: 80,
semanticsLabel: 'Star Icon',
),
),
],
),
),
),
SizedBox(height: 20),
// 3. 网络加载优化 - 缓存和错误处理
Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
Text('网络加载优化', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
SizedBox(height: 10),
SvgPicture.network(
'https://raw.githubusercontent.com/flutter/website/main/examples/assets/svg/flutter_logo.svg',
height: 100,
placeholderBuilder: (context) => Container(
height: 100,
width: 100,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(),
SizedBox(height: 8),
Text('加载中...', style: TextStyle(fontSize: 12)),
],
),
),
),
// 错误占位符
excludeFromSemantics: true,
),
],
),
),
),
SizedBox(height: 20),
// 4. 控制台打印SVG信息(调试用)
Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
Text('SVG调试信息', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
SizedBox(height: 10),
SvgPicture.asset(
'assets/icons/complex.svg',
height: 100,
onDraw: (canvas, size, render) {
print('SVG尺寸: $size');
print('SVG渲染信息: ${render.toString().substring(0, 100)}...');
},
),
],
),
),
),
],
),
);
}
}
3. SVG动画和交互
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
class SvgAnimationPage extends StatefulWidget {
_SvgAnimationPageState createState() => _SvgAnimationPageState();
}
class _SvgAnimationPageState extends State<SvgAnimationPage> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
double _scale = 1.0;
double _rotation = 0.0;
Color _color = Colors.blue;
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(seconds: 2),
vsync: this,
)..repeat(reverse: true);
_animation = Tween(begin: 0.8, end: 1.2).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
);
}
void dispose() {
_controller.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('SVG动画与交互')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 1. 缩放动画
AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Transform.scale(
scale: _animation.value,
child: SvgPicture.asset(
'assets/icons/heart.svg',
height: 100,
colorFilter: ColorFilter.mode(Colors.red, BlendMode.srcIn),
),
);
},
),
SizedBox(height: 40),
// 2. 交互式SVG
GestureDetector(
onTap: () {
setState(() {
_scale = _scale == 1.0 ? 1.5 : 1.0;
_rotation = _rotation == 0.0 ? 180.0 : 0.0;
_color = _color == Colors.blue ? Colors.green : Colors.blue;
});
},
child: Transform.rotate(
angle: _rotation * 3.1415926535 / 180,
child: Transform.scale(
scale: _scale,
child: SvgPicture.asset(
'assets/icons/refresh.svg',
height: 80,
colorFilter: ColorFilter.mode(_color, BlendMode.srcIn),
),
),
),
),
SizedBox(height: 20),
Text('点击旋转/缩放', style: TextStyle(fontSize: 16)),
SizedBox(height: 40),
// 3. 组合多个SVG
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Transform.rotate(
angle: 0.1,
child: SvgPicture.asset(
'assets/icons/star.svg',
height: 60,
colorFilter: ColorFilter.mode(Colors.amber[300]!, BlendMode.srcIn),
),
),
Transform.rotate(
angle: -0.1,
child: SvgPicture.asset(
'assets/icons/star.svg',
height: 80,
colorFilter: ColorFilter.mode(Colors.amber, BlendMode.srcIn),
),
),
Transform.rotate(
angle: 0.1,
child: SvgPicture.asset(
'assets/icons/star.svg',
height: 60,
colorFilter: ColorFilter.mode(Colors.amber[300]!, BlendMode.srcIn),
),
),
],
),
SizedBox(height: 10),
Text('星级评分', style: TextStyle(fontSize: 16)),
],
),
),
);
}
}
4. SVG图标库管理
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
// 图标枚举
enum AppIcons {
home,
settings,
user,
search,
heart,
star,
arrow,
check,
}
// 图标工具类
class IconHelper {
static String getIconPath(AppIcons icon) {
switch (icon) {
case AppIcons.home:
return 'assets/icons/home.svg';
case AppIcons.settings:
return 'assets/icons/settings.svg';
case AppIcons.user:
return 'assets/icons/user.svg';
case AppIcons.search:
return 'assets/icons/search.svg';
case AppIcons.heart:
return 'assets/icons/heart.svg';
case AppIcons.star:
return 'assets/icons/star.svg';
case AppIcons.arrow:
return 'assets/icons/arrow.svg';
case AppIcons.check:
return 'assets/icons/check.svg';
default:
return 'assets/icons/default.svg';
}
}
static Widget getIcon(AppIcons icon, {double size = 24, Color? color}) {
return SvgPicture.asset(
getIconPath(icon),
height: size,
width: size,
colorFilter: color != null
? ColorFilter.mode(color, BlendMode.srcIn)
: null,
);
}
}
// 使用示例
class IconLibraryPage extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('SVG图标库管理')),
body: GridView.count(
crossAxisCount: 3,
padding: EdgeInsets.all(16),
mainAxisSpacing: 16,
crossAxisSpacing: 16,
children: [
_buildIconCard(AppIcons.home, '首页', Colors.blue),
_buildIconCard(AppIcons.settings, '设置', Colors.green),
_buildIconCard(AppIcons.user, '用户', Colors.orange),
_buildIconCard(AppIcons.search, '搜索', Colors.purple),
_buildIconCard(AppIcons.heart, '收藏', Colors.red),
_buildIconCard(AppIcons.star, '评分', Colors.amber),
_buildIconCard(AppIcons.arrow, '箭头', Colors.teal),
_buildIconCard(AppIcons.check, '确认', Colors.green),
],
),
);
}
Widget _buildIconCard(AppIcons icon, String label, Color color) {
return Card(
elevation: 4,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconHelper.getIcon(icon, size: 40, color: color),
SizedBox(height: 8),
Text(label, style: TextStyle(fontSize: 14)),
],
),
);
}
}
二、flutter_markdown - Markdown渲染
简介
flutter_markdown 是Flutter的Markdown渲染器,支持CommonMark规范,可以轻松渲染Markdown文本。
安装
dependencies:
flutter_markdown: ^0.6.15
基础使用
1. 基本Markdown渲染
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
class MarkdownBasicPage extends StatelessWidget {
final String markdownData = '''
# Flutter Markdown 示例
这是一个Markdown渲染示例文档。
## 文本格式化
**粗体文本** 和 *斜体文本* 以及 ~~删除线~~
## 列表
### 无序列表
- 列表项1
- 列表项2
- 子列表项1
- 子列表项2
### 有序列表
1. 第一项
2. 第二项
3. 第三项
## 链接和图片
[访问Flutter官网](https://flutter.dev)

## 引用
> 这是引用文本
> 第二行引用
## 代码
行内代码:`print('Hello World')`
代码块:
```dart
void main() {
runApp(MyApp());
}
def hello():
print("Hello, World!")
表格
| 名称 | 描述 | 价格 |
|---|---|---|
| 商品A | 这是商品A的描述 | $100 |
| 商品B | 这是商品B的描述 | $200 |
文档结束
‘’';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(‘Markdown基础使用’)),
body: SafeArea(
child: Markdown(
data: markdownData,
styleSheet: MarkdownStyleSheet(
h1: TextStyle(fontSize: 32, fontWeight: FontWeight.bold, color: Colors.blue),
h2: TextStyle(fontSize: 24, fontWeight: FontWeight.w600, color: Colors.green),
p: TextStyle(fontSize: 16, height: 1.5),
code: TextStyle(
backgroundColor: Colors.grey[100],
fontFamily: ‘monospace’,
fontSize: 14,
),
blockquote: TextStyle(
color: Colors.grey[700],
fontStyle: FontStyle.italic,
),
),
),
),
);
}
}
#### 2. Markdown高级功能
```dart
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:url_launcher/url_launcher.dart';
class MarkdownAdvancedPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 4,
child: Scaffold(
appBar: AppBar(
title: Text('Markdown高级功能'),
bottom: TabBar(
tabs: [
Tab(text: '样式定制'),
Tab(text: '交互处理'),
Tab(text: '自定义组件'),
Tab(text: '高级用法'),
],
),
),
body: TabBarView(
children: [
_buildStyleTab(),
_buildInteractionTab(),
_buildCustomTab(),
_buildAdvancedTab(),
],
),
),
);
}
Widget _buildStyleTab() {
final markdown = '''
# 自定义样式
## 标题样式
### 三级标题
**粗体** *斜体* ~~删除线~~
`代码`
> 引用文本
- 列表项1
- 列表项2
1. 有序1
2. 有序2
''';
return Markdown(
data: markdown,
styleSheet: MarkdownStyleSheet.fromTheme(ThemeData.dark()).copyWith(
h1: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: Colors.amber,
decoration: TextDecoration.underline,
),
h2: TextStyle(
fontSize: 22,
fontWeight: FontWeight.w600,
color: Colors.lightBlue,
),
strong: TextStyle(
color: Colors.red,
fontWeight: FontWeight.bold,
),
em: TextStyle(
color: Colors.green,
fontStyle: FontStyle.italic,
),
del: TextStyle(
color: Colors.grey,
decoration: TextDecoration.lineThrough,
),
code: TextStyle(
backgroundColor: Colors.grey[900],
color: Colors.lightGreen,
fontFamily: 'RobotoMono',
fontSize: 14,
),
blockquoteDecoration: BoxDecoration(
color: Colors.grey[850],
border: Border(left: BorderSide(color: Colors.amber, width: 4)),
),
blockquotePadding: EdgeInsets.all(16),
listBullet: TextStyle(
color: Colors.purpleAccent,
fontSize: 20,
),
),
);
}
Widget _buildInteractionTab() {
final markdown = '''
# 交互功能
## 链接处理
[点击打开Flutter官网](https://flutter.dev)
[发送邮件](mailto:support@example.com)
[拨打电话](tel:+1234567890)
## 图片点击

## 自定义交互
点击这个链接: [自定义操作](#custom-action)
''';
return Markdown(
data: markdown,
onTapLink: (text, href, title) {
if (href == null) return;
// 处理自定义操作
if (href == '#custom-action') {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('自定义操作'),
content: Text('你点击了: $text'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('确定'),
),
],
),
);
return;
}
// 处理URL链接
_launchUrl(href);
},
imageBuilder: (uri, title, alt) {
return GestureDetector(
onTap: () {
showDialog(
context: context,
builder: (context) => Dialog(
child: Image.network(uri.toString()),
),
);
},
child: Image.network(uri.toString()),
);
},
);
}
Widget _buildCustomTab() {
final markdown = '''
# 自定义组件
## 自定义表格
| 产品 | 价格 | 库存 |
|------|------|------|
| iPhone | \$999 | 100 |
| MacBook | \$1299 | 50 |
| iPad | \$799 | 75 |
## 自定义标签
<warning>
这是一个警告信息
</warning>
<info>
这是一个信息提示
</info>
## 特殊格式
:::tip
这是一个提示框
:::
:::warning
这是一个警告框
:::
''';
return Markdown(
data: markdown,
builders: {
// 自定义表格样式
'table': TableBuilder(),
// 自定义警告标签
'warning': ElementBuilder(
textAlign: WrapAlignment.center,
style: TextStyle(color: Colors.orange, fontWeight: FontWeight.bold),
),
// 自定义信息标签
'info': ElementBuilder(
textAlign: WrapAlignment.center,
style: TextStyle(color: Colors.blue, fontWeight: FontWeight.bold),
),
},
);
}
Widget _buildAdvancedTab() {
return SingleChildScrollView(
padding: EdgeInsets.all(16),
child: Column(
children: [
// 1. 动态生成Markdown
Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('动态Markdown生成', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
SizedBox(height: 10),
MarkdownBody(
data: _generateDynamicMarkdown(),
styleSheet: MarkdownStyleSheet(
p: TextStyle(fontSize: 14),
),
),
],
),
),
),
SizedBox(height: 20),
// 2. Markdown编辑器预览
Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Markdown编辑器', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
SizedBox(height: 10),
TextField(
maxLines: 5,
decoration: InputDecoration(
hintText: '输入Markdown文本...',
border: OutlineInputBorder(),
),
onChanged: (value) {
// 实时预览功能可以在这里实现
},
),
SizedBox(height: 10),
MarkdownBody(
data: '**预览区域**\n在这里显示预览效果',
styleSheet: MarkdownStyleSheet(
p: TextStyle(fontSize: 14),
),
),
],
),
),
),
SizedBox(height: 20),
// 3. 扩展语法示例
Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('扩展语法', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
SizedBox(height: 10),
MarkdownBody(
data: '''
==高亮文本==
上标: 2^10^
下标: H~2~O
脚注[^1]
[^1]: 这是一个脚注
任务列表:
- [ ] 未完成任务
- [x] 已完成任务
定义列表:
术语1
: 定义1
术语2
: 定义2
''',
extensionSet: MarkdownExtensionSet.gitHubWeb,
),
],
),
),
),
],
),
);
}
String _generateDynamicMarkdown() {
final items = ['苹果', '香蕉', '橙子', '葡萄'];
final prices = [5.0, 3.0, 4.0, 6.0];
StringBuffer buffer = StringBuffer();
buffer.writeln('# 动态商品列表\n');
buffer.writeln('## 商品清单\n');
for (int i = 0; i < items.length; i++) {
buffer.writeln('### ${items[i]}');
buffer.writeln('价格: \$${prices[i].toStringAsFixed(2)}');
buffer.writeln();
}
buffer.writeln('**总计**: \$${(prices.reduce((a, b) => a + b)).toStringAsFixed(2)}');
return buffer.toString();
}
Future<void> _launchUrl(String url) async {
if (await canLaunch(url)) {
await launch(url);
} else {
throw '无法打开链接: $url';
}
}
}
3. Markdown自定义构建器
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
class MarkdownCustomBuilderPage extends StatelessWidget {
Widget build(BuildContext context) {
final markdown = '''
# 自定义Markdown构建器
## 自定义代码高亮
```dart
void main() {
runApp(MyApp());
}
def hello():
print("Hello, World!")
console.log("Hello, World!");
自定义卡片组件
这是一个自定义卡片组件 警告信息内容自定义按钮
自定义分隔符
特殊文本
红色文本
蓝色文本
绿色文本
‘’';
return Scaffold(
appBar: AppBar(title: Text(‘自定义Markdown构建器’)),
body: Markdown(
data: markdown,
builders: {
‘card’: CardBuilder(),
‘button’: ButtonBuilder(),
‘separator’: SeparatorBuilder(),
‘red’: ColorTextBuilder(color: Colors.red),
‘blue’: ColorTextBuilder(color: Colors.blue),
‘green’: ColorTextBuilder(color: Colors.green),
},
syntaxHighlighter: DartSyntaxHighlighter(),
),
);
}
}
// 自定义卡片构建器
class CardBuilder extends MarkdownElementBuilder {
@override
Widget? visitElementAfter(MarkdownElement element, TextStyle? preferredStyle) {
String? title;
String? type;
String? content = element.textContent;
// 解析属性
final attributes = element.attributes;
if (attributes[‘title’] != null) {
title = attributes[‘title’];
}
if (attributes[‘type’] != null) {
type = attributes[‘type’];
}
Color cardColor = Colors.blue!;
if (type == ‘warning’) {
cardColor = Colors.orange!;
}
return Container(
margin: EdgeInsets.symmetric(vertical: 8),
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
color: cardColor,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.grey!),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (title != null)
Text(
title,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
if (title != null) SizedBox(height: 8),
Text(
content ?? ‘’,
style: TextStyle(fontSize: 14),
),
],
),
);
}
}
// 自定义按钮构建器
class ButtonBuilder extends MarkdownElementBuilder {
@override
Widget? visitElementAfter(MarkdownElement element, TextStyle? preferredStyle) {
final text = element.attributes[‘text’] ?? ‘按钮’;
final action = element.attributes[‘action’];
return Container(
margin: EdgeInsets.symmetric(vertical: 8),
child: ElevatedButton(
onPressed: () {
if (action == ‘alert’) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(‘按钮点击’),
content: Text(‘你点击了按钮: $text’),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(‘确定’),
),
],
),
);
}
},
child: Text(text),
),
);
}
@override
void handleEvent(MarkdownEvent event, Element? element) {
// 处理事件
}
}
// 自定义分隔符构建器
class SeparatorBuilder extends MarkdownElementBuilder {
@override
Widget? visitElementAfter(MarkdownElement element, TextStyle? preferredStyle) {
return Divider(
thickness: 2,
color: Colors.blue,
height: 40,
);
}
}
// 自定义彩色文本构建器
class ColorTextBuilder extends MarkdownElementBuilder {
final Color color;
ColorTextBuilder({required this.color});
@override
Widget? visitElementAfter(MarkdownElement element, TextStyle? preferredStyle) {
return Text(
element.textContent,
style: TextStyle(
color: color,
fontWeight: FontWeight.bold,
),
);
}
}
// 自定义语法高亮器
class DartSyntaxHighlighter extends SyntaxHighlighter {
@override
TextSpan format(String source) {
// 简化的高亮逻辑,实际项目中可以使用highlight包
final regex = RegExp(r’(\bvoid\b|\bmain\b|\brunApp\b)');
final matches = regex.allMatches(source);
if (matches.isEmpty) {
return TextSpan(text: source, style: TextStyle(color: Colors.black));
}
final spans = [];
int lastIndex = 0;
for (final match in matches) {
if (match.start > lastIndex) {
spans.add(TextSpan(
text: source.substring(lastIndex, match.start),
style: TextStyle(color: Colors.black),
));
}
spans.add(TextSpan(
text: source.substring(match.start, match.end),
style: TextStyle(color: Colors.blue, fontWeight: FontWeight.bold),
));
lastIndex = match.end;
}
if (lastIndex < source.length) {
spans.add(TextSpan(
text: source.substring(lastIndex),
style: TextStyle(color: Colors.black),
));
}
return TextSpan(children: spans);
}
}
#### 4. Markdown性能优化
```dart
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
class MarkdownPerformancePage extends StatefulWidget {
@override
_MarkdownPerformancePageState createState() => _MarkdownPerformancePageState();
}
class _MarkdownPerformancePageState extends State<MarkdownPerformancePage> {
final TextEditingController _controller = TextEditingController();
String _markdownText = '';
bool _useSelectable = true;
bool _cacheRender = true;
// 缓存已渲染的Markdown
Widget? _cachedMarkdown;
@override
void initState() {
super.initState();
_controller.text = '''
# Markdown性能优化
## 大文档处理技巧
当处理大型Markdown文档时,性能优化很重要。
${'这是一个长段落。' * 100}
### 列表性能
${List.generate(100, (i) => '- 列表项 ${i + 1}').join('\n')}
### 表格性能
| 列1 | 列2 | 列3 | 列4 |
|-----|-----|-----|-----|
${List.generate(50, (i) => '| 数据${i + 1}A | 数据${i + 1}B | 数据${i + 1}C | 数据${i + 1}D |').join('\n')}
''';
_markdownText = _controller.text;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Markdown性能优化')),
body: Column(
children: [
// 控制面板
Card(
child: Padding(
padding: EdgeInsets.all(12),
child: Row(
children: [
Switch(
value: _useSelectable,
onChanged: (value) {
setState(() {
_useSelectable = value;
_cachedMarkdown = null;
});
},
),
Text('可选中文本'),
SizedBox(width: 20),
Switch(
value: _cacheRender,
onChanged: (value) {
setState(() {
_cacheRender = value;
_cachedMarkdown = null;
});
},
),
Text('缓存渲染'),
SizedBox(width: 20),
ElevatedButton(
onPressed: () {
setState(() {
_markdownText = _controller.text;
if (!_cacheRender) {
_cachedMarkdown = null;
}
});
},
child: Text('渲染'),
),
],
),
),
),
// 编辑器
Expanded(
flex: 1,
child: Container(
padding: EdgeInsets.all(8),
child: TextField(
controller: _controller,
maxLines: null,
decoration: InputDecoration(
hintText: '输入Markdown文本...',
border: OutlineInputBorder(),
),
),
),
),
// 渲染结果
Expanded(
flex: 2,
child: Container(
padding: EdgeInsets.all(8),
child: _buildMarkdownWidget(),
),
),
],
),
);
}
Widget _buildMarkdownWidget() {
if (_cacheRender && _cachedMarkdown != null) {
return _cachedMarkdown!;
}
final widget = Container(
decoration: BoxDecoration(
color: Colors.grey,
borderRadius: BorderRadius.circular(8),
),
child: _useSelectable
? Markdown(
data: _markdownText,
selectable: true, // 允许文本选择
softLineBreak: true, // 软换行
onTapLink: (text, href, title) {
print('链接点击: $href');
},
styleSheet: MarkdownStyleSheet(
p: TextStyle(fontSize: 14),
code: TextStyle(
backgroundColor: Colors.grey,
fontFamily: 'monospace',
),
),
)
: SingleChildScrollView(
child: MarkdownBody(
data: _markdownText,
styleSheet: MarkdownStyleSheet(
p: TextStyle(fontSize: 14),
),
),
),
);
if (_cacheRender) {
_cachedMarkdown = widget;
}
return widget;
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
三、实际应用场景
1. SVG图标系统
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
class IconSystemPage extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('SVG图标系统')),
body: ListView(
children: [
_buildIconCategory('社交图标', [
'assets/icons/social/facebook.svg',
'assets/icons/social/twitter.svg',
'assets/icons/social/instagram.svg',
'assets/icons/social/linkedin.svg',
]),
_buildIconCategory('文件类型', [
'assets/icons/files/pdf.svg',
'assets/icons/files/doc.svg',
'assets/icons/files/xls.svg',
'assets/icons/files/zip.svg',
]),
_buildIconCategory('支付方式', [
'assets/icons/payment/visa.svg',
'assets/icons/payment/mastercard.svg',
'assets/icons/payment/paypal.svg',
'assets/icons/payment/alipay.svg',
]),
],
),
);
}
Widget _buildIconCategory(String title, List<String> icons) {
return Card(
margin: EdgeInsets.all(8),
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
SizedBox(height: 12),
Wrap(
spacing: 12,
runSpacing: 12,
children: icons.map((path) {
return Container(
width: 60,
height: 60,
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.grey,
borderRadius: BorderRadius.circular(8),
),
child: SvgPicture.asset(path),
);
}).toList(),
),
],
),
),
);
}
}
2. Markdown富文本编辑器
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
class MarkdownEditorPage extends StatefulWidget {
_MarkdownEditorPageState createState() => _MarkdownEditorPageState();
}
class _MarkdownEditorPageState extends State<MarkdownEditorPage> {
final TextEditingController _controller = TextEditingController();
bool _showPreview = true;
final String _template = '''
# 标题
## 二级标题
**粗体** *斜体* ~~删除线~~
[链接](https://example.com)
- 列表项1
- 列表项2
1. 有序项1
2. 有序项2
> 引用
\`\`\`dart
void main() {
print('Hello');
}
\`\`\`
''';
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Markdown编辑器'),
actions: [
IconButton(
icon: Icon(_showPreview ? Icons.visibility_off : Icons.visibility),
onPressed: () {
setState(() {
_showPreview = !_showPreview;
});
},
),
IconButton(
icon: Icon(Icons.content_copy),
onPressed: _insertTemplate,
),
],
),
body: Column(
children: [
// 工具栏
Container(
height: 50,
child: ListView(
scrollDirection: Axis.horizontal,
children: [
_buildToolButton('H1', '# '),
_buildToolButton('H2', '## '),
_buildToolButton('B', '**粗体**'),
_buildToolButton('I', '*斜体*'),
_buildToolButton('S', '~~删除线~~'),
_buildToolButton('链接', '[链接文本](url)'),
_buildToolButton('列表', '- 列表项'),
_buildToolButton('代码', '`代码`'),
_buildToolButton('引用', '> 引用'),
],
),
),
Divider(height: 1),
// 编辑和预览区域
Expanded(
child: _showPreview
? Row(
children: [
// 编辑区
Expanded(
child: Container(
padding: EdgeInsets.all(8),
child: TextField(
controller: _controller,
maxLines: null,
expands: true,
decoration: InputDecoration(
hintText: '输入Markdown...',
border: InputBorder.none,
),
),
),
),
VerticalDivider(width: 1),
// 预览区
Expanded(
child: Container(
padding: EdgeInsets.all(8),
child: Markdown(data: _controller.text),
),
),
],
)
: Container(
padding: EdgeInsets.all(8),
child: TextField(
controller: _controller,
maxLines: null,
expands: true,
decoration: InputDecoration(
hintText: '输入Markdown...',
border: InputBorder.none,
),
),
),
),
],
),
);
}
Widget _buildToolButton(String label, String insertText) {
return TextButton(
onPressed: () {
final selection = _controller.selection;
_controller.text = _controller.text.replaceRange(
selection.start,
selection.end,
insertText,
);
_controller.selection = TextSelection.fromPosition(
TextPosition(offset: selection.start + insertText.length),
);
},
child: Text(label),
);
}
void _insertTemplate() {
_controller.text = _template;
}
}
四、性能优化与最佳实践
1. SVG性能优化
class SvgOptimization {
// 1. 使用SvgPicture.cache缓存
static final Map<String, Widget> _svgCache = {};
static Widget cachedSvg(String assetPath, {double? size}) {
if (_svgCache.containsKey(assetPath)) {
return _svgCache[assetPath]!;
}
final widget = SvgPicture.asset(
assetPath,
height: size,
width: size,
);
_svgCache[assetPath] = widget;
return widget;
}
// 2. 预加载SVG
static Future<void> preloadSvgs(BuildContext context) async {
final svgAssets = [
'assets/icons/home.svg',
'assets/icons/settings.svg',
'assets/icons/user.svg',
];
for (final asset in svgAssets) {
await precachePicture(
ExactAssetPicture(
SvgPicture.svgStringDecoderBuilder,
asset,
),
context,
);
}
}
// 3. 简化复杂SVG
static Widget optimizedSvg(String assetPath) {
return SvgPicture.asset(
assetPath,
excludeFromSemantics: true, // 如果没有无障碍需求
allowDrawingOutsideViewBox: false, // 限制绘制范围
);
}
}
2. Markdown性能优化
class MarkdownOptimization {
// 1. 分块渲染大型文档
static List<Widget> chunkedMarkdown(String content, int chunkSize) {
final chunks = <String>[];
final lines = content.split('\n');
for (int i = 0; i < lines.length; i += chunkSize) {
final end = i + chunkSize < lines.length ? i + chunkSize : lines.length;
chunks.add(lines.sublist(i, end).join('\n'));
}
return chunks.map((chunk) => MarkdownBody(data: chunk)).toList();
}
// 2. 懒加载Markdown
static Widget lazyMarkdown(String content) {
return ListView.builder(
itemCount: (content.length / 1000).ceil(),
itemBuilder: (context, index) {
final start = index * 1000;
final end = (index + 1) * 1000 < content.length
? (index + 1) * 1000
: content.length;
return MarkdownBody(
data: content.substring(start, end),
styleSheet: MarkdownStyleSheet(
p: TextStyle(fontSize: 14),
),
);
},
);
}
// 3. 避免频繁重建
static Widget cachedMarkdown(String content, {String? cacheKey}) {
return CachedMarkdown(
key: ValueKey(cacheKey ?? content.hashCode),
data: content,
);
}
}
class CachedMarkdown extends StatefulWidget {
final String data;
const CachedMarkdown({
Key? key,
required this.data,
}) : super(key: key);
_CachedMarkdownState createState() => _CachedMarkdownState();
}
class _CachedMarkdownState extends State<CachedMarkdown> {
Widget? _cachedWidget;
Widget build(BuildContext context) {
if (_cachedWidget == null) {
_cachedWidget = MarkdownBody(data: widget.data);
}
return _cachedWidget!;
}
}
总结
对比总结
| 特性 | flutter_svg | flutter_markdown |
|---|---|---|
| 主要用途 | 渲染SVG矢量图形 | 渲染Markdown文本 |
| 优势 | 矢量无损缩放、文件小、颜色可编程 | 富文本渲染、样式丰富、扩展性强 |
| 性能考虑 | 复杂SVG影响性能、需要预加载 | 大文档需分块渲染、缓存结果 |
| 适用场景 | 图标、Logo、矢量插画 | 文档展示、富文本编辑器、帮助文档 |
| 最佳实践 | 缓存、预加载、简化复杂路径 | 懒加载、分块渲染、自定义样式 |
使用建议
- SVG使用建议:
- 对于简单的图标,优先使用SVG
- 复杂SVG考虑优化或转换为位图
- 使用颜色滤镜动态改变图标颜色
- 预加载常用SVG资源
- Markdown使用建议:
- 对于静态内容,使用缓存Markdown
- 动态内容考虑懒加载
- 自定义样式保持一致性
- 处理链接点击和图片加载
- 组合使用:
- 在Markdown中嵌入SVG图标
- 使用SVG作为Markdown的装饰元素
- 创建带SVG图标的Markdown组件
这两个包都是Flutter生态中非常实用的UI增强工具,合理使用可以大大提升应用的用户体验和开发效率。
更多推荐
所有评论(0)