5.2 KiB
5.2 KiB
【Demo】富文本渲染(RichTextContent:HTML → 题目/解析)
源文件:
lib/pages/demo/rich_text_demo.dart| 分类:组件封装 一句话:后端给的 HTML(题目/解析/听力材料)渲染成富文本 —— 还支持字号调节、深色模式、自定义样式。
一、这个 Demo 解决什么问题
教育 App 的题目/解析是后端返回的 HTML 字符串(<h3>、<img>、<table>、<audio>…),
前端要把它渲染成漂亮的可交互富文本。本页演示 RichTextContent(lib/core/components/rich_text_content.dart):
- 完整渲染:标题、粗体、上标下标、
<ul>选项、<table>表格; <audio>标签自动替换成本项目的 AudioPlayerCard;- 学生端能力:字号 Aa± 实时调节、深色模式切换(通过
textStyle注入); - 自定义样式:
.answer类 → 红色加粗(customStylesBuilder)。
对照 Vue:相当于
v-html+ 样式定制,但 Flutter 没有浏览器引擎,得用 flutter_html 这类包逐标签渲染。
二、怎么运行 / 演示内容
runApp(const MaterialApp(home: RichTextDemoPage()));
- 顶部控制栏:滑块拖字号(12~24)、切深色开关 → 正文实时变化;
- 页面里:化学方程式上下标、本地图片(
asset://协议)、表格、听力音频、视频、红色加粗的答案 B。
三、核心知识点(新手向)
1. 一行渲染 HTML
RichTextContent(
html: _html, // 后端返回的 HTML 字符串
// 字号 + 深色模式都通过 textStyle 注入(学生端统一入口)
textStyle: TextStyle(
fontSize: _fontSize,
height: 1.6,
color: _dark ? Colors.white70 : const Color(0xFF333333),
),
)
textStyle 注入是核心设计:页面不重建 HTML、不改渲染器,只改一个 TextStyle 就全局换字号/颜色——学生端"无障碍调节"的标准做法。
2. 自定义样式:customStylesBuilder
customStylesBuilder: (element) {
if (element.className.contains('answer')) {
return const {'color': '#EF4444', 'font-weight': 'bold'};
}
return null; // 其他元素不特殊处理
},
后端 HTML 里写 <span class="answer">B</span> → 渲染成红色加粗。按 class 定制样式,不用后端改结构。
3. 本地图片:asset:// 协议
<img src="asset://assets/img/luola_logo.png" width="160" height="160"/>
asset:// + 资源路径 = 加载本地资源图片(最稳,不受网络影响)。实际后端返回完整 URL 或相对路径(相对路径需业务层拼 baseUrl)。
4. audio 标签自动替换
<audio src="asset://assets/audio/sample-30s.mp3" title="听力材料(30 秒)" controls></audio>
组件内部检测 <audio> → 替换成 AudioPlayerCard(见 audio_player_demo.md)——听力题直接能播。
四、关键代码剖析
static const String _html = '''
<h3>第 1 题 · 阅读理解</h3>
<p>光合作用是植物利用光能将<strong>二氧化碳</strong>和<strong>水</strong>合成有机物并释放氧气的过程。化学方程式:6CO<sub>2</sub> + 6H<sub>2</sub>O → C<sub>6</sub>H<sub>12</sub>O<sub>6</sub> + 6O<sub>2</sub>,面积用 x<sup>2</sup> 表示。</p>
<img src="asset://assets/img/luola_logo.png" width="160" height="160"/>
<ul>
<li>A. 光合作用只发生在夜间</li>
<li>B. 光合作用会释放氧气</li>
</ul>
<table border="1" cellpadding="6">
<tr><th>条件</th><th>有光</th><th>黑暗</th></tr>
<tr><td>释放 O₂</td><td>√</td><td>×</td></tr>
</table>
<audio src="asset://assets/audio/sample-30s.mp3" title="听力材料(30 秒)" controls></audio>
<p><b>【解析】</b>正确答案:<span class="answer">B</span>。</p>
''';
字号控制条(12~24 共 12 档):
Slider(
value: _fontSize,
min: 12,
max: 24,
divisions: 12, // 档位数 → 滑块只能停在整数
activeColor: const Color(0xFF3B82F6),
onChanged: (v) => setState(() => _fontSize = v),
)
五、踩坑记录
- textStyle 只影响文本:图片、表格、AudioPlayerCard 是各自样式——深色模式下卡片白底不随主题变(业务侧需按需适配);
- 内联 CSS 只支持常用属性,复杂布局会降级(flutter_html 的边界,别拿它当浏览器);
- 公式(LaTeX)本 demo 未含:以后在
RichTextContent的customWidgetBuilder里扩展(如 flutter_math_fork); - 相对路径图片要业务层拼 baseUrl,别把裸相对路径直接塞进 html;
- 深色模式只是
_dark布尔 + 换色,页面底色/AppBar 也要跟着换(本页手动处理了,主题化见 theme_demo.md)。
六、扩展思考
<audio>替换成的播放器组件见 audio_player_demo.md;- 富文本在真实考试页的运用见 exam_demo.md(题干 HTML 渲染);
- 学生端"字号调节 + 深色模式"这类偏好设置,进阶做法是存进全局状态(Riverpod),见 exam_architecture.md 的 Riverpod 章节;
- 真实项目:
_html换成接口返回的题目 HTML,asset://换成 CDN 完整 URL 即可。