Files
STM32F4-Base/docs/UI设计/UI_界面制作流程.md
T
wandering1 4a4ad6bc5f chore(全仓): @build/@test 旧文档别名清退为技能调用名
现状口径:/keil-build 与 /serial-boot-test 是 DSH 会话的技能调用名,
不是 shell 命令;「名 + 命令行参数」的命令形态改为可执行体
run_boot_test.py 的真实调用。

- AGENTS.md:别名说明重写为「技能调用名不是 shell 命令」,两条目改
  「编译(/keil-build)」「串口回归(/serial-boot-test)」
- keil-build / serial-boot-test 两 skill 调用名与示例同步;task-worktree
  方案文档去掉 @build/@test 惯例从句;@task 保留(不在点名范围)
- 123 文件现状陈述全改;台架捕获、变更记录/时间线、rename 映射输入
  等 41 行历史证据保留旧名
- gen_memory_svg.py 6 处措辞随改,3 张 SVG 产物同批重跑
- 顺手补并行方案同行漏网计数 51→52 钩子

验证:提交期快钩子对 123 文件实跑全 Passed;check_doc_links --all
rc=0(3038 处、无法判定 0);check_mermaid --all rc=0;YAML 解析 52
钩子不变;skill-size rc=0;py_compile 全过;字节卫生零漂移。
2026-10-30 13:03:12 +08:00

28 KiB
Raw Blame History

UI 界面制作流程(参考图 → LVGL)

用途:规范"参考图 → 设计规格 → LVGL 代码 → 双端验证"的完整管线,规定每一步的动作、产物、放行条件(gate),保证过程可重复、可交接。 适用:所有 UI 新界面 / 界面大改;小改动走「小改动快速通道」一节(见文末第十节)。 配套文档:UI_设计架构.md(系统如何工作)、菜单页面设计.md(菜单/导航行为与数据流设计)、LVGL模拟器部署与使用说明.md(模拟器)、UI_中文字体生成与配置.md(字体生成)、UI_自动化测试流程.md(P2/P3 回归门禁的可执行版本:脚本 + 池水位常量 + 黄金参考更新)。 首个实例:界面 01 主界面(根页),规格 mockups/01_主界面_说明.md(本文所有示例数字均取自该试点)。

沿革:旧管线含"HTML 样机"阶段(参考图 → HTML 样机 → 代码),2026-09-13 起移除——设计基准改为文本规格(布局参数表)+ 参考图,视觉基准改为模拟器出图,用户视觉确认点移到 P2 模拟器窗口/出图(沿革细节查 git log,本节只写现状)。


一、流程总览

四阶段单向管线:参考图 → P0 → P1 → P2 → P3 → P4。每阶段产出明确产物、满足**放行条件(Gate)**才进入下一阶段;其中 P2 是"比对 → 修正"迭代环,未收敛不得前进。

flowchart TD
    R(["参考图"]) --> P0["P0 需求录入 + 设计规格<br/>→ 屏幕台账 + NN_说明.md(布局参数表/字符集/问题)"]
    P0 -- "Gate: 界面作用/菜单位置/极性已确认 + 阻塞性问题有答案 + 布局验证等式成立 + 字符集核对" --> P1["P1 规格 → LVGL 代码<br/>→ Drivers/BSP/UI 代码 + 字体(重生成)"]
    P1 -- "Gate: 参数逐项对账 + 字体重生成 + 内存预算 + 双端注册" --> P2["P2 模拟器验证<br/>→ shot.png(对照参考图 + 规格参数表)"]
    P2 --> C2{"输出图 ↔ 参考图<br/>同构?"}
    C2 -- "否: 按根因修正(修代码 / 回 P0 修规格)→ 再出图" --> P2
    C2 -- "是(Gate: 导航符合按键语义 + 自动化回归 PASS)" --> P3["P3 MCU 验证<br/>→ 日志 + 上机目检结论"]
    P3 -- "Gate: /keil-build 0 错 0 警 + /serial-boot-test PASS + 上机目检通过" --> P4(["P4 归档<br/>→ 受影响章节现状化/台账 + 随代码提交(代码+字体+字集+规格+文档)"])

小改动(文案/间距/纯交互/字体重生成)走「小改动快速通道」一节(见文末第十节),无需全流程。

1.1 各阶段职责分工

阶段 用户做 AI 做 产物
P0 需求录入 + 设计规格 提供图片(每批 1~3 屏);说明各屏作用与菜单位置;回答问题;确认布局参数表 图片分析、界面登记、字体度量核对、字符集核对、布局计算(验证等式)、规格制作 台账 + _说明.md(参数表/字符集/问题/映射/翻译要点)
P1 规格→代码 (可选)抽查代码 逐参数翻译、字体重生成、内存预算、双端注册 代码 + 字体
P2 模拟器 视觉确认(窗口模式/出图),指出差异 构建、离线出图、出图与原图逐项比对、依规格改码 比对结论
P3 MCU 真机目检(DTU-RemoteLCD) /keil-build / /serial-boot-test 执行、日志解析 日志 + 结论
P4 归档 — 把受影响章节改成改动后的现状 + 更新台账 文档

用户视觉确认点由旧"浏览器打开 HTML 样机"改为 P2 模拟器窗口/出图:P0 确认参数表(数值层面),P2 确认渲染结果(视觉层面);渲染结果与意图不符时按 §5.1 根因修正(代码错 → 修代码;参数表错 → 回 P0 修规格再修代码)。


二、设计约束(硬规则)

管线中一切设计必须满足以下约束;违反即需重做,不存在"真机上再凑合"的选项。

# 约束 值 来源
C1 屏幕 160×160,1bpp 单色(LV_COLOR_FORMAT_I1),无物理屏(远端客户端显示) ui_port.h
C2 字体 唯一:ui_font_cn_12(宋体 12px 1bpp)。汉字 12px/字,ASCII 6px/字,行高 13px。需要其他字号 = 大工程(新字体 + 双端 lv_conf.h),默认禁止 ui_font_cn_12.c
C3 字符集 子集字体(当前 224 汉字/符号 + ASCII 0x20-0x7E,共 319 字形);缺字显示空白;新字必须重生成字体 tools/font_gen/
C4 LVGL 内存池 16KB(LV_MEM_SIZE,2026-10-15 由 40KB 收缩;实测峰值 6,568 B = 池的 40%);任一时刻只放"当前导航路径 + 当前内容页" Lib/lv_conf.h
C5 文本 一律静态字符串(lv_label_set_text_static),禁止动态字符串/strdup 架构文档
C6 编码 Drivers/BSP/UI/*、ui_task.c、Lib/lv_conf.h 为 UTF-8 带 BOM;改动只用 Edit 工具,禁止 PowerShell/cmd 整文件重写(U+FFFD 不可逆) AGENTS.md 铁律
C7 代码共用 ui_menu.c / 字体为 MCU 与模拟器共用源,禁止在 simulator/ 复制副本 架构文档
C8 视觉禁区(1bpp) 圆角 / 阴影 / 透明度 / 渐变 / 多色 / 抗锯齿 全部禁用;设计与代码都不得引入 物理限制
C9 双端注册 新增 Drivers/BSP/UI/*.c 必须两处手动注册:Keil .uvprojx + simulator/CMakeLists.txt 的 UI_SOURCES 列表(均为显式列表,无通配扫描)。只注册一端 = 另一端静默缺文件 实测(2026-09-03)

2.1 字体度量速查(2026-09-03 实测,字体变更时重测)

项 原始值 = 设备像素 说明
汉字步进宽 adv_w = 192(1/16 px) 12 px/字 4 字标签 = 48px
ASCII 步进宽 adv_w = 96 6 px/字 "OK" = 12px,"V1.0" = 24px
汉字墨迹盒 box_w 911,box_h 1112 墨迹在盒内再内缩 1~2px 字体自带侧边距,贴边文字仍有视觉留白
行高 line_height = 13 13px 布局按 13 算,不是 12
基线 base_line = 2(自底部) — 垂直定位参考

设备字体的源字体为 SimSun(上述度量的来源);字符集由仓库根 ui_cn_chars.txt 管理。


三、P0:需求录入与设计规格

3.1 用户输入

  1. 参考图(建议每批 1~3 屏,不必一次给全)
  2. 每图一句话:是什么界面、干什么用、挂在菜单哪个位置
  3. 对 AI 提出的问题的回答(能答则答,答不了的标"待定")

3.2 AI 动作:图片分析(检查单)

分析项 内容
总体结构 页头 / 主体(网格/列表/数值/曲线/表单)/ 页脚 / 状态栏
元素类型 网格、列表、数值、曲线、开关、进度条、图标(1bpp 小屏图标基本不可行 → 文本替代)
文本内容 逐字抄录(字符集核对的输入;漏一个字 = 真机一处空白)
比例换算 参考图比例 → 160×160 设备像素(参考屏分辨率通常大于 160,做比例适配而非像素照搬)
风格 极性(白底黑字/黑底白字)、边框样式、选中样式
信息缺失 截图裁切、遮挡、模糊处 → 转成待确认问题

3.3 界面台账

  • 编号 NN(01 = 试点界面),名称、作用、菜单位置、状态(待做/进行中/已完成)
  • 台账随 P4 归档更新

3.4 字体度量核对(字体每次变更时重做)

  1. 读 Drivers/BSP/UI/ui_font_cn_12.c 头部 Opts 注释 → 字体源(宋体)、尺寸(12)、bpp(1)、当前字符集
  2. 读 glyph_dsc[] 表 → adv_w ÷ 16 = px(核对 CJK=12 / ASCII=6)
  3. 读 font_dsc → line_height=13、base_line=2
  4. 与 §2.1 速查不一致 → 全部规格参数重算

3.5 字符集核对

  1. 收集本屏全部文本中的去重汉字
  2. 对照现有字符集(字体 .c 头部 --symbols 行为准;ui_cn_chars.txt 为生成物,重生成后一致)
  3. 输出:命中列表(现有)/ 新增列表(标红)→ 记入配套 _说明.md
  4. 新增列表决定 P1 字体重生成的范围

试点实例:01 主界面 7 个菜单项共 22 个唯一汉字 → 命中 21(装置信息实时数据参数定值遥设维护通讯记录查询),新增 1(三);状态行"当前模式:无模式"新增 5(当前模式无);箭头 ▶(U+25B6)为新增符号。合计确定新增 7 个字形(若状态行用全角冒号":"则再多 1,共 8)。

3.6 布局计算

  • 所有值为设备像素整数
  • 列宽:cell_w = floor((W − 2×边距 − 间隙×(n−1)) / n),1px 余量给最后一列
  • 行高:cell_h = floor((H − 2×边距 − 间隙×(m−1)) / m)
  • 必须给出验证等式(每项相加 = 屏幕尺寸)
  • 文字宽度检查:字数 × 12px(步进宽)vs 格内宽(格宽 − 2px 边框)。步进宽超出但墨迹宽(约 −1px/端)放得下即可;放不下 → 减字数 / 减列数 / 回 3.1 问用户

试点实例(7 行列表): 160 = 顶边距2 + 顶装饰5 + 隙4 + 状态行13 + 隙5 + 列表框128 + 底边距3 列表框 144×128(1px 边框,x=8):内容高 7×18 + 2(上下边框) = 128,7 行 × 18px 行文字 4字 = 步进 48px,行内宽 144(左留白 4px)→ 富余,左对齐成立

推论:行通栏 144px,文字左对齐(左留白 4px)+ 字体自带侧边距 = 视觉留白;行尾 ▶ 右对齐(右留白 4px)。选中行整行反色(白底黑字)。

3.7 设计规格 NN_界面名_说明.md

文件:docs/UI设计/mockups/NN_界面名_说明.md(mockups/ 目录仅存"原始参考图 + 设计规格";骨架见附录 A)

规格(顺序固定):

  1. 布局参数表 —— 每个 px 值都列出(P1 翻译的依据,必须与验证等式自洽)+ 验证等式
  2. 行/元素内容与状态
  3. 字符集核对结果 —— 命中 / 新增
  4. 待确认问题 —— 沿用 3.2 的 Q 编号
  5. 映射建议(菜单类界面:条目 → 子树)
  6. LVGL 翻译要点 —— 本屏具体的翻译提示(控件选择、状态、内存注意点)

规格是唯一设计基准:P1 照参数表翻译代码;P2 出图与原图不符时,先查代码是否忠实实现参数表——代码错 → 修代码;参数表本身偏离原图/用户意图 → 回本阶段修规格,再修代码。 规格定稿(文件头标 已定稿 (日期))后,改动须用户明确提出。

3.8 Gate(进入 P1)

  • 界面作用与菜单位置已确认
  • 阻塞性问题已回答;非阻塞项已标注默认值(如 "TBD")
  • 布局计算完成,验证等式成立
  • 字符集核对完成(新增字清单确定)
  • 用户已确认规格(参数表/内容/状态)

试点实例:01 主界面。用户给一张根页截图:顶部梯形装饰 + 状态行"当前模式:无模式" + 7 行列表(装置信息/实时数据/参数定值/三遥设置/装置维护/通讯参数/记录查询),黑底白字(与现系统同极性)。AI 分析出:7 项与现有 g_menu_tab 的映射,其中 3 项疑似重命名(参数定值/三遥设置/通讯参数)→ Q1;"三遥设置"对应现有"遥测量"还是新增"三遥"节点 → Q2;旧菜单"厂家设置/版本信息"未列入 7 项,入口在哪 → Q3;"当前模式"指什么、数据从哪来 → Q4。


四、P1:规格 → LVGL 代码

4.1 翻译对照表(规格参数 → LVGL v9)

规格 / 设计描述 LVGL(设备端) 备注
160×160 白底画布 display(I1)+ 屏幕背景色 极性随全局决策(Q2 类问题)
元素位置 x, y lv_obj_set_pos(o, x, y) 整数 px,绝对坐标
元素尺寸 w×h lv_obj_set_size(o, w, h)
横向/纵向排布 lv_obj_set_flex_flow(o, LV_FLEX_FLOW_ROW[_WRAP])
间距 gap lv_obj_set_style_pad_gap(o, g, 0)
1px 边框 lv_style_set_border_width(st,1) + border_color
仅底边框 lv_style_set_border_side(st, LV_BORDER_SIDE_BOTTOM, 1) v9 分边设置
黑/白背景 lv_style_set_bg_color + lv_style_set_bg_opa 1bpp 仅纯黑/纯白
文本 lv_label_create + lv_label_set_text_static 静态字符串(C5)
文字对齐 标签定位 / lv_label_set_long_mode 默认左对齐(见 §3.6 推论)
选中反色 LV_STATE_CHECKED 样式(bg/文字色互换) 条目对象自身带 CHECKED(防白底白字,UI_设计架构.md §5.5)
隐藏状态 LV_OBJ_FLAG_HIDDEN 页面/状态切换
圆角/阴影/透明/渐变 禁止(C8) 规格里就不该有

4.2 文件策略与双端注册

  • 首选原地扩展 ui_menu.c(共用源,改一处两端生效)
  • 确需新文件(模块独立,如曲线页渲染器)时,两处注册(C9):
    1. Keil:MDK-ARM/*.uvprojx 手动添加
    2. 模拟器:simulator/CMakeLists.txt 的 UI_SOURCES 列表加一行
  • 新页面类型(内容页/编辑页)扩展现有"容器持久、条目动态"架构,禁止另起第二套架构
  • 字体文件 ui_font_cn_12.c 两端均已注册 → 重生成是纯内容替换,无需注册

4.3 翻译纪律

  1. 逐参数翻译:代码中每个 px 值必须能在规格"布局参数表"中找到出处;禁止"看着办"
  2. 样式沿用现有 s_style_flat 模式(静态样式、一次性初始化、后加优先覆盖主题)
  3. 焦点对象恒为页面容器;条目不入输入组(UI_设计架构.md §5.4)
  4. 容器永不删除(indev 释放事件 use-after-free,UI_设计架构.md §5.2);只建/毁框体(行自绘、无对象)
  5. 编码纪律(C6):Edit 工具精确替换;文件保持 UTF-8 BOM 不变
  6. 注释按代码规范 V1.0:文件头模块块 + 函数头块(功能/参数/返回/限定/说明)

4.4 字体重生成(固定顺序,不得跳过或调换)

1. 代码落库(新界面字符串已写入 ui_menu.c 等 UI 源码)
2. python tools/font_gen/extract_cn.py    # 扫描 UI 源码字符串 → 仓库根 ui_cn_chars.txt(自动含新字;纯标准库,任意 python3)
3. & '.venv\Scripts\python.exe' tools/font_gen/make_font.py   # 宋体 12px 1bpp + ASCII + 字符集 → ui_font_cn_12.c(全量重生成 + 自动补回宋体缺失的 ▶ 箭头,幂等;需 fontTools)
4. 生成物 diff 检查:新增字在位、旧字不变、Opts 行字符集与预期一致
  • 一次性环境准备:npm i -g lv_font_conv;fonttools/pillow 统一装进仓库 .venv(.venv\Scripts\python.exe -m pip install -r requirements.txt,见 AGENTS.md「Python 环境」)。PATH 默认 python 是 msys64 的、无第三方库,勿用它跑 make_font.py
  • ui_cn_chars.txt(仓库根,受 git 跟踪)与 ui_font_cn_12.c 随代码一起提交
  • 双端 LV_FONT_DEFAULT 均指向 &ui_font_cn_12 → 重生成后两端自动生效
  • P0 已知的新增字若含生僻字(宋体可能无此字形)→ 在写码前先手动验证字形,避免"代码写完才发现是空白"
    • 已处理实例:▶(U+25B6)箭头——宋体无此字形,lv_font_conv 静默跳过 → 真机方框。make_font.py 已内置 merge_arrow_glyph() 自动从 Segoe UI Symbol 补回(幂等),重生成字体后箭头不丢。换用自带 ▶ 的字体时可忽略

4.5 内存预算

  • 估算:对象数 × 单价(lv_obj ≈ 150B、lv_label ≈ 200B 量级,以实测为准)
  • 实测点:建页前后打 lv_mem_monitor 池水位(沿用现有 menu build start/done 日志模式)
  • 红线:最坏路径(根页 + 最深下钻 + 当前内容页)< 40KB 且留 ≥25% 余量
  • 超预算 → 回到 P0 减元素 / 改按需建毁策略;不得靠加大 LV_MEM_SIZE 硬扛(RAM 总预算紧张)

4.6 Gate(进入 P2)

  • 代码与规格参数逐项一致(自查对照表)
  • 字体已重生成且 diff 检查通过
  • 内存预算通过(估算 + 代码中埋好水位日志)
  • 新文件(如有)双端注册完成

五、P2:模拟器验证

5.1 动作

  1. cd simulator; cmake --build build --config Debug(改过 CMakeLists.txt 才需重新 cmake -B)
  2. 离线出图(模拟器页面渲染完成后):
    • .\build\Debug\lvgl_sim.exe --screenshot shot.bin → 离屏渲染首帧,输出 I1(1bpp) 位图
    • python i1_to_png.py shot.bin -o shot.png --scale 4 → 160×160 PNG(放大便于比对)
  3. 读两图比对:模拟器 shot.png vs 参考图(mockups/NN_..._原始图片.png),按 §5.2 检查单逐项核对(参考图比例可能不同:几何以规格参数表的设备 px 为准,不照参考图像素)
  4. 不符 → 根因修正(本阶段核心迭代环):
    • 代码与规格参数表不一致(位置/尺寸/颜色错)→ 修代码
    • 代码忠实于参数表、但参数表本身偏离参考图/用户意图 → 回 P0 修规格,再修代码
    • 重构建 → 重新出图 → 再比对,循环至收敛
  5. 交互行为(导航/滚动/选中)在窗口模式 .\build\Debug\lvgl_sim.exe 内按键验证(离线出图只覆盖首帧静态画面)
  6. 自动化回归(任何 UI 改动必跑,脚本已入库无需重写):仓库根目录执行 python simulator/tests/check_menu_rows.py + python simulator/tests/check_content_pages.py, 全部门禁与常量说明见 UI_自动化测试流程.md

循环目的:消除"AI 读图不准"的风险——参数表是 AI 对参考图的理解,出图与原图比对才能暴露读图错误(此时是参数表问题而非代码问题,回 P0 修)。

5.2 比对检查单

项 通过标准
几何 位置/尺寸/边框与规格布局参数表逐格一致(容差 1px);整体结构/比例与原图一致
文本 内容完整、无空白块(空白 = 缺字 → 回 §4.4)、宽度/位置正确
状态 选中反色正确(极性正确:黑底白字↔白底黑字不反)
导航 ↑↓ 移动(环绕)、→/Enter 进入、←/Esc 返回;滚动条行为正常
日志 menu build done: used=… 在预算内;无 ERROR/FAIL

5.3 已知不阻塞差异(不修)

  • 参考图为高分辨率截图/手工图,字形与线宽与设备 1bpp 渲染不完全一致(形状近似即可,几何以参数表为准)
  • 系统 DPI 缩放导致的窗口显示大小差异

5.4 Gate(进入 P3)

  • 模拟器出图 shot.png 与规格参数表逐项一致、与参考图结构一致(§5.2,忽略 5.3)
  • 导航行为符合键位语义表(UI_设计架构.md §3.4)
  • 自动化回归 PASS:check_menu_rows.py + check_content_pages.py(UI_自动化测试流程.md)

六、P3:MCU 验证

  1. /keil-build → 0 错误 0 警告(AGENTS.md 强制;有警告看 MDK-ARM/build_log.txt)
  2. /serial-boot-test(一键 .dsh/skills/serial-boot-test/scripts/run_boot_test.py):
    • 采集窗口先于烧录武装(脚本 --flash-cmd 已保证顺序)
    • Required:UI port init: OK;UI menu init: OK + 本界面定制标记(按屏增补,如页头标题字样)
    • Forbidden:FAIL;ERROR;HardFault
  3. 真机目检(用户执行,DTU-RemoteLCD @ TCP 7003):极性 / 边框 / 文字 / 选中态 / 滚动
  4. 串口日志:池水位、任务栈高水位(uxTaskGetStackHighWaterMark,uiTask 预算 16384 words)

Gate(进入 P4)

  • /keil-build 0错0警
  • /serial-boot-test PASS
  • 真机目检通过(用户确认)

七、P4:归档

  1. 更新 docs/UI设计/UI_设计架构.md:只改受影响章节(文件地图 / 关键参数 / 菜单架构 / 键位语义),把它们写成改动后的现状;不再记流水(2026-10-28 裁定,docs/文档规范.md §5.1)
  2. 设计规格 NN_界面名_说明.md 保留于 mockups/(定稿版 = 设计档案,后续改界面须先改规格再改代码)
  3. 台账:该界面状态 → 已完成(日期)
  4. 提交检查:代码 + 字体(ui_font_cn_12.c)+ ui_cn_chars.txt + 规格 + 文档 一起进 git

八、批量生产策略

  1. 试点先行:01 主界面走完全流程,校准各阶段耗时、翻译损耗点、内存基线(试点已完成 P1,后续阶段数据回填本文档)
  2. 按界面族批量:
    • 列表页(复用现有级联框自绘架构)→ 数据显示页(数值行框架)→ 参数编辑页(编辑/确认交互)→ 曲线/状态页
    • 每族先做族内第一屏产出可复用的页面框架,再批量铺同族界面
  3. 每批收尾:字体统一重生成(各屏字符集并集,一次生成)→ 全量回归(已完成界面重跑 /serial-boot-test)
  4. 字符集只增不减;字体文件每次全量重生成,禁止手工拼合

8.1 规格投入分流(按屏型)

两条正交轴:设计规格(布局参数表)只解决"长什么样"(静态外观);"怎么动 / 数据从哪来"(行为 / 导航 / 数据驱动)靠设计文档(菜单页面设计.md)+ LVGL 模拟器(动态 oracle)。两轴互补——规格是"设计先行"的确定性工具,不是代码生成器,别指望它加速行为代码。

按屏型决定 P0 规格投入(收益从高到低):

屏型 例 P0 规格投入 行为 / 数据靠什么定
静态、元素多的内容页 数据显示 / 参数定值 / 装置信息 完整参数表逐项(收益最大:几何钉死→抄写 + 缺字/1bpp 排雷前移 + P2 客观视觉基准) 数据绑定 + 少量交互 → 设计文档
导航 / 交互重的屏 菜单(横向级联框)/ 参数编辑 参数表只做静态首帧样子;交互重头放设计文档 设计文档定状态机/级联框/键位语义 + 模拟器验证行为
  • 规格的真实价值(确定但偏窄,集中在 P0/P2):① 几何从"推导"降为"抄写" ② 缺字 / 1bpp 禁区在 P0 排雷(比 P3 真机发现便宜得多) ③ P2 提供客观几何基准 ④ 用户可在参数表层面廉价确认设计。
  • 规格帮不上(本工程 C 的复杂度大头,与规格无关):LVGL 管线(离屏渲染 / 输入组 / 焦点 / 容器持久)、导航状态机、数据驱动(g_menu_tab 一表驱动)、运行时内存水位。这些靠 LVGL 功底 + UI_设计架构.md + 模拟器。

九、常见坑速查

坑 现象 对策
PowerShell 重写含中文文件 整文件 U+FFFD 乱码(不可逆) 只许 Edit 工具(C6)
���了字体子集外的字 该字显示空白 P0 字符集核对 + P1 字体重生成
字体缺字形(如宋体无 ▶) 该字符显示成方框 □ make_font.py 已内置 merge_arrow_glyph() 自动补回;其余缺字形同法(从有该字的字体提取位图合并,§4.4)
新 .c 只注册了 Keil(或只改了 CMake) 单端编译缺函数/静默缺失 双端注册(C9)
模拟器主循环调 SDL_PollEvent 键盘全部无响应 事件只由 LVGL SDL 驱动内部轮询(UI_设计架构.md §5.7)
选中行"白底白字" 选中文字不可见 历史坑(2026-09-02):子 label 自身 card 样式总命中、继承不到父选中色;现行行自绘(绘制时给色、无样式继承)已根除(UI_设计架构.md §5.5)
按键处理里删除焦点容器 use-after-free 崩溃 容器持久不删(UI_设计架构.md §5.2)
LVGL 池爆 16KB(2026-10-15 起) 对象创建失败/画面残缺 §4.5 预算 + 按需建毁
极性配反 黑屏/白屏/不可读 模拟器 mono 主题 dark_mode 参数 + MCU 端背景色一致
步进宽接近/超出格宽的标签居中 亚像素错位/不对称 左对齐 + 字体侧边距(§3.6)
关闭了 LV_DRAW_SW_COMPLEX(省代码) lv_line 斜线不绘制(如主界面顶部"浅 V"两腿消失,只剩水平线) LV_DRAW_SW_COMPLEX 两端显式 =1;LV_DRAW_SW_CIRCLE_CACHE_SIZE 须非零(默认 4,否则零长数组 C2466)(UI_设计架构.md §关键约束 7)
斜线用细 line_width(如 2px) 1bpp 阈值吃掉斜线边缘,腿偏细、比设计稿细 斜线加粗(腿 3px),或按段拆 lv_line 分粗细(如"浅 V"两腿 3 / 水平线 2)
规格引入翻译不出来的效果 P1 翻译不出来 1bpp 禁区(C8),P0 阶段就拦下
UI 源文件(UTF-8 BOM)里 snprintf 格式串含中文 模拟器 cl.exe 报 C4819(代码页 936 误读 UTF-8 字节) 格式化输出用 lv_snprintf(LVGL 自带,双端一致),勿用标准库 snprintf
常驻样式用动态 lv_style_t + set_prop LVGL 池永久占用:首次 set_prop 分配 values_and_props 且永不释放(x64 实测 88 B/个) 常驻样式一律常量样式:LV_STYLE_CONST_INIT(var, props[]) + lv_style_const_prop_t[](属性存 Flash、零池分配,见UI_设计架构.md §5.6)

十、小改动快速通道(简化流程)

改动类型 流程
文案修改 改规格 → 改代码 → 字符集核对(有新字则重生成字体)→ P2 比对
间距/尺寸调整 改规格参数表 → 改代码 → P2/P3 合并为一轮验证
纯交互调整(键位/滚动) 改规格/设计文档 → 改代码 → P2 验证行为
字体重生成 任何引入新字的改动后强制执行 §4.4(最常见的漏项)

附录 A:设计规格模板骨架(NN_界面名_说明.md)

# 界面 NN:<名称>(设计规格)

> 状态:制作中 / 已定稿 (YYYY-MM-DD)
> 参考图:mockups/NN_<名称>原始图片.png
> 本文件为设计基准:P1 照参数表翻译代码;P2 出图对照参数表 + 原图

## 一、布局参数表
| 元素 | x | y | w | h | 边框 | 内容/状态 |
|---|---|---|---|---|---|---|
| 页头 | … | … | … | … | … | … |

验证等式:`160 = 顶边距2 + … + 底边距3`(各项相加 = 屏幕尺寸)

## 二、行/元素内容与状态
…

## 三、字符集核对
- 命中(__):…
- 新增(__):…

## 四、待确认问题
- Q1:…

## 五、映射建议(菜单类界面)
…

## 六、LVGL 翻译要点
…

附录 B:单界面生产检查单(复制使用)

界面 NN:<名称>                负责人:____   开始:____

P0  [ ] 台账登记(编号/作用/菜单位置)
    [ ] 图片分析(结构/元素/文本逐字/比例/缺失)
    [ ] 字体度量核对(与 §2.1 一致)
    [ ] 字符集核对:__ 命中 / __ 新增
    [ ] 布局计算(验证等式:____________)
    [ ] NN_名称_说明.md 规格产出(参数表/内容/字符集/问题/映射/LVGL 要点)
    [ ] 用户确认规格(日期:____)
P1  [ ] 代码翻译(与规格参数逐项一致)
    [ ] 字体重生成(extract → make_font → diff 检查)
    [ ] 新文件双端注册(如适用,C9)
    [ ] 内存预算(实测水位:____KB / 40KB)
P2  [ ] 模拟器构建 0 错误
    [ ] 离线出图:--screenshot shot.bin → i1_to_png.py → shot.png
    [ ] shot.png 与参考图/规格参数表逐项比对(几何/文本/状态);不符按根因(修码/修规格)再出图(§5.1)
    [ ] 导航行为验证(窗口模式按键)
    [ ] 自动化回归:check_menu_rows.py + check_content_pages.py PASS
P3  [ ] /keil-build 0 错 0 警
    [ ] /serial-boot-test PASS(Required:____ / Forbidden:FAIL;ERROR;HardFault)
    [ ] 真机目检通过
P4  [ ] UI_设计架构.md 受影响章节已改成现状(不记流水)
    [ ] 台账状态 → 已完成(代码+字体+字符集+规格+文档 一并提交)