【奶茶Beta专项】【LVGL9.4源码分析】09-core-group焦点组管理
- 1 概述
- 1.1 文档目的
- 1.2 代码版本与范围
- 2 设计意图与总体定位
- 2.1 为什么需要 `lv_group`
- 2.2 `lv_group` 在架构中的位置
- 2.3 与全局输入/焦点状态的关系
- 3 使用方式与典型 DEMO
- 3.1 创建 group 并绑定编码器输入
- 3.2 在菜单/表单中使用 group
- 4 接口分类与 API 速查表
- 4.1 group 创建与销毁
- 4.2 对象与 group 的关联管理
- 4.3 焦点对象管理与导航
- 4.4 group 行为与模式配置
- 4.5 输入设备与 group 的绑定
- 4.6 默认 group 与“自动加入 group”的组件
- 5 设计优势与缺点(含案例)
- 5.1 设计优势
- 5.2 潜在缺点与注意事项
- 5.3 简单案例:设置菜单与数值调节
- 5.4 多 group 场景下的焦点切换参考方案
- 6 与其它框架的对比与改进思路
- 6.1 与 AWTK 的对比
- 6.2 与 Qt 的对比
- 6.3 与 Android / HTML/CSS 的对比
- 6.4 可能的改进方向
- 7 小结
- 8 附录
- A 参考文档(外部)
- B 相关资源(CSDN 系列)
文档版本: 1.0
更新日期: 2025年12月
适用对象: 需要在 LVGL9.4 中实现键盘/编码器等焦点导航的工程师(嵌入式 GUI 框架维护者与应用开发者)
1 概述
1.1 文档目的
本篇聚焦library/lvgl/src/core/lv_group*,从“焦点与输入路由”角度解析 LVGL9.4 中 group 机制的设计思路与在系统中的定位,说明它如何把若干对象组合成一个“可通过键盘/编码器顺序导航”的焦点组,以及这对桌面类 UI、遥控器/旋钮交互的实际意义。
通过典型用法和常见问题,本篇希望帮助读者建立一套清晰的 group 使用心智:什么时候需要创建 group、如何为输入设备绑定 group、如何在工程中设计合理的焦点路径,并为后续性能与结构优化提供参考。
1.2 代码版本与范围
- 仓库路径:
https://github.com/lvgl/lvgl.git - 版本:v9.4.0
- 主要关注源码文件:
library/lvgl/src/core/lv_group.hlibrary/lvgl/src/core/lv_group.c
- 关联模块与调用者:
- 输入设备(特别是编码器/键盘)与
lv_indev_t; - 对象系统
lv_obj_t及其焦点状态; - 主题和样式中与“焦点态”相关的展示效果。
- 输入设备(特别是编码器/键盘)与
2 设计意图与总体定位
2.1 为什么需要lv_group
对于只使用触摸或鼠标的 UI 来说,焦点管理可以非常简单:
- 用户直接点到哪个对象,就把事件派发给它;
- 焦点/高亮状态更多是视觉上的,而不是严格的“输入路由”概念。
但在以下场景中,这种方式就远远不够:
- 使用编码器/方向键/遥控器导航 UI;
- 使用硬件按键在多个控件间顺序切换;
- 想要实现“表单式焦点跳转”、“菜单上下切换”等桌面式体验。
为此,LVGL 提供了lv_group机制,将一组对象纳入统一管理:
- 把一批对象组织成一个“焦点组”(group);
- 为某个输入设备(encoder/keyboard)绑定一个 group;
- 当输入设备上产生“左/右/上下/确认”等事件时,由 group 决定当前焦点对象是谁、下一个是谁,并更新对象的焦点状态与高亮样式。
2.2lv_group在架构中的位置
从层次上看,lv_group主要位于:
- 下接:输入设备(encoder/keyboard 等)与事件系统;
- 上连:对象系统中的“可聚焦对象”(比如按钮、文本框、列表项等);
- 旁连:主题/样式系统,用于在对象获得焦点时展示不同外观。
可以用一个简单的示意图表示:
输入设备 (encoder / keyboard) │ ▼ lv_indev_t │ (绑定 group) ▼ lv_group ┌───────────────┐ │ obj_1 obj_2 │ ← 一组可聚焦对象(按钮、输入框、列表项等) │ obj_3 obj_4 │ └───────────────┘ │ ▼ 对象系统 + 样式 (focus 高亮 / 焦点环等)因此可以理解为:
lv_group是 LVGL 中承载“基于方向键/编码器的焦点导航逻辑”的中间层,把输入事件转化为“在一组对象之间移动焦点”。
2.3 与全局输入/焦点状态的关系
lv_group并不直接管理所有输入设备,只对绑定到它的输入设备负责:
- 每个
lv_indev_t可以选择性地绑定一个lv_group_t; - 一个 group 可以被一个或多个输入设备使用(视应用需求而定);
- 对象的
LV_STATE_FOCUSED状态由 group 切换,主题可以基于该状态绘制焦点高亮。
这让 LVGL 能够支持多种配置:
- 单 group + 单 encoder:典型菜单导航;
- 多 group + 多 encoder:多区域独立导航;
- group + 触摸混合:触摸直接选中对象,同时 encoder 在 group 内顺序切换。
3 使用方式与典型 DEMO
3.1 创建 group 并绑定编码器输入
一个典型的焦点导航场景代码结构大致如下:
/* 1. 创建 group */lv_group_t*g=lv_group_create();/* 2. 创建若干可聚焦对象 */lv_obj_t*btn1=lv_button_create(lv_screen_active());lv_obj_t*btn2=lv_button_create(lv_screen_active());lv_obj_t*ta=lv_textarea_create(lv_screen_active());/* 3. 把对象加入 group */lv_group_add_obj(g,btn1);lv_group_add_obj(g,btn2);lv_group_add_obj(g,ta);/* 4. 为编码器输入设备绑定 group */lv_indev_t*enc=lv_indev_create();/* 伪代码,根据实际驱动注册方式获取 */lv_indev_set_type(enc,LV_INDEV_TYPE_ENCODER);lv_indev_set_group(enc,g);此后,旋转编码器、按下确认键等操作会由 LVGL 转换成“在 group 中移动焦点/激活当前对象”的行为:
- 左/右/上/下:在 group 内切换当前焦点对象;
- 确认:触发当前对象的点击/激活事件(例如按钮被按下、文本框获得输入焦点)。
3.2 在菜单/表单中使用 group
菜单/表单是 group 的高频应用场景:
- 把一列按钮或表单项(文本输入、开关、滑条等)按顺序加入同一个 group;
- 焦点顺序通常与视觉顺序一致(从上到下、从左到右);
- 用户通过方向键/编码器快速在这些元素中移动焦点,并使用确认键进行操作。
示意代码:
lv_group_t*menu_group=lv_group_create();lv_obj_t*item_wifi=lv_button_create(scr);lv_obj_t*item_bluetooth=lv_button_create(scr);lv_obj_t*item_about=lv_button_create(scr);lv_group_add_obj(menu_group,item_wifi);lv_group_add_obj(menu_group,item_bluetooth);lv_group_add_obj(menu_group,item_about);lv_indev_set_group(enc_indev,menu_group);配合主题中对LV_STATE_FOCUSED的样式定义,可以实现类似 TV/机顶盒菜单那样清晰的焦点高亮效果。
4 接口分类与 API 速查表
说明:具体函数/类型名以当前版本
lv_group.h为准,这里按功能维度做整理。
4.1 group 创建与销毁
| 功能 | 接口 | 说明 |
|---|---|---|
| 创建 group | lv_group_create(void) | 创建一个新的焦点组 |
| 删除 group | lv_group_delete(group) | 删除整个 group,自动把其中的对象移出 |
4.2 对象与 group 的关联管理
| 功能 | 接口 | 说明 |
|---|---|---|
| 把对象加入 group | lv_group_add_obj(group, obj) | 把一个对象加入指定 group |
| 从 group 移除对象 | lv_group_remove_obj(obj) | 把对象从其所在的 group 中删除 |
| 获取对象所属 group | lv_group_get_group(obj) | 查询对象当前属于哪个 group |
4.3 焦点对象管理与导航
| 功能 | 接口 | 说明 |
|---|---|---|
| 获取当前焦点对象 | lv_group_get_focused(group) | 返回 group 中当前具有焦点的对象 |
| 设置当前焦点对象 | lv_group_focus_obj(obj) | 让某个对象成为所在 group 的焦点对象 |
| 焦点移动到下一项 | lv_group_focus_next(group) | 在 group 内按顺序移动到下一个对象 |
| 焦点移动到上一项 | lv_group_focus_prev(group) | 在 group 内按顺序移动到上一个对象 |
4.4 group 行为与模式配置
| 功能 | 接口 | 说明 |
|---|---|---|
| 设置 wrap 模式 | lv_group_set_wrap(group, true/false) | 焦点是否在首尾间循环 |
| 设置 focus 回调 | lv_group_set_focus_cb(group, cb) | 当焦点变化时触发的回调 |
| 设置编辑模式 | lv_group_set_editing(group, true/false) | 控制 group 是否处于“编辑模式” |
| 查询编辑模式 | lv_group_get_editing(group) | 当前是否是编辑模式 |
4.5 输入设备与 group 的绑定
| 功能 | 接口 | 说明 |
|---|---|---|
| 为输入设备绑定 group | lv_indev_set_group(indev, group) | 让输入设备的焦点导航作用于指定 group |
| 获取输入设备当前 group | lv_indev_get_group(indev) | 查询某输入设备当前绑定的 group |
4.6 默认 group 与“自动加入 group”的组件
lv_group提供了“默认 group”相关的 API,用于简化键盘/编码器场景下的导航配置:
- 通过
lv_group_set_default(group)设置一个默认 group; - 通过
lv_group_get_default()在需要时获取当前默认 group 指针。
结合lv_obj_class_init_obj()的实现,可以看到 LVGL9.4 实际上已经接好了“根据类配置自动加入默认 group”的逻辑:
- 在
lv_obj_class_init_obj()末尾,框架会执行:lv_group_t * def_group = lv_group_get_default();- 如果存在默认 group,且
lv_obj_is_group_def(obj)返回 true,则调用lv_group_add_obj(def_group, obj);
lv_obj_is_group_def(obj)会沿着对象类的继承链查找lv_obj_class_t::group_def字段:- 当某个类的
group_def设置为LV_OBJ_CLASS_GROUP_DEF_TRUE时,该类实例在构造阶段就会自动加入默认 group; - 若为
LV_OBJ_CLASS_GROUP_DEF_FALSE,则不会自动加入; - 缺省值是
LV_OBJ_CLASS_GROUP_DEF_INHERIT,表示沿用基类的配置。
- 当某个类的
也就是说,默认 group 机制的真实控制开关是类描述中的group_def,而不是早期注释里提到的add_to_def_group;
当前版本的官方 widgets 是否自动进默认 group,完全取决于各自类描述里对group_def的设置。实际工程中,如果希望自定义控件在创建时自动进入默认 group,应当在自定义lv_obj_class_t里显式把group_def设为LV_OBJ_CLASS_GROUP_DEF_TRUE;
对于其余控件,则仍然可以通过显式调用lv_group_add_obj()精细控制哪些对象参与键盘/编码器导航。
5 设计优势与缺点(含案例)
5.1 设计优势
- 把“焦点导航”从业务代码中抽象出来:
- 应用只需把相关对象加入 group,具体“上一个/下一个焦点是谁”由 group 统一负责;
- 减少在业务层到处自己维护索引/指针链表的重复代码。
- 支持多种输入设备与混合交互:
- 同一个 UI 可以同时支持触摸与编码器:触摸直接点选,编码器按 group 顺序导航;
- 对于遥控器、物理按键等场景尤其友好。
- 焦点状态与样式解耦:
- group 只负责逻辑焦点,实际高亮效果由样式/主题根据
LV_STATE_FOCUSED决定; - 可以为不同主题提供不同风格的焦点框、阴影、缩放等效果。
- group 只负责逻辑焦点,实际高亮效果由样式/主题根据
5.2 潜在缺点与注意事项
- 需要额外维护 group 结构:
- 一旦对象树发生动态变化(增删节点),需要记得同步更新 group,否则可能出现“看得到的控件无法通过键盘导航到”;
- 当对象被删除时,要确保从 group 中也被正确移除。
- 多 group 场景下的焦点切换策略需要自行设计:
- 比如左侧菜单一个 group,右侧内容区另一个 group,什么时候把焦点从一个 group 切到另一个,通常由业务逻辑决定;
- 若设计不当,用户可能会感觉“焦点乱跳”或“卡在某个区域出不来”。
- 与复杂布局/自定义控件结合时需要额外心智负担:
- 某些复合控件内部可能已经使用 group 或自管理焦点,和外层 group 叠加时要特别小心,避免冲突。
5.3 简单案例:设置菜单与数值调节
以常见的“设置菜单 + 数值调节界面”为例:
- 左侧是一个菜单列表(Wi-Fi、蓝牙、声音等),右侧是对应设置项;
- 编码器旋转用于在左侧菜单 item 之间切换,按下确认键进入右侧调节;
- 在右侧调节界面中,编码器旋转用于调节滑条/数值,按下返回键切回左侧菜单。
可以用两个 group 来划分职责:
group_menu:管理左侧菜单 item 的焦点;group_detail:管理右侧调节控件的焦点;- 在进入/退出详情界面时,根据状态切换 encoding 输入设备绑定的 group。
这种分层方式能最大限度减少焦点混乱,也让逻辑更易于维护。
5.4 多 group 场景下的焦点切换参考方案
结合上面的案例,可以抽象出一套通用的“多 group 焦点切换”设计方案,供工程实践参考:
状态机建模:
- 定义 UI 处于哪个区域(如
FOCUS_ON_MENU/FOCUS_ON_DETAIL/FOCUS_ON_DIALOG等枚举); - 焦点切换不直接在 group 间跳,而是先切换状态,再根据状态绑定对应 group。
- 定义 UI 处于哪个区域(如
统一入口函数:
- 实现一个小工具函数
ui_focus_set_state(new_state):- 解除当前输入设备与旧 group 的绑定;
- 根据
new_state绑定到新的 group(例如group_menu、group_detail); - 可选:在新 group 内调用
lv_group_focus_obj()把焦点设置到合适的默认对象。
- 实现一个小工具函数
清晰的触发规则:
- 例如:
- 在菜单上按“确认”键 →
FOCUS_ON_MENU→FOCUS_ON_DETAIL; - 在详情中按“返回”键 →
FOCUS_ON_DETAIL→FOCUS_ON_MENU; - 如果弹出对话框,则暂存当前状态,临时切到
FOCUS_ON_DIALOG并绑定对话框 group。
- 在菜单上按“确认”键 →
- 例如:
这种“状态机 + 统一切换入口”的设计,让多 group 场景下的焦点行为更可预测,也更便于后期维护和扩展。
6 与其它框架的对比与改进思路
6.1 与 AWTK 的对比
- AWTK 中同样存在“焦点链表/控件树上的 focus 管理”,并支持键盘/遥控器导航;
- 相比之下:
- LVGL 使用
lv_group明确把一批对象组织成导航组,强调“显式分组”; - AWTK 更多是依赖控件树上的顺序与 Tab 索引来控制焦点移动。
- LVGL 使用
- 启发:
- 在 LVGL 上,可以借鉴 AWTK 对“自动焦点顺序”的处理,为 group 增加更灵活的“自动聚焦顺序”策略,而不仅仅是“按加入顺序”。
6.2 与 Qt 的对比
- Qt 的焦点管理依赖
QWidget树和QFocusPolicy,通过 Tab 顺序/箭头键决定焦点移动; - Qt 中,焦点大多围绕“窗口级别”组织,而不是额外的 group 类型。
- 对比来看:
lv_group类似“独立的焦点管理器”,可以跨越对象树的部分结构自定义焦点路径;- 这在嵌入式 UI 中对于“多个独立区域 + 遥控器导航”的场景更加灵活。
6.3 与 Android / HTML/CSS 的对比
- Android:
- 通过
focusable、nextFocusUp/Down/Left/Right等属性配置焦点路径; - 系统根据这些属性和 View 树做自动焦点导航。
- 通过
- HTML/CSS:
- 通过
tabindex控制 Tab 键顺序; - 浏览器内置默认焦点走向,但复杂页面往往需要手工调整。
- 通过
- 对 LVGL 的启发:
- 在现有
lv_group基础上,可以考虑为对象增加更丰富的“焦点导航提示”(类似nextFocus*或tabindex),group 再根据这些提示决定下一个焦点对象; - 这样既保留 group 的显式分组优势,又能在复杂布局中更精细地控制焦点路径。
- 在现有
6.4 可能的改进方向
- 从性能角度:
- 当 group 包含大量对象时,频繁在内部线性遍历切换焦点可能带来一定开销;
- 可以考虑为 group 提供“跳表/索引缓存”等轻量优化手段,减少在复杂界面中切换焦点的遍历成本。
- 从代码结构角度:
- 将与 group 强相关的逻辑(比如编辑模式、wrap 模式等)进一步解耦成小型策略对象,使得不同输入设备/不同 UI 模式可以选择不同策略;
- 这样可以在不修改核心 group 代码的情况下,为特定项目注入定制的焦点策略。
- 从 API 设计角度:
- 提供辅助工具函数,帮助开发者在调试时可视化 group 中对象的焦点顺序;
- 增强与 UI 编辑器/配置工具的联动,让 group 配置可以在设计期完成,而不是全部写在 C 代码中。
7 小结
lv_group是 LVGL9.4 中承载“键盘/编码器焦点导航”的关键组件:
- 它把一批对象组织成逻辑上的“焦点组”,并与输入设备绑定,用来决定焦点在各对象之间的流转路径;
- 通过与对象状态和主题样式配合,
lv_group能在资源受限的嵌入式设备上提供接近桌面/TV 级的焦点导航体验; - 在工程实践中,合理规划 group 粒度、焦点顺序和多 group 切换策略,是做好键盘/编码器交互体验的关键。
对有更复杂交互需求的项目,可以在lv_group之上构建更高层的“页面/区域导航管理器”,借鉴 AWTK、Qt、Android、Web 的成熟经验,进一步提升可维护性和可配置性。
8 附录
A 参考文档(外部)
- LVGL 官方文档:输入设备与焦点
- LVGL 官方文档:对象与状态
- LVGL GitHub 仓库
B 相关资源(CSDN 系列)
- 【奶茶Beta专项】【LVGL9.4源码分析】01-目录结构
- 【奶茶Beta专项】【LVGL9.4源码分析】02-编译框架-Cmake详解
- 【奶茶Beta专项】【LVGL9.4源码分析】03-显示框架-display
- 【奶茶Beta专项】【LVGL9.4源码分析】04-OS抽象层
- 【奶茶Beta专项】【LVGL9.4源码分析】05-标准库