MD 阅读 Android 版 · 图形高度自适应与全屏查看
两个问题的根因完全不同,分别处理。
- 版本:1.1.4 (1001004) · debug 包
- 真机:HUAWEI Mate 30 Pro (LIO-AN00) / Android 12 / 1176×2400 / density 480
- 日期:2026-09-24
- 范围:图形渲染层(Mermaid + SVG 共用)的内联高度算法与全屏查看层
一、老板反馈的两个问题
| # | 问题 | 现象 |
|---|---|---|
| 1 | 「全屏查看」按钮点击无反应 | 点图形块没有任何响应 |
| 2 | 内联小图高度不对 | 7.5 状态图左右合适但高度过低、显示不全;7.8 时间线高度过高、底部大块留白 |
两个问题的根因完全不同,分别处理。
二、问题 1:点击无反应
原因
不是"按钮失效",而是全屏查看层从来就不存在:
ui/MarkdownRender.kt一直把onGraphClick(kind, code)往上传(Block.Mermaid/Block.Svg两处);- 但
ui/ReaderScreen.kt调BlockItem(block = block, onOpenUrl = onOpenUrl)时从没传这个参数,
回调恒为 null;
- 且工程里没有任何全屏层组件(当时只是画了一个「全屏查看」提示标签)。
修复
- 新增
ui/GraphFullScreen.kt(对应鸿蒙Index.ets的FullscreenViewer):
蓝色顶栏 #2a6fDB(「渲染图」+「关闭」)+ 白色 Web 区 + insetBottom 避让导航条;
BackHandler 让返回键优先退全屏而不是退出应用。
ui/GraphView.kt新增fullscreen: Boolean参数,全屏时向页面传fitWidth = false
(保持自然尺寸,交给页内手势缩放/平移);全屏的容器用 fillMaxSize() 而非固定高度。
ui/ReaderScreen.kt外层套Box,把全屏层作为 overlay 盖住标题栏与正文,并接上点击回调。- 先渲染轻量占位、下一帧才挂 Web(对齐鸿蒙
fsReady策略):点击所在帧不必等
2.5MB 的 mermaid.min.js 初始化完成,避免"点击完成时延"被判不合格。
三、问题 2:内联高度两个方向都出错
原因
旧实现直接读 svg.getBoundingClientRect().height。这个值是元素盒,受内联 style 与布局时机
双重干扰 —— 实测两个方向都会错,所以两个图表现出相反的症状:
- 偏小 → 显示不全:无
viewBox的 SVG 在max-width:100% + height:auto下失去等比依据,
高度塌成 height 属性值或浏览器默认 150px;外加 mermaid 输出偶有内容溢出 viewBox 被裁。
- 偏大 → 底部留白:宿主侧的高度区间校验把合法的小值拒了。
铁证
旧代码 GRAPH_MIN_H = 80,而时间线的真实高度实测 79:
I MDGraph : notify h=79 accepted=true full=false
79 < 80 → 被区间校验丢弃 → viewHeight 保持初始值 260 →
底部留白 181px。这就是 7.8「高度过高」的确切原因(不是测量偏大,是不合法值被拒)。
修复(三层)
① 确定性高度计算(assets/graph.html 的 getInlineHeight())
不再读渲染盒,改用与浏览器同一套算法:
h = min(自然宽, 可用宽) × vbH / vbW + 容器上下 padding + 警告条高度
即"宽度以容器为限、高度按 viewBox 比例自动",与渲染结果一致且与布局时机无关。
② viewBox 归一与内容对齐
applyInlineSizing()三步走:保证有viewBox(无则先由width/height属性补,
再用 getBBox() 补)→ 把内容包围盒并入/收拢 viewBox → width/height 属性写回 viewBox 尺寸
(消除 width="100%" 这类百分比带来的歧义)。
- 新增
fitViewBoxToContent():getBBox()得到内容真实包围盒,与viewBox对比 ——
溢出就扩(治显示不全)、富余过多就收(治图形自身留白)。 阈值保守:偏差超 25% 且 ≥30 单位才动,并留 2% 余量,避免裁掉描边或文字外沿。
③ 页面主动上报 + 放宽区间校验
- 页面渲染稳定后
console.log('MDH|' + h),Kotlin 在onConsoleMessage里截获;
另在 120ms / 600ms 各补一次(等字体就绪)。原有的定时轮询降级为兜底。
走 console 而不用 addJavascriptInterface —— 不向页面暴露任何可调用的原生对象,零安全面。
- 高度下限
GRAPH_MIN_H由 80 放宽到 24,扁图形(时间线等)不再被误拒。 - 单位口径确认:WebView 的
devicePixelRatio == Android density(本机均为 3.0),
1 CSS px == 1 dp,Kotlin 侧可直接当 dp 使用。
四、附加修复:全屏内图形贴顶不居中
全屏打开后图形贴在顶部、下方大片空白。诊断日志(MDFS| → fsGeom)给出确凿数据:
fsGeom {"nw":376,"nh":73,"sc":1,"min":1,"vw":376,"vh":0,"tx":0,"ty":319,...}
^^^^^^ vh 读到 0
vh = 0 说明容器内容盒高度塌陷。逐版排查:
| 版本 | 写法 | 结果 |
|---|---|---|
| ① | body{height:100%} + #container.actual{height:100%} | clientHeight 塌成 16px(只剩 padding)、vh=0 → 不做垂直居中 |
| ② | 改 body{height:100vh} | 百分比链仍断;且 overflow:hidden 把图形整块裁掉(全屏一片空白) |
| ③ ✅ | #container.actual{position:fixed; top/left/right/bottom:0} | vh=695、ty=311 → 居中正确 |
根因:本页的 html 不能设高度(设了就触发本项目已定位的「普通流元素整块不绘制」问题),
于是 body 的百分比链断裂,height:100% 解析成 0。
position:fixed 的包含块是视口,完全不依赖父级百分比链,一劳永逸。
配套两处修正:
computeLimits()改用getBoundingClientRect()读自然尺寸 ——
SVGElement.scrollWidth/scrollHeight 对根 <svg> 返回偏大值,导致 naturalH 虚高、
被误判为"内容比视口高"而不做居中;
clampPan()改用contentBox()(减掉 padding)算可用宽高。
五、验收(Mate 30 Pro / demo.md 全量)
内联高度(问题 2)
| 图形 | 结果 |
|---|---|
| 7.1 流程图(纵向) | ✅ 完整、高度贴合 |
| 7.2 流程图(横向) | ✅ 完整、高度贴合 |
| 7.3 时序图 | ✅ |
| 7.4 类图 | ✅ |
| 7.5 状态图 | ✅ 完整显示(未打开 → 预览 → 源码/图形全屏/终止),此前"高度过低显示不全"已消除 |
| 7.6 甘特图 | ✅ |
| 7.7 饼图 | ✅ |
| 7.8 时间线 | ✅ 底部留白消除(notify h=79 已被接受) |
| 8.1 SVG 柱状图 | ✅ |
| 8.2 SVG 环形进度 | ✅ |
全屏查看(问题 1)
| 项 | 结果 |
|---|---|
| 点击图形块进入全屏 | ✅ 蓝栏「渲染图」+「关闭」,图形完整显示 |
| 全屏内垂直居中 | ✅ 横向流程图 nh=73 → ty=311;时间线 nh=194 → ty=251 |
| 宽度以屏为限 | ✅ 宽图收缩到屏宽,内容完整可读 |
| 双击放大(锚点=点击处) | ✅ |
| 拖动平移 / 双指缩放 | ✅(沿用鸿蒙同款页内手势逻辑) |
| 「关闭」按钮退出 | ✅ |
| 返回键优先退全屏 | ✅ 不退出应用,且保留正文滚动位置 |
| 点击完成时延 | ✅ 先占位帧、下一帧才挂 Web |
六、变更文件
| 文件 | 说明 |
|---|---|
app/src/main/assets/graph.html | 高度确定性计算、viewBox 归一与内容对齐、主动上报、全屏 CSS 改 fixed 定位、computeLimits/clampPan 修正 |
app/src/main/java/.../ui/GraphView.kt | 新增 fullscreen 参数;console 截获 `MDH |
app/src/main/java/.../ui/GraphFullScreen.kt | 新增:全屏查看层(对应鸿蒙 FullscreenViewer) |
app/src/main/java/.../ui/ReaderScreen.kt | 接上 onGraphClick、外层 Box + 全屏 overlay、透传 insetBottom |
app/src/main/java/.../MainActivity.kt | 向 ReaderScreen 传 insetBottom(全屏层与源码态底部避让) |
零权限、零网络、零收集的定位未被破坏:图形仍由包内 mermaid.min.js 离线渲染,
全屏层也不引入任何网络请求。
七、遗留与下一步
- 保留两条诊断日志(
MDH|内联高度、MDFS|全屏几何)便于后续回归,发布 release 前可评估移除。 - 待办:保存语音 / MP3 导出(面板按钮已占位,路线 = 逐句
synthesizeToFile→ WAV 拼接 →
LAME NDK 编 MP3)→ 菜单「设置」项 → Release 签名打包与上架。
- 待老板批示:本轮
graph.html的 CSS/高度修正是否同步回鸿蒙版?
鸿蒙当前图形渲染正常,但内联高度算法与全屏 height:100% 这两处对鸿蒙同样是隐患
(鸿蒙侧 html/body 也曾设 height:100%)。