fix(ncm): 列表首行标题不显示 — 行高按字体行高派生(M12)
根因(真机 seated,非数据/颜色/遮挡):SDK meow_listview 双行渲染把 「2*行高 + 2」的文本块在行内垂直居中,且标题只在 ty >= 视口顶时才画 (meow_ui_widget.c:2061 的越界守卫)。真机 source_han_serif 20px 面 ascent 24 + descent 6 → 行高 30 → 文本块 62px,而 ncm 行高 APP_ROW_H=56 → ty = 行顶 - 3;首行行顶 == 视口顶 → ty < 视口顶 → 标题被整条跳过, 副标题(ty+32)仍在视口内照画。两类列表同用该路径 + 同一行高,故症状一致。 修复(不动 SDK):ncm_app_internal.h 新增 ncm_row_h()/ncm_row_h_for_line_h(), 行高 = max(56, 2*字体行高 + 2 + 8)(偶数;本机 30 → 70,卡高 62 == 文本块); home/playlist/queue/cache/search 在 page create 取一次 g_item_h,统一用于 set_item_h 与卡片几何/可见行/页脚/补载视口换算。 回归: - app/test/test_meow_row_text_fit.c(新):真机字库行高矩阵 14/21/26/27/30 48 断言(文本块 ≤ 行高、首行 ty ≥ 0、卡高 ≥ 文本块、旧 56 哨兵)。 - app/test/test_first_row_title_render.c(新):--wrap 行高=30 驱动真实 SDK 渲染 + 读回 PPM 像素计数:旧 56 → 首行标题 0 像素/副标题 186;新 70 → 标题 84/副标题 100。 - test_meow_row_gap、plpage_sim 全 PASS;ncm_host 7 页出图与改动前逐字节相同 (宿主行高 14 → 行高仍 56,证重构无视觉回归);5 页自检 PASS;0 warning。 - plpage_sim 临时目录改 $TMPDIR(Termux /tmp 不可写,原写死无法运行)。 - REPORT §43:根因/排除方向/修复/验证/真机验收要点。
This commit is contained in:
1 parent
98b9169b86
commit
22f9dab2ce
11 files changed
+440
-34
No files matched your search
@@ -3726,3 +3726,100 @@ if (now - last_meta >= 1000) { send_meta(fd, ...); last_meta = now; }
|
||||
(旧:5-7s);launcher 日志 `ipc: META app=dev.player.ncm` 空闲时应约每 30s 一条(旧:~10/s);
|
||||
播放中约每 5s 一条、暂停/切歌各多一条;同时观察 ncm CPU 与首屏/列表滑动是否恢复。
|
||||
- **未部署/未 push**:设备只读访问,未改设备;本节所有验证均在宿主完成。
|
||||
|
||||
## 43. M12:列表首行主标题不显示(两行列表行高 < 文本块,SDK 视口顶裁剪把首行标题整条跳过)(2026-11-05)
|
||||
|
||||
### 43.1 现象(用户原话)
|
||||
|
||||
> 「网易云有一个问题 歌单列表的第1个歌单名字不显示 副标题(歌曲数目)有显示 。点开歌单 加载歌曲列表
|
||||
> 第1个歌曲名字不显示 副标题(歌手名)有显示。」
|
||||
|
||||
特征:两类列表(首页歌单列表 / 歌单详情曲目列表)都只有**首行主标题完全空白**、副标题正常,
|
||||
其余行标题/副标题都正常。设备二进制 431ca4dd(含 M5/M6/M10/M11)。
|
||||
|
||||
### 43.2 根因(代码路径 + 触发机制,已坐实)
|
||||
|
||||
SDK `meow_listview` 的双行渲染(`patch_v2/sdk/meow_ui_widget.c:2061`,src2 分支):
|
||||
|
||||
```c
|
||||
ty = ry + (item - (2 * thh + MEOW_LISTV_SUB_GAP)) / 2;
|
||||
if (it.title && ty >= vy0 && ty + thh <= vy1)
|
||||
listv_text_trunc(rtx, ty, it.title, ...); /* 标题:视口顶裁剪守卫 */
|
||||
int sy = ty + thh + MEOW_LISTV_SUB_GAP;
|
||||
if (sy >= vy0 && sy + thh <= vy1)
|
||||
listv_text_trunc(rtx, sy, it.subtitle, ...); /* 副标题 */
|
||||
```
|
||||
|
||||
- `thh = meow_ui_text_height(theme->font_body)`。真机 ncm 进程(pid 6693)环境:
|
||||
`MEOW_MULTI_FONT=1`、主库 `MEOW_FONT_APP=source_han_serif`;从设备
|
||||
`/usr/data/mnt/sd_0/MeowOS/fonts/source_han_serif/regular/font.idx` 的 MFNI 面头直接读出
|
||||
20px 面 `ascent=24 / descent=6` → `meow_font_line_height(20) = 30`
|
||||
(多字库取最大:google_sans 类库 20px 面 = 26,不改变结果)→ **文本块 = 2*30 + 2 = 62**。
|
||||
- ncm 行高 `APP_ROW_H = 56`(与 SDK `item_h` 默认 56 相同)→ `ty = ry + (56-62)/2 = ry - 3`。
|
||||
- 首行(scroll=0)`ry == vy0`(行顶 == 控件视口顶)→ `ty = vy0 - 3 < vy0` → **标题被整条跳过**
|
||||
(不是画了被裁,是守卫直接不画);`sy = vy0 + 29` 在视口内 → 副标题照画。
|
||||
- 其余行 `ty = ry - 3 > vy0` → 标题仍画(比本行顶高 3px,文字整体仍在视口内,肉眼难察觉)。
|
||||
- 两类列表同用 `meow_listview_create(..., src2, ...)` + 双行 + `APP_ROW_H`,故症状完全一致。
|
||||
|
||||
排除的其它方向:
|
||||
|
||||
| 怀疑方向 | 结论 |
|
||||
|---|---|
|
||||
| ① title 指针悬垂/空串(绑定生命周期) | **排除**:`home_src2`/`pl_src2` 每帧刷新静态缓冲,副标题取自同一缓冲且可见 |
|
||||
| ② 首行被顶部栏/裁剪区遮住(布局偏移) | **排除**:组头红条在 `r_list` 上方 8px;行卡片 z=5、文本 z=10、叠层 z=60 均不覆盖首行标题位 |
|
||||
| ③ 首行字体色 = 背景色(选中/主题色) | **排除**:同一次绘制里 title=`onSurface`、subtitle=`onSurfaceVariant`;且用户确认「完全空白」非同色 |
|
||||
| ④ 首项特殊路径(我喜欢的音乐/当前播放) | **排除**:歌单详情首行是任意曲目,无特殊渲染分支 |
|
||||
|
||||
### 43.3 修复(app 侧,不动 SDK / 不动 meow_ui.a)
|
||||
|
||||
行高改为**按当前主题字体实测行高派生**,保证文本块装得下(卡高 == 文本块):
|
||||
|
||||
| 文件 | 改动 |
|
||||
|---|---|
|
||||
| `app/ncm_app_internal.h` | 新增 `NCM_ROW_SUB_GAP`(与 SDK 同口径 2)、`ncm_row_h_for_line_h(line_h)`(= `max(APP_ROW_H, 2*行高 + 2 + NCM_ROW_GAP)`,偶数)、`ncm_row_h()`(取 `meow_ui_text_height(meow_theme_get()->font_body)`)。`APP_ROW_H` 降级为**行高下限**(注释同步)。 |
|
||||
| `app/ui/home.c` / `playlist.c` / `queue.c` / `cache.c` / `search.c` | 各页 `g_item_h = ncm_row_h()` 在 page create 取一次(字体档位切换走重建页面,避免同一页内行高漂移),`meow_listview_set_item_h` 与全部卡片几何/可见行推算/页脚「x-y / n 首」/补载视口换算统一用 `g_item_h`。 |
|
||||
| `app/test/test_meow_row_text_fit.c`(新) | 纯头内联几何回归(不链 SDK):真机已装字库 20px 面实测行高矩阵 14/21/26/27/30 → 断言文本块 ≤ 行高、首行 ty ≥ 0、副标题不越行、卡高 ≥ 文本块、行高偶数且 ≥ 下限;并用旧 56 + 行高 30 断言 `ty<0`(bug 形态哨兵,防再写死)。 |
|
||||
| `app/test/test_first_row_title_render.c`(新) | **渲染级回归**:宿主 5x7 回退行高是 14,照原样渲染复现不了。用 `-Wl,--wrap=meow_ui_text_height` 把行高替换成真机实测 30,驱动**真实 SDK listview 双行渲染**,读回 `MEOW_UI_DUMP` 的 PPM 数像素:旧行高 56 → 首行标题带 0 文字像素、副标题带 186;新行高 70 → 标题带 84、副标题带 100。 |
|
||||
| `bench/plpage_sim.c` | 滚动换行高改用 `ncm_row_h()`;临时目录/出图改走 `$TMPDIR`(Termux 下 `/tmp` 不可写,原写死 `/tmp` 无法运行)。 |
|
||||
| `build_ncm_host.sh` | **补 `meow_ui_time`**:M11 新增 `sdk/meow_ui_time.c` 后本脚本未同步,main 上宿主链接报 `undefined symbol: meow_ui_time_ms`(与 M12 无关的既有断裂,不补则本 mission 无法出图验证)。 |
|
||||
|
||||
行高取值(本机默认字体 source_han_serif,行高 30):`2*30 + 2 + 8 = 70` → 卡高 62 == 文本块 62,
|
||||
`ty = +4`(首行标题正常落卡内)。其它字库:行高 27 → 64;26 → 62;21 → 52 但受下限钳到 56(与旧视觉一致)。
|
||||
|
||||
### 43.4 验证(宿主,0 真机写、0 真实 NCM 请求)
|
||||
|
||||
| 项 | 结果 |
|
||||
|---|---|
|
||||
| `app/test/test_meow_row_text_fit`(新) | **PASS:48 checks / 0 fail**(行高矩阵 5 档;含「旧 56 + 行高 30 → ty=-3」哨兵) |
|
||||
| `app/test/test_first_row_title_render`(新,真实 SDK 渲染 + PPM 像素计数) | **PASS**:legacy `item=56 ty=-3 → title_ink=0 sub_ink=186`(复现用户症状);fixed `item=70 ty=4 → title_ink=84 sub_ink=100`(首行标题正常) |
|
||||
| `app/test/test_meow_row_gap`(既有卡片几何) | **PASS**(row_gap=8 card_h=48 top2top=56 symmetric=1) |
|
||||
| `bench/plpage_sim`(真实 playlist.c 全链路:懒加载分页/触底/回滚补物化) | **RESULT: PASS**(全用例 PASS) |
|
||||
| `ncm_host` 出图(home/playlist/cache/search/searchpl/plsplay/queuetest) | 全部 rc=0;与**改动前基线逐字节相同**(宿主行高 14 → `ncm_row_h()` 仍为 56,几何零变化 → 证明重构本身无视觉回归) |
|
||||
| `ncm_host` 页面自检(queuetest/modetest/barhit/searchtap/cachetest) | 全 PASS(covswitch 因测试自身写死 `/tmp/*.bmp`、Termux 下不可写而 `init FAIL`——与 M12 无关的既有环境限制) |
|
||||
| 宿主告警 | 改动/新增 TU(5 个 ui + ncm_app_internal.h + 2 个 test + 1 个 sim)`-Wall -Wextra` **0 warning**;`build_ncm_host.sh` 全量链接通过 |
|
||||
| 真机只读取证 | 读 `/proc/6693/environ`(字体档/多字体开关)+ 设备 `font.idx` 面头(ascent/descent);**未改设备** |
|
||||
| mipsel / 真机 | **未跑**(本机无 zig 交叉链;交叉编译 + 热更由塔台做) |
|
||||
|
||||
### 43.5 诚实出入 / 遗留
|
||||
|
||||
- **修复形态是「行高变高」**:本机默认字库下行高 56 → 70(每屏可见行数略减)。这是必然代价——旧行高
|
||||
本来就装不下 62px 的两行文本(文字一直溢出卡外 3~7px),只是首行标题被守卫整条吞掉才暴露。
|
||||
若用户切到行高 21 的字库(source_han_sans 等),行高回到 56,视觉与旧版一致。
|
||||
- **滚动时顶部半露行仍会隐藏标题**:SDK 的语义是「标题不完全在视口内就不画」(`ty >= vy0`),
|
||||
这是滚动裁剪的正常表现,不属于本 bug;scroll=0 的首行标题现已正常。
|
||||
- **根因在 SDK 通用渲染**:2 行档默认 `item_h=56` 假定字体行高 ≤27;任何 App 的两行列表在 20px
|
||||
字库(行高 30)下都会丢首行标题。本 mission scope 只到 `patch_v2/ncm_app/**`,故 app 侧派生行高
|
||||
规避;**SDK 侧仍应修**(2 行分支把 `ty` 钳到 `vy0` 或按文本块反推行高),已作为 finding 上报塔台。
|
||||
- **`build_ncm_host.sh` 的 M11 漏编**:本脚本补了 `meow_ui_time`(否则宿主链接失败)。同类还写死
|
||||
`/tmp` 的两处(`bench/plpage_sim.c` 已改 `$TMPDIR`;`app/test/test_ncm_app.c` covswitch 仍写死,
|
||||
未改——与本 bug 无关)。
|
||||
- **未部署/未 push**:设备只读访问,未改设备;所有验证在宿主完成。
|
||||
|
||||
### 43.6 真机验证要点(塔台热更后按此验收)
|
||||
|
||||
1. 首页「我的音乐」歌单列表:**第 1 个歌单名字可见**,副标题「N 首」仍在;第 2 行起视觉正常。
|
||||
2. 点开任一歌单(含「我喜欢的音乐」):**第 1 首歌名可见**,副标题「歌手 / 专辑」仍在。
|
||||
3. 同类页面顺带确认:播放队列(ACT_QUEUE)、已缓存列表(ACT_CACHE)、搜索结果单曲页签,首行标题都在。
|
||||
4. 预期可见变化:每行变高(本机字库下 56 → 70px)、每屏可见行数略减;卡片内两行文字完整、不再溢出卡片。
|
||||
5. 滚动列表:顶部半露行标题不可见属正常裁剪;回到顶部(scroll=0)后首行标题必须重新出现。
|
||||
6. 若系统字体切到 source_han_sans 等小行高字库:行高回落 56,视觉与旧版一致且首行标题仍正常。
|
||||
Reference in new issue
Block a user