- 嵌入式
- 物联网
- 智能硬件
- 嵌入式GUI
- 硬件开发
【免费下载链接】HoloCubic
带网络功能的伪全息透明显示桌面站
本文以 LVGL 官方示例 lv_ex_switch 文档 为核心骨架,逐行拆解 Switch(开关)控件的创建、事件响应、动画开关与样式定制方法,并结合 HoloCubic 固件中实际集成的 lv_switch 源码 与 lv_conf.h 配置,讲透该控件在 240×240 伪全息屏幕上的落地方式。读完你将掌握 LVGL Switch 的完整 API、底层实现机制,以及如何在 HoloCubic 这类嵌入式 GUI 工程中启用与运行它。
一、示例文档与源码的对应关系
在 HoloCubic 固件自带的 LVGL 源码树中,Switch 控件示例由三部分构成,位于同一目录下:
- index.rst:Sphinx 格式的示例索引页,标题为Simple Switch,通过
.. lv_example:: lv_ex_widgets/lv_ex_switch/lv_ex_switch_1指令将 C 语言示例嵌入文档,并注明 "MicroPython: No examples yet"(当时尚无官方 MicroPython 版本); - lv_ex_switch_1.c:C 语言参考实现,演示"创建 + 事件回调 + 复制 + 动画开启"四个关键操作;
- lv_ex_switch_1.png:示例在 320×240 屏幕上的渲染效果图,即上方文章头图。
示例入口函数lv_ex_switch_1()在 lv_ex_widgets.h 中声明,并被 examples.txt 收录进自动化示例构建清单——这是 LVGL 官方示例体系的标准组织方式,任何新增控件示例都会被自动纳入文档与构建流程。
二、C 语言示例逐行拆解
示例完整代码位于 lv_ex_switch_1.c,核心逻辑如下:
#include "../../../lv_examples.h" #include <stdio.h> #if LV_USE_SWITCH static void event_handler(lv_obj_t * obj, lv_event_t event) { if(event == LV_EVENT_VALUE_CHANGED) { printf("State: %s\n", lv_switch_get_state(obj) ? "On" : "Off"); } } void lv_ex_switch_1(void) { /*Create a switch and apply the styles*/ lv_obj_t *sw1 = lv_switch_create(lv_scr_act(), NULL); lv_obj_align(sw1, NULL, LV_ALIGN_CENTER, 0, -50); lv_obj_set_event_cb(sw1, event_handler); /*Copy the first switch and turn it ON*/ lv_obj_t *sw2 = lv_switch_create(lv_scr_act(), sw1); lv_switch_on(sw2, LV_ANIM_ON); lv_obj_align(sw2, NULL, LV_ALIGN_CENTER, 0, 50); } #endif这段代码展示了 Switch 控件的四个典型用法:
- 创建开关:
lv_switch_create(lv_scr_act(), NULL)在当前活动屏幕lv_scr_act()上创建一个全新开关(copy参数为NULL); - 对齐定位:
lv_obj_align(sw1, NULL, LV_ALIGN_CENTER, 0, -50)将开关水平居中、垂直上移 50 像素; - 绑定事件:
lv_obj_set_event_cb(sw1, event_handler)注册回调。当用户点击开关时,控件内部会发送LV_EVENT_VALUE_CHANGED事件,回调中通过lv_switch_get_state(obj)读取布尔状态并打印On/Off; - 复制对象:
lv_switch_create(lv_scr_act(), sw1)以第一个开关为模板创建第二个开关,会自动复制样式、事件回调与对象属性;随后lv_switch_on(sw2, LV_ANIM_ON)以带动画方式将开关拨到开启位置,lv_obj_align(sw2, NULL, LV_ALIGN_CENTER, 0, 50)将其放置在下方的对称位置。
值得注意的是#if LV_USE_SWITCH条件编译:只有当lv_conf.h中启用了LV_USE_SWITCH,示例才会被编译,这保证了控件未启用时不会引入无用的代码。
三、MicroPython 版本:样式定制增强
尽管 index.rst 标注当时"暂无 MicroPython 示例",仓库中实际附带了一份更完整的 lv_ex_switch_1.py,它对 C 版本做了样式层面的增强,正好补充了 C 示例中注释"apply the styles"未展开的部分:
def event_handler(obj, event): if event == lv.EVENT.VALUE_CHANGED: print("State: %s" % ("On" if obj.get_state() else "Off")) # Create styles for the switch bg_style = lv.style_t() indic_style = lv.style_t() knob_on_style = lv.style_t() knob_off_style = lv.style_t() lv.style_copy(bg_style, lv.style_pretty) bg_style.body.radius = 800 bg_style.body.padding.top = 6 bg_style.body.padding.bottom = 6 lv.style_copy(indic_style, lv.style_pretty_color) indic_style.body.radius = 800 indic_style.body.main_color = lv.color_hex(0x9fc8ef) indic_style.body.grad_color = lv.color_hex(0x9fc8ef) indic_style.body.padding.left = 0 indic_style.body.padding.right = 0 indic_style.body.padding.top = 0 indic_style.body.padding.bottom = 0 lv.style_copy(knob_off_style, lv.style_pretty) knob_off_style.body.radius = 800 knob_off_style.body.shadow.width = 4 knob_off_style.body.shadow.type = lv.SHADOW.BOTTOM lv.style_copy(knob_on_style, lv.style_pretty_color) knob_on_style.body.radius = 800 knob_on_style.body.shadow.width = 4 knob_on_style.body.shadow.type = lv.SHADOW.BOTTOM # Create a switch and apply the styles sw1 = lv.sw(lv.scr_act()) sw1.set_style(lv.sw.STYLE.BG, bg_style) sw1.set_style(lv.sw.STYLE.INDIC, indic_style) sw1.set_style(lv.sw.STYLE.KNOB_ON, knob_on_style) sw1.set_style(lv.sw.STYLE.KNOB_OFF, knob_off_style) sw1.align(None, lv.ALIGN.CENTER, 0, -50) sw1.set_event_cb(event_handler) # Copy the first switch and turn it ON sw2 = lv.sw(lv.scr_act(), sw1) sw2.on(lv.ANIM.ON) sw2.align(None, lv.ALIGN.CENTER, 0, 50) sw2.set_event_cb(lambda o,e: None)该版本完整揭示了 Switch 的三大部分样式模型,与底层部件枚举一一对应:
| 样式角色 | 代码标识 | 对应部件 | 作用 |
|---|---|---|---|
| 背景槽 | lv.sw.STYLE.BG | LV_SWITCH_PART_BG | 开关整体凹槽,圆角radius = 800,上下内边距 6px |
| 填充指示 | lv.sw.STYLE.INDIC | LV_SWITCH_PART_INDIC | 开启时高亮区域,此处用0x9fc8ef浅蓝填充 |
| 滑块(开) | lv.sw.STYLE.KNOB_ON | LV_SWITCH_PART_KNOB | 开启态滑块,带 4px 底部投影 |
| 滑块(关) | lv.sw.STYLE.KNOB_OFF | LV_SWITCH_PART_KNOB | 关闭态滑块,同样带投影 |
其中radius = 800是 LVGL 将圆角值设为"最大"的惯用写法(值远大于控件尺寸即等价于全圆角),滑块被绘制为圆形。事件逻辑与 C 版一致,但第二个开关的事件回调被替换为lambda o,e: None空函数,仅演示"复制对象"语义。
四、底层实现原理:基于 lv_bar 的派生控件
Switch 不是从零实现的独立控件,而是lv_bar(进度条)的派生类。这一点在 lv_switch.h 与 lv_switch.c 中体现得十分清晰:
typedef struct { lv_bar_ext_t bar; /*Ext. of ancestor*/ /*New data for this type */ lv_style_list_t style_knob; /*Style of the knob*/ } lv_switch_ext_t;扩展数据结构直接内嵌lv_bar_ext_t,仅在祖先基础上新增一个滑块样式列表。创建流程lv_switch_create()内部先调用lv_bar_create(par, copy),随后设置默认参数:
lv_obj_set_click(sw, true); lv_obj_add_protect(sw, LV_PROTECT_PRESS_LOST); lv_obj_set_size(sw, LV_DPX(60), LV_DPX(35)); /* 默认 60×35 尺寸 */ lv_bar_set_range(sw, 0, 1); /* 取值范围固定为 0 和 1 */ lv_theme_apply(sw, LV_THEME_SWITCH);从源码可以推断 Switch 的状态模型:取值范围被锁定为 0/1,开关的 ON/OFF 实际就是 bar 值为 1/0。这一点在 getter 中得到了直接印证:
static inline bool lv_switch_get_state(const lv_obj_t * sw) { return lv_bar_get_value(sw) == 1 ? true : false; }交互与信号处理
lv_switch_signal()是控件的信号中枢,处理三类关键交互:
- 点击释放(
LV_SIGNAL_RELEASED):检测当前状态并取反——lv_switch_get_state(sw)为真则lv_switch_off(sw, LV_ANIM_ON),否则lv_switch_on(sw, LV_ANIM_ON),随后发送LV_EVENT_VALUE_CHANGED事件通知应用层回调; - 键盘控制(
LV_SIGNAL_CONTROL):在启用LV_USE_GROUP时,方向键右键/上键lv_switch_on,左键/下键lv_switch_off,同样会触发LV_EVENT_VALUE_CHANGED; - 可编辑性声明(
LV_SIGNAL_GET_EDITABLE):显式返回false——虽然父类 slider 可编辑,但 Switch 在组导航中不可用方向键连续调节。
绘制机制
lv_switch_design()完成自定义绘制:先调用祖先(bar)的绘制函数画出背景槽与指示区,再由指示区宽度与槽宽度的比例关系计算滑块的横向位置——滑块 x 坐标由ext->bar.indic_area(bar 计算出的指示区)动态推导,因此开启/关闭动画的滑块移动本质上是bar 值在 0→1 之间动画过渡时指示区宽度变化的可视化结果;同时该函数支持LV_BIDI_DIR_RTL从右向左的镜像布局。
此外,lv_switch_on/lv_switch_off/lv_switch_toggle三者内部都会先检查LV_USE_ANIMATION:若编译期未启用动画,anim参数会被强制降级为LV_ANIM_OFF,直接跳变。
五、完整 API 速查
结合 lv_switch.h,Switch 对外暴露的接口全集如下:
| API | 说明 | 关键参数 |
|---|---|---|
lv_switch_create(par, copy) | 创建开关;copy非空时复制其属性与样式 | copy = NULL表示全新创建 |
lv_switch_on(sw, anim) | 打开开关 | anim取LV_ANIM_ON/LV_ANIM_OFF |
lv_switch_off(sw, anim) | 关闭开关 | 同上 |
lv_switch_toggle(sw, anim) | 翻转状态,返回翻转后的结果(bool) | 同上 |
lv_switch_set_anim_time(sw, anim_time) | 设置滑块动画时长(ms),实际委托给lv_bar_set_anim_time | uint16_t |
lv_switch_get_state(sw) | 读取状态,false为 OFF、true为 ON | 无 |
lv_switch_get_anim_time(sw) | 读取当前动画时长 | 无 |
部件枚举LV_SWITCH_PART_BG、LV_SWITCH_PART_INDIC复用自LV_BAR_PART_*,LV_SWITCH_PART_KNOB为虚拟部件之后的新成员,用于lv_obj_set_style系列样式接口与示例中的lv.sw.STYLE.KNOB_ON对应。
六、在 HoloCubic 固件中启用与运行
1. 配置开关:lv_conf.h
HoloCubic 固件使用的是 LVGL v7 分支,配置集中在 lv_conf.h。与 Switch 直接相关的三个开关均已默认启用:
#define LV_USE_ANIMATION 1 // lv_conf.h:149,开关动画的前置依赖 #define LV_USE_SLIDER 1 // lv_conf.h:712,Switch 的编译期硬依赖 #define LV_USE_SWITCH 1 // lv_conf.h:718,Switch 控件本体其中LV_USE_SLIDER是硬性依赖而非可选优化——lv_switch.c 开头有强制校验:
#if LV_USE_SLIDER == 0 #error "lv_sw: lv_slider is required. Enable it in lv_conf.h (LV_USE_SLIDER 1) " #endif若关闭LV_USE_SLIDER而保留LV_USE_SWITCH,编译会直接报错;默认配置通过 lv_conf_internal.h 将三者都置 1。
2. 在 240×240 屏幕上创建开关
HoloCubic 的显示环境由 display.cpp 初始化:LVGL 显示驱动注册为 240×240(disp_drv.hor_res = 240; disp_drv.ver_res = 240;),刷新回调my_disp_flush通过 TFT_eSPI 的pushColors将帧缓冲写入 ST7789 屏。在该工程中运行示例时,只需在lv_holo_cubic_gui()初始化完成之后调用lv_ex_switch_1(),即可在屏幕中央看到上下两个开关,点击上方开关时串口(115200 波特率)会打印State: On/State: Off。
需注意两点差异:一是官方示例效果图(320×240)宽度大于 HoloCubic 屏幕,控件相对居中位置会略有偏移,可改用LV_ALIGN_IN_TOP_MID/LV_ALIGN_IN_BOTTOM_MID适配;二是 HoloCubic 主循环 main.cpp 中由screen.routine()周期性调用lv_task_handler()驱动 LVGL 任务调度,示例中的事件响应依赖此循环持续运行。
3. 与现有 GUI 体系的衔接
HoloCubic 固件目前采用 GUI Guider 生成的lv_ui结构管理界面(见 gui_guider.c 与 setup_scr_home.c),事件绑定集中在 events_init.c 的events_init(lv_ui *ui)中(当前为空实现)。若要将 Switch 融入现有界面,建议在lv_holo_cubic_gui()或setup_scr_*中创建开关对象,并在events_init()中注册LV_EVENT_VALUE_CHANGED回调,与 LVGL 官方示例保持一致的"创建—绑定—派发"模式。
七、小结
LVGL Switch 示例(lv_ex_switch_1)虽然体量极小,却是理解 LVGL 派生控件设计范式的绝佳入口:它以 0/1 取值的 bar 为基础,通过信号函数接管点击翻转与键盘控制,通过设计函数绘制滑块位置,最终以LV_EVENT_VALUE_CHANGED事件向应用层传递状态变化。在 HoloCubic 固件中,启用LV_USE_SWITCH后即可在 240×240 屏幕上直接复用该示例,并可按 MicroPython 版本提供的三部分样式模型自由定制外观,为后续在桌面站界面中实现"功能开关"类交互(如 WiFi、背光、灯效的启停控制)提供现成的控件基础。
- 嵌入式
- 物联网
- 智能硬件
- 嵌入式GUI
- 硬件开发
【免费下载链接】HoloCubic
带网络功能的伪全息透明显示桌面站
相关推荐
HoloCubic 固件中的 LVGL List 列表控件实战:从 lv_ex_list_1 示例到源码级解析
HoloCubic 固件中的 LVGL List 列表控件实战:从 lv_ex_list_1 示例到源码级解析 导读 本文以 HoloCubic 固件内置的 L
嵌入式物联网智能硬件嵌入式GUI硬件开发HoloCubic 固件中 LVGL lv_table 表格组件实战:从 Simple Table 示例到源码级剖析
HoloCubic 固件中 LVGL lv_table 表格组件实战:从 Simple Table 示例到源码级剖析 HoloCubic 是一款带网络功能的伪全
嵌入式物联网智能硬件嵌入式GUI硬件开发HoloCubic 固件中的 LVGL 折线控件:lv_ex_line 示例与 lv_line 源码深度解析
HoloCubic 固件中的 LVGL 折线控件:lv_ex_line 示例与 lv_line 源码深度解析 导读 本文以 HoloCubic 伪全息桌面站固件
嵌入式物联网智能硬件嵌入式GUI硬件开发
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考