☰
HoloCubic 固件中的 LVGL Switch 开关控件实战解析:从 lv_ex_switch 示例到源码级原理
2026/10/15 2:33:24 网站建设 项目流程
  • 嵌入式
  • 物联网
  • 智能硬件
  • 嵌入式GUI
  • 硬件开发

【免费下载链接】HoloCubic

带网络功能的伪全息透明显示桌面站

项目地址:https://gitcode.com/gh_mirrors/ho/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 控件的四个典型用法:

  1. 创建开关:lv_switch_create(lv_scr_act(), NULL)在当前活动屏幕lv_scr_act()上创建一个全新开关(copy参数为NULL);
  2. 对齐定位:lv_obj_align(sw1, NULL, LV_ALIGN_CENTER, 0, -50)将开关水平居中、垂直上移 50 像素;
  3. 绑定事件:lv_obj_set_event_cb(sw1, event_handler)注册回调。当用户点击开关时,控件内部会发送LV_EVENT_VALUE_CHANGED事件,回调中通过lv_switch_get_state(obj)读取布尔状态并打印On/Off;
  4. 复制对象: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.BGLV_SWITCH_PART_BG开关整体凹槽,圆角radius = 800,上下内边距 6px
填充指示lv.sw.STYLE.INDICLV_SWITCH_PART_INDIC开启时高亮区域,此处用0x9fc8ef浅蓝填充
滑块(开)lv.sw.STYLE.KNOB_ONLV_SWITCH_PART_KNOB开启态滑块,带 4px 底部投影
滑块(关)lv.sw.STYLE.KNOB_OFFLV_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_timeuint16_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

带网络功能的伪全息透明显示桌面站

项目地址:https://gitcode.com/gh_mirrors/ho/HoloCubic
点击查看免费下载

相关推荐

上一篇:tinygrad MNIST 入门教程:从模型定义、训练到 TinyJit 提速的完整实战
下一篇:Django Silk 查询分析功能:EXPLAIN ANALYZE 实战应用指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询