多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

LVGL 表格控件 lv_table 全面解析:从单元格设置、滚动交互到源码级原理

LVGL 表格控件 lv_table 全面解析:从单元格设置、滚动交互到源码级原理 智能硬件嵌入式硬件开发【免费下载链接】X-TRACKA GPS bicycle speedometer that supports offline maps and track recording项目地址https://gitcode.com/gh_mirrors/xt/X-TRACK点击查看免费下载LVGLLight and Versatile Graphics Library的lv_table是一个专为嵌入式 GUI 设计的高效表格控件它只存储文本、不创建任何真实子对象单元格全部在绘制时即时渲染因此内存占用极轻非常适合在资源受限的嵌入式设备上展示结构化数据。本文以 X-TRACK离线地图 GPS 自行车码表仓库中内置的 LVGL 源码与文档为研究对象系统讲解lv_table的样式划分、单元格读写、行列管理、合并、滚动、事件与按键交互并结合 lv_table.h 与 lv_table.c 给出源码级原理支撑以及 lv_example_table_1.c / lv_example_table_2.c 两个可直接复用的实战示例。读完本文你将能够独立完成一个可滚动、可合并、可按键选中单元格的 LVGL 表格。一、控件概述为什么说 Table 是最轻量的表格lv_table的定位很明确表格由行、列、存放文本的单元格构成控件本身极其轻量因为只有文本被存储单元格不会创建任何真实对象而是在绘制时按需画出来。这意味着你在创建一张 200 行的表格时LVGL 并不会实例化 200 个 label 对象而是维护一张紧凑的文本数据表绘制阶段统一渲染。从源码结构看这一点得到了充分印证。lv_table_t的数据结构lv_table.h非常简单typedef struct { lv_obj_t obj; /* 继承自 Base Object */ uint16_t col_cnt; /* 列数 */ uint16_t row_cnt; /* 行数 */ char ** cell_data; /* 单元格文本指针数组每格头部 1 字节存控制位 */ lv_coord_t * row_h; /* 每行高度 */ lv_coord_t * col_w; /* 每列宽度 */ uint16_t col_act; /* 当前激活列 */ uint16_t row_act; /* 当前激活行 */ } lv_table_t;整个控件的数据全部由这几个字段承载没有子对象树。此外lv_table_class中声明了editable LV_OBJ_CLASS_EDITABLE_TRUE和group_def LV_OBJ_CLASS_GROUP_DEF_TRUE见 lv_table.c说明 Table 是可编辑对象且默认加入控件组group——这正是它能通过编码器encoder方向键逐格选中单元格的前提。启用条件lv_table依赖 Label 控件且受LV_USE_TABLE宏开关控制若LV_USE_LABEL 0编译会直接报错lv_table: lv_label is required. Enable it in lv_conf.h (LV_USE_LABEL 1)见 lv_table.h默认配置模板中LV_USE_TABLE为 1见 lv_conf_template.h本仓库 Simulator 的 lv_conf.h 同样开启了该功能Kconfig 菜单中也提供了对应配置项见 lvgl/Kconfig。二、Parts 与样式Part 机制下的表格美化lv_table只有两个样式部分PartLV_PART_MAIN表格的背景支持全部典型背景样式属性背景色、边框、圆角、内边距等LV_PART_ITEMS单元格区域同样支持背景样式属性外加文本样式属性字体、文字颜色、对齐方式、行间距等。默认主题default theme专门为单元格准备了table_cell样式见 lv_theme_default.c其核心是给每个单元格绘制 1px 的上下边框LV_BORDER_SIDE_TOP | LV_BORDER_SIDE_BOTTOM从而形成典型的表格分隔线外观。一个实用的样式技巧表格默认会为被按下的单元格显示按下状态如果你希望自己接管选中态的绘制例如下面示例 2 中要自定义开关图形可以移除该状态样式lv_obj_remove_style(table, NULL, LV_PART_ITEMS | LV_STATE_PRESSED);三、单元格操作写入、格式化与读取设置单元格文本单元格只能存储文本因此数值必须先转换为字符串再写入void lv_table_set_cell_value(lv_obj_t * obj, uint16_t row, uint16_t col, const char * txt);要点对应文档说明文本会被 Table拷贝保存因此可以传入局部变量函数返回后无需担心指针失效文本支持换行符例如Value\n60.3会在单元格内渲染成两行行列会自动扩展如果指定的row/col超过了当前行列数Table 会自动增加行列。从实现看lv_table.clv_table_set_cell_value内部首先做自动扩展if(col table-col_cnt) lv_table_set_col_cnt(obj, col 1); if(row table-row_cnt) lv_table_set_row_cnt(obj, row 1);随后用lv_mem_realloc重新分配该格内存并拷贝文本最后调用refr_cell_size触发该格尺寸刷新。值得注意的实现细节每个单元格内存块的第 0 字节保存控制位control byte真正文本从第 1 字节开始读取时通过table-cell_data[cell][1]跳过格式字节见 lv_table.c。printf 风格格式化写入LVGL 还提供了printf风格的便捷接口尤其适合批量生成带编号的文本void lv_table_set_cell_value_fmt(lv_obj_t * obj, uint16_t row, uint16_t col, const char * fmt, ...);其实现先通过lv_vsnprintf(NULL, 0, fmt, ap)计算格式化后的长度再按需 realloc 并写入见 lv_table.c。注意该实现还处理了阿拉伯/波斯语文本整形LV_USE_ARABIC_PERSIAN_CHARS的兼容路径。示例用法lv_table_set_cell_value_fmt(table, i, 0, Item %LV_PRIu32, i 1);其中LV_PRIu32是 LVGL 提供的跨平台 printf 格式宏避免在 32/64 位环境下%u/%lu混用导致的格式警告。读取单元格const char * lv_table_get_cell_value(lv_obj_t * obj, uint16_t row, uint16_t col);如果row/col越界或单元格为空返回空字符串而非空指针见 lv_table.c调用方可安全使用。四、行列管理行数、列数与列宽显式设置行列数使用void lv_table_set_row_cnt(lv_obj_t * obj, uint16_t row_cnt); void lv_table_set_col_cnt(lv_obj_t * obj, uint16_t col_cnt);对应的读取接口为lv_table_get_row_cnt/lv_table_get_col_cnt。从源码看改变行数时会 reallocrow_h与cell_data数组缩小表格时释放多余的单元格内存扩大时清零新增区域见 lv_table.c改变列数时由于列主序存储布局变化需要整体搬迁cell_data并重新映射新增列默认宽度取LV_DPI_DEF每英寸点数换算的默认像素值见 lv_table.c。设置列宽使用void lv_table_set_col_width(lv_obj_t * obj, uint16_t col_id, lv_coord_t w);表格整体宽度会自动等于所有列宽之和表格高度则由单元格样式字体、内边距等与行数自动计算。实际项目中推荐先设行列数、再逐格填充先lv_table_set_row_cntlv_table_set_col_cnt可以一次性分配好整个数据数组避免set_cell_value时反复自动扩展引发大量 realloc示例 2 的注释就明确写着 Not required but avoids a lot of memory reallocation。五、单元格合并水平合并单元格lv_table支持水平方向的单元格合并void lv_table_add_cell_ctrl(lv_obj_t * obj, uint16_t row, uint16_t col, lv_table_cell_ctrl_t ctrl); // 合并标志 // lv_table_add_cell_ctrl(table, row, col, LV_TABLE_CELL_CTRL_MERGE_RIGHT);要合并多个相邻单元格需要对每个目标单元格分别调用一次该函数。对应清除合并标志的接口是lv_table_clear_cell_ctrl查询状态用lv_table_has_cell_ctrl。控制位定义完整列表见 lv_table.h控制位值用途LV_TABLE_CELL_CTRL_MERGE_RIGHT1 0与右侧单元格合并LV_TABLE_CELL_CTRL_TEXT_CROP1 1裁剪超宽文本LV_TABLE_CELL_CTRL_CUSTOM_11 4用户自定义位 1LV_TABLE_CELL_CTRL_CUSTOM_21 5用户自定义位 2LV_TABLE_CELL_CTRL_CUSTOM_31 6用户自定义位 3LV_TABLE_CELL_CTRL_CUSTOM_41 7用户自定义位 4CUSTOM_1CUSTOM_4四个自定义控制位完全交由应用层使用lv_table_has_cell_ctrl可查询是否全部置位。源码中控制位就存放在每个单元格内存块的第 0 字节格式字节这也是每个格子在 realloc 文本前先备份ctrl的原因见 lv_table.c。六、尺寸与滚动LV_SIZE_CONTENT 与可滚动表格lv_table默认宽度与高度均为LV_SIZE_CONTENT见类定义width_def/height_deflv_table.c因此若宽或高设为LV_SIZE_CONTENTTable 会按内容展开显示全部行列例如lv_obj_set_size(table, LV_SIZE_CONTENT, LV_SIZE_CONTENT);会自动调整表格尺寸以容纳所有行列若宽或高小于内容固有尺寸Table 会变成可滚动LVGL 所有对象都带滚动机制。实战中最常见的是固定高度 内容宽度的组合示例 1 正是这样构造可滚动表格的lv_obj_t * table lv_table_create(lv_scr_act()); /* 填充单元格…… */ lv_obj_set_height(table, 200); /* 高度 200px超过部分可滚动 */ lv_obj_center(table);七、事件系统绘制回调与值变更LV_EVENT_VALUE_CHANGED当通过按键编码器选中一个新单元格时Table 发送LV_EVENT_VALUE_CHANGED。典型用途是配合lv_table_get_selected_cell实现选中即操作例如示例 2 的开关列表选中某行后切换该行CUSTOM_1控制位从而翻转开关状态static void change_event_cb(lv_event_t * e) { lv_obj_t * obj lv_event_get_target(e); uint16_t row, col; lv_table_get_selected_cell(obj, row, col); bool chk lv_table_has_cell_ctrl(obj, row, 0, LV_TABLE_CELL_CTRL_CUSTOM_1); if(chk) lv_table_clear_cell_ctrl(obj, row, 0, LV_TABLE_CELL_CTRL_CUSTOM_1); else lv_table_add_cell_ctrl(obj, row, 0, LV_TABLE_CELL_CTRL_CUSTOM_1); }LV_EVENT_DRAW_PART_BEGIN / DRAW_PART_END这两个绘制事件对单元格逐个触发事件类型为LV_TABLE_DRAW_PART_CELL事件参数lv_obj_draw_part_dsc_t中与表格相关的字段partLV_PART_ITEMSdraw_area单元格绘制区域rect_dsc/label_dsc矩形与文本绘制描述符可在此修改背景色、对齐方式等id单元格线性索引等于当前行 × 总列数 当前列。示例 1 用该回调实现了三种典型美化if(dsc-part LV_PART_ITEMS) { uint32_t row dsc-id / lv_table_get_col_cnt(obj); uint32_t col dsc-id - row * lv_table_get_col_cnt(obj); if(row 0) { /* 表头居中、浅蓝背景 */ dsc-label_dsc-align LV_TEXT_ALIGN_CENTER; dsc-rect_dsc-bg_color lv_color_mix(lv_palette_main(LV_PALETTE_BLUE), dsc-rect_dsc-bg_color, LV_OPA_20); dsc-rect_dsc-bg_opa LV_OPA_COVER; } else if(col 0) { /* 第一列右对齐 */ dsc-label_dsc-align LV_TEXT_ALIGN_RIGHT; } if((row ! 0 row % 2) 0) { /* 偶数行灰色条纹斑马纹 */ dsc-rect_dsc-bg_color lv_color_mix(lv_palette_main(LV_PALETTE_GREY), dsc-rect_dsc-bg_color, LV_OPA_10); dsc-rect_dsc-bg_opa LV_OPA_COVER; } }由此可以看出表格样式整列/整行统一这种需求不必逐格设置样式在绘制回调里按行号/列号分支处理即可这也是 Table 控件推荐的美化模式。若在LV_EVENT_DRAW_PART_END中回调则可像示例 2 那样用dsc-draw_ctx在单元格上额外绘制自定义图形开关滑块。另外LV_EVENT_DRAW_PART_BEGIN/END之外Table 作为 Base Object 的子类还继承 Base Object 的全部事件事件机制的通用说明可参考 事件总览。八、按键与编码器交互方向键选格lv_table是一个可编辑对象且默认加入 group因此通过方向键/编码器即可逐格移动选区LV_KEY_RIGHT / LEFT / UP / DOWN移动选中单元格LV_KEY_ENTER按惯例被翻译为LV_EVENT_PRESSED / PRESSING / RELEASED等事件Table 不单独处理 Enter 键。查询当前选中的单元格uint16_t row, col; lv_table_get_selected_cell(table, row, col); // 无选中时 row/col 会被置为 LV_TABLE_CELL_NONE (0xFFFF)LV_TABLE_CELL_NONE定义为0XFFFF见 lv_table.h用于表示没有单元格被选中。按键机制的通用说明可参考 输入设备总览。九、实战示例从示例代码到完整表格示例 1带斑马纹与滚动的水果价格表完整代码见 lv_example_table_1.c核心步骤lv_table_create(lv_scr_act())创建表格逐格写入两列数据第 0 列为 Name第 1 列为 Price8 行数据lv_obj_set_height(table, 200)制造垂直滚动注册LV_EVENT_DRAW_PART_BEGIN回调实现表头居中高亮、首列右对齐、偶数行灰底。这是查看型表格的通用写法适合数据浏览、日志列表等场景。示例 2用 Table 实现超轻量列表含开关完整代码见 lv_example_table_2.c它演示了 Table 作为列表容器的经典用法预分配 200 行 × 1 列lv_table_set_row_cnt(table, ITEM_CNT)lv_table_set_col_cnt(table, 1)避免自动扩展带来的反复 realloc用lv_table_set_cell_value_fmt循环生成Item N文本移除单元格按下态样式改由LV_EVENT_DRAW_PART_END在每行右侧绘制一个圆形开关滑块开关状态由CUSTOM_1控制位记忆在LV_EVENT_VALUE_CHANGED中翻转控制位实现选中即切换。该示例运行结束后会在屏幕上报告200 项仅用数毫秒创建、占用极少内存直观展示了lv_table只存文本、按需绘制的轻量特性。这种表格当列表用的模式非常适合嵌入式菜单、设置项列表等交互界面。十、API 速查表功能函数说明创建lv_table_create(parent)创建 Table 对象写单元格lv_table_set_cell_value(table, row, col, txt)文本拷贝保存行列自动扩展格式化写lv_table_set_cell_value_fmt(table, row, col, fmt, ...)printf 风格读单元格lv_table_get_cell_value(table, row, col)空/越界返回行数lv_table_set_row_cnt/lv_table_get_row_cnt显式设定行数列数lv_table_set_col_cnt/lv_table_get_col_cnt显式设定列数列宽lv_table_set_col_width/lv_table_get_col_width整体宽度 各列宽之和合并lv_table_add_cell_ctrl(table, r, c, LV_TABLE_CELL_CTRL_MERGE_RIGHT)水平合并控制位lv_table_add_cell_ctrl/lv_table_clear_cell_ctrl/lv_table_has_cell_ctrl合并、裁剪、自定义位选中格lv_table_get_selected_cell(table, row, col)无选中时为LV_TABLE_CELL_NONE滚动lv_obj_set_size(table, LV_SIZE_CONTENT, 高度/宽度)小于固有尺寸即可滚动十一、在 X-TRACK 工程中使用 Table 的注意事项X-TRACK 是一个基于 LVGL 的嵌入式 GPS 码表项目Simulator 与 MDK-ARM 各平台固件共用同一套 LVGL 源码树Simulator/LVGL.Simulator/lvgl。在资源敏感的嵌入式目标上使用lv_table时建议确认目标工程的lv_conf.h中LV_USE_TABLE已开启否则头文件中的#if LV_USE_TABLE ! 0会让整个控件被编译排除大数据量表格先设行列数再填充减少自动扩展期间的动态内存 realloc 碎片文本内容采用存指针/复用字符串策略善用lv_table_set_cell_value_fmt避免手写snprintf转换数值用绘制回调而非逐格建样式对象的方式实现斑马纹、表头高亮等视觉效果保持控件无子对象的轻量特性。综上lv_table以轻量存储 即时绘制的设计在嵌入式 GUI 中兼顾了表现力与性能配合其按键选格、绘制回调与自定义控制位机制足以覆盖数据展示、设置列表、日志浏览等绝大多数表格场景。赞分享智能硬件嵌入式硬件开发【免费下载链接】X-TRACKA GPS bicycle speedometer that supports offline maps and track recording项目地址https://gitcode.com/gh_mirrors/xt/X-TRACK点击查看免费下载相关推荐Vant Cell 单元格组件完全指南从基础用法到源码级原理解析Vant Cell 单元格组件完全指南从基础用法到源码级原理解析 Cell 单元格是 Vant 移动端 UI 库中最基础也最高频的列表展示组件常用于信息前端UI组件Vant Weapp SwipeCell 滑动单元格组件完全指南从基础用法到源码级原理解析Vant Weapp SwipeCell 滑动单元格组件完全指南从基础用法到源码级原理解析 导读 SwipeCell滑动单元格是 Vant Weapp 小前端小程序UI组件移动开发ThingsBoard 表格组件单元格内容函数Cell content function完全指南从配置到源码级实现ThingsBoard 表格组件单元格内容函数Cell content function完全指南从配置到源码级实现 导读 单元格内容函数Cell con物联网后端数据可视化消息队列上一篇如何永久备份微信聊天记录本地免费工具WeChatMsg终极指南下一篇AutoSubs终极指南3步实现本地AI字幕生成告别繁琐手动编辑创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表