From 4d9ba46a7b48099555ff74fa8c29130abeef1ecd Mon Sep 17 00:00:00 2001 From: AskaEth Date: Thu, 5 Nov 2026 20:20:53 +0800 Subject: [PATCH] =?UTF-8?q?fix(sdk):=20listview=20=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E8=A1=8C=E9=AB=98=E6=8C=89=E5=AD=97=E4=BD=93=E8=A1=8C=E9=AB=98?= =?UTF-8?q?=E6=B4=BE=E7=94=9F=20=E2=80=94=20=E5=8F=8C=E8=A1=8C=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E9=A6=96=E8=A1=8C=E6=A0=87=E9=A2=98=E4=B8=8D=E5=86=8D?= =?UTF-8?q?=E8=A2=AB=E8=A3=81=E5=89=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因(M12 finding / SDK 侧):meow_listview 双行 renderer 把「标题+副标题」 文本块 block = 2*meow_ui_text_height(font_body) + MEOW_LISTV_SUB_GAP(2) 在行内 垂直居中(ty = 行顶 + (item-block)/2),且文本只在 `ty >= 视口顶` 时才画。 旧默认行高固定 56 假定行高 ≤ 27;真机 source_han_serif 20px(ascent 24 + descent 6)行高 30 → block 62 > 56 → ty = 行顶 - 3,首行(行顶 == 视口顶) 标题被裁剪守卫整条跳过、副标题仍在 → 所有用默认行高的 App 两行列表首行 标题消失。 修复(patch_v2/sdk/meow_ui_widget.c): - item_h=0 改为「派生」哨兵:listv_item_h() 在未显式定行高时按当前主题正文 字体行高算 —— lines=2: max(56, 2*line_h+2)(行高 30 → 62)、lines=1: max(48, line_h)。draw/hit/max_scroll 共用同一函数,行几何恒一致; - meow_listview_create 不再写死 56;set_item_renderer 不再写死 48/56; set_item_tier 恢复派生默认(仍是 1→≥48 / 2→≥56 的口径,字体大时自动长高); - 显式 meow_listview_set_item_h(>0) 语义不变、优先且固定(ncm 传 70、files 传 56 等调用方不受影响);set_item_h(<=0) 仍忽略,返回值改为「当前生效行高」; - 新增 meow_listview_item_h() 读当前生效行高(App 自绘行装饰对齐用,避免再 硬编码 56); - src2 分支的双行判断补 `w->lines == 2`,与 pool 分支同口径:tier/renderer 设 单行档位时明确不画副标题(此前单行档位 48px 里仍塞 62px 文本块 → 同样丢标题)。 同类路径评估(不改): - LazyColumn/LazyRow(W_LIST):单行 label,item_h 由 create 显式给(默认 44), 且 item_h 兼作横向列宽,按行高派生意味不明;默认 44 > 真机行高 30,当前不触发; - lazygrid:格子几何由 App 显式给、文本底部对齐并整体 clip_push 裁剪,不受影响。 影响面:所有用默认行高的两行列表(hello/reader/files 及其它 App)在 20px 字库 下默认行高 56 → 62;显式 set_item_h 的调用方(ncm 70/launcher/files 等)零变化。 meow_ui.a 变更 → 下游 App 需重链(追加导出符号 meow_listview_item_h,ABI 兼容, 老 App 不重编也能跑)。 验证:见下一条 test 提交(宿主 --wrap 行高 30 + 像素断言 35 断言)。 --- patch_v2/sdk/include/meow_ui_widgets.h | 12 +++++-- patch_v2/sdk/meow_ui_widget.c | 45 ++++++++++++++++++++------ 2 files changed, 44 insertions(+), 13 deletions(-) diff --git a/patch_v2/sdk/include/meow_ui_widgets.h b/patch_v2/sdk/include/meow_ui_widgets.h index f6088fc..af94af4 100644 --- a/patch_v2/sdk/include/meow_ui_widgets.h +++ b/patch_v2/sdk/include/meow_ui_widgets.h @@ -316,11 +316,16 @@ typedef struct meow_list_item { /* 行数据源回调:idx 有效时填充 out 并返回 1;越界返回 0。每行轻量填充(不做 IO,§5.5)。 */ typedef int (*meow_list2_source)(int idx, meow_list_item *out, void *ctx); -/* 创建:视口(x,y,w,h) + 行数 count + 行数据源 + 选中模式。行高默认 56(可用 set_item_h 改)。 */ +/* 创建:视口(x,y,w,h) + 行数 count + 行数据源 + 选中模式。 + * 行高默认按当前主题字体行高派生(双行 max(56, 2*行高+2) / 单行 max(48, 行高)), + * 20px 字库(行高 30)下双行=62;显式 set_item_h 可覆盖(App 自管卡高时用)。 */ meow_widget *meow_listview_create(int x, int y, int w, int h, int count, meow_list2_source src, void *ctx, meow_list_sel mode); -/* 行高(固定;越界/<=0 忽略)。 */ +/* 行高:显式 px(>0)优先并固定;越界/<=0 忽略(保持字体派生)。返回当前生效行高。 */ int meow_listview_set_item_h(meow_widget *lv, int item); +/* 当前生效行高(显式值,或未显式时按主题字体行高派生的值)。无效控件返回 0。 + * 行几何(draw/hit/max_scroll)全部按此值,App 自绘行装饰用它对齐,勿再硬编码 56。 */ +int meow_listview_item_h(meow_widget *lv); /* 行数(搜索过滤/懒加载/目录切换时动态更新):截断 sel 到新范围 + clamp scroll。 */ void meow_listview_set_count(meow_widget *lv, int count); /* 滚动:set 越界 clamp 到 [0, max_scroll];by 相对滚动(App on_swipe/touch_move 用)。 */ @@ -394,7 +399,8 @@ int meow_listview_set_vsource(meow_widget *lv, meow_listview_count_fn count, * show_icon=1 画左图标/缩略图;show_right=1 画右尾件。返回 0=成功。 */ int meow_listview_set_item_renderer(meow_widget *lv, int lines, int show_icon, int show_right); -/* 行档位默认行高:lines=1→48(单行)、2→56(双行);与 set_item_h(精确 px)互补。 */ +/* 行档位(默认行高口径):lines=1→max(48, 行高)、2→max(56, 2*行高+2),由当前字体行高派生 + * (与 set_item_h 互补:tier 恢复派生默认,set_item_h 设精确 px)。 */ int meow_listview_set_item_tier(meow_widget *lv, int lines); /* 完全自绘:设 draw_item 回调(覆盖默认 renderer);NULL=用默认 renderer。返回 0=成功。 */ int meow_listview_set_item_draw(meow_widget *lv, meow_listview_draw_fn draw, diff --git a/patch_v2/sdk/meow_ui_widget.c b/patch_v2/sdk/meow_ui_widget.c index 2a2bb5b..aca1cf3 100644 --- a/patch_v2/sdk/meow_ui_widget.c +++ b/patch_v2/sdk/meow_ui_widget.c @@ -79,7 +79,7 @@ struct meow_widget { int maxlen; /* W_TEXTFIELD 上限(含 NUL) */ meow_ime_mode ime_mode; /* W_TEXTFIELD IME 模式 */ int value; /* W_PROGRESS:0-100 / -1=不定; W_BADGE:计数 */ - int item_h; /* W_LIST:行高/列宽(lazy),默认 44 */ + int item_h; /* W_LIST:行高/列宽(lazy),默认 44;W_LISTVIEW:显式行高,0=按字体行高派生 */ int scroll; /* W_LIST:滚动偏移(lazy) */ int horizontal; /* W_LIST:1=横向(LazyRow) */ int back_on; /* W_TOPAPPBAR:显示返回箭头 */ @@ -1051,7 +1051,24 @@ int meow_list_set_container(meow_widget *w, int on) * 宿主/设备需图片图标的 App 经 meow_ui_img_load_* 强引用 meow_ui_img.o → 此处自动解析。 */ extern void meow_ui_img_scaled(int x, int y, int w, int h, meow_img *img) __attribute__((weak)); -static int listv_item_h(const meow_widget *w) { return (w->item_h > 0) ? w->item_h : 56; } +/* 行高:显式 meow_listview_set_item_h 优先(App 自管行几何时按自己算出的值传,如 ncm 70); + * 未显式(item_h<=0)→ 按当前主题正文字体行高派生,保证「文本块 ≤ 行高」: + * lines=2:max(56, 2*line_h + MEOW_LISTV_SUB_GAP) (20px 字库行高 30 → 62) + * lines=1:max(48, line_h) + * 旧默认固定 56 在真机 source_han_serif 20px(ascent 24 + descent 6 = 30)下小于双行 + * 文本块 62 → 居中后 ty = 行顶 - 3,首行(行顶==视口顶)标题被 `ty >= vy0` 裁剪守卫整条 + * 跳过,只剩副标题(M12 finding / M15 本源修复)。派生值由主题字库实时算出,draw/hit/ + * max_scroll 三路共用同一函数 → 行几何恒一致;改字库档位/主题后自动跟随。 */ +static int listv_item_h(const meow_widget *w) +{ + if (w->item_h > 0) return w->item_h; + const meow_theme *th = meow_theme_get(); + int line_h = th ? meow_ui_text_height(th->font_body) : 0; + if (w->lines == 1) + return (line_h > 48) ? line_h : 48; + int h = 2 * line_h + MEOW_LISTV_SUB_GAP; + return (h > 56) ? h : 56; +} /* §5.5 虚拟化辅助:当前行数(pool 模式实时查 count_fn;src2 模式用缓存的 w->count)。 */ static int listv_count(const meow_widget *w) @@ -1203,7 +1220,7 @@ meow_widget *meow_listview_create(int x, int y, int w, int h, int count, t->owner = meow_ui_activity_active(); t->state = MEOW_WS_ENABLED; t->count = count > 0 ? count : 0; t->src2 = src; t->ctx = ctx; - t->sel = -1; t->item_h = 56; t->scroll = 0; t->swipe_idx = -1; + t->sel = -1; t->item_h = 0; t->scroll = 0; t->swipe_idx = -1; /* item_h=0 → 按字体行高派生(见 listv_item_h) */ t->sel_mode = (mode >= MEOW_LIST_SEL_NONE && mode <= MEOW_LIST_SEL_MULTI) ? mode : MEOW_LIST_SEL_NONE; t->lines = 2; t->show_icon = 1; t->show_right = 1; /* 默认双行富 item + 图标 + 右尾件(src2 布局不变) */ t->container = 1; /* M6.37 P1:SDK 默认列表走 MD3 容器模式——行=surfaceContainer 圆角卡 + 空隙 + 去分隔线 + 卡内 tonal 选中, @@ -1225,7 +1242,12 @@ int meow_listview_set_item_h(meow_widget *w, int item) listv_ensure_pool(w); /* 行高变小 → 可见行增多 → 池容量随之调整 */ meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); } - return w->item_h; + return listv_item_h(w); /* 返回当前生效行高(显式值或字体派生值) */ +} +int meow_listview_item_h(meow_widget *w) +{ + if (!w || w->type != W_LISTVIEW) return 0; + return listv_item_h(w); } void meow_listview_set_count(meow_widget *w, int count) { @@ -1398,7 +1420,7 @@ int meow_listview_set_item_renderer(meow_widget *w, int lines, int show_icon, if (lines < 1) lines = 1; if (lines > 2) lines = 2; w->lines = lines; w->show_icon = show_icon ? 1 : 0; w->show_right = show_right ? 1 : 0; - if (w->item_h <= 0) w->item_h = (lines == 1) ? 48 : 56; /* 未显式定行高 → 档位默认 */ + /* 未显式定行高(item_h<=0)→ 保持派生模式(按 lines 档位从字体行高算),不写死像素 */ listv_ensure_pool(w); /* 行档位可能变化 → 确保池容量 */ meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); return 0; @@ -1410,7 +1432,7 @@ int meow_listview_set_item_tier(meow_widget *w, int lines) if (lines < 1) lines = 1; if (lines > 2) lines = 2; w->lines = lines; - w->item_h = (lines == 1) ? 48 : 56; /* 档位默认高(可随后 set_item_h 覆盖) */ + w->item_h = 0; /* 档位=字体派生默认(单行 max(48,行高) / 双行 max(56,2*行高+2));set_item_h 可覆盖 */ listv_ensure_pool(w); meow_gfx_mark_dirty(w->x, w->y, w->w, w->h); return 0; @@ -2007,8 +2029,9 @@ void meow_widget_draw(meow_widget *w) } } } else { - /* ① src2 模式(既有):每帧按需取行(meow_list2_source),行为不变。 - * 行 = 图标(左) + 标题 + 副标题(次要色) + 右侧操作区;选中行 = primaryContainer。 */ + /* ① src2 模式(既有):每帧按需取行(meow_list2_source),虚拟化行为不变。 + * 行 = 图标(左) + 标题 + 副标题(次要色) + 右侧操作区;选中行 = primaryContainer。 + * lines=1(set_item_renderer/tier 单行档位)时不画副标题,与 pool 分支同口径。 */ for (int i = first; i < last; i++) { meow_list_item it; memset(&it, 0, sizeof it); @@ -2055,9 +2078,11 @@ void meow_widget_draw(meow_widget *w) if (icy + isz >= vy0 && icy < vy1) /* 图标不越视口边界才画(防泄漏) */ listv_draw_icon(th, rlx + pad, icy, it.icon_kind, it.icon, iccol); - /* 标题 + 副标题(两行居中;无副标题=单行居中;越界不画防泄漏) */ + /* 标题 + 副标题(lines=2 有副标题→两行居中;lines=1 或无副标题→单行居中;越界不画防泄漏)。 + * lines 档位与 pool 分支同口径:tier/renderer 设单行时明确不画副标题, + * 否则 48px 单行档位里塞 62px 文本块 → 首行标题被裁剪守卫跳过。 */ int ty; - if (it.subtitle && it.subtitle[0]) { + if (w->lines == 2 && it.subtitle && it.subtitle[0]) { 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, text_maxw, th->font_body, textcol);