- 嵌入式
- 物联网
- 智能硬件
- 嵌入式GUI
- 硬件开发
【免费下载链接】HoloCubic
带网络功能的伪全息透明显示桌面站
导读:本文以 HoloCubic 开源仓库中 LVGL 官方示例文档 index.rst 及其对应示例
lv_ex_spinbox_1为主体,深入剖析 LVGL v7 中 Spinbox(数值微调控件)的创建、配置、交互与底层实现原理。读完本文,你将掌握 Spinbox 的全部核心 API(范围、数字格式、步进、回绕、光标定位),理解其基于 Textarea 的继承设计与格式化输出逻辑,并能在 HoloCubic 这类嵌入式 LVGL 项目中直接落地可运行的参数设置界面。
一、示例文档与代码位置
该文档位于仓库 LVGL 组件示例目录:
- 文档入口:index.rst,标题为Simple Spinbox,通过
.. lv_example::指令引用 C 示例。 - C 示例源码:lv_ex_spinbox_1.c
- 示例声明:lv_ex_widgets.h 中导出
void lv_ex_spinbox_1(void); - 运行截图:lv_ex_spinbox_1.png
需要说明的是:index.rst在 MicroPython 小节标注 "No examples yet",但示例目录中实际已存在 lv_ex_spinbox_1.py(详见本文第七节),本文将以 C 版为主线、Python 版为补充展开讲解。
上图即lv_ex_spinbox_1的运行效果:中间为数值显示框(当前显示+00.150,其中青色高亮竖条所在的数字5是当前选中待编辑的数位),左右两侧各有一个圆角方块按钮(左侧为减号,右侧为加号,右侧按钮处于焦点高亮状态)。这与示例代码中"两侧按钮 + 中间 Spinbox"的布局完全对应。
二、在 HoloCubic 工程中启用 Spinbox
Spinbox 的启用开关定义在 LVGL 配置文件 lv_conf.h:
#define LV_USE_SPINBOX 1依赖关系在 lv_spinbox.h 中做了强制校验:Spinbox 继承自 Textarea,因此必须同时启用文本框组件:
#if LV_USE_TEXTAREA == 0 #error "lv_spinbox: lv_ta is required. Enable it in lv_conf.h (LV_USE_TEXTAREA 1) " #endifHoloCubic 仓库固件目录2.Firmware/HoloCubic-fw/lib/lvgl下内置了同版本的 LVGL 库,其lv_conf.h同样将LV_USE_SPINBOX置 1,因此上述示例与 API 在该固件环境中开箱即用。
三、Simple Spinbox 示例逐行拆解
以下为lv_ex_spinbox_1的完整源码(位于 lv_ex_spinbox_1.c,以#if LV_USE_SPINBOX保护,未启用时整段被剔除):
static lv_obj_t * spinbox; static void lv_spinbox_increment_event_cb(lv_obj_t * btn, lv_event_t e) { if(e == LV_EVENT_SHORT_CLICKED || e == LV_EVENT_LONG_PRESSED_REPEAT) { lv_spinbox_increment(spinbox); } } static void lv_spinbox_decrement_event_cb(lv_obj_t * btn, lv_event_t e) { if(e == LV_EVENT_SHORT_CLICKED || e == LV_EVENT_LONG_PRESSED_REPEAT) { lv_spinbox_decrement(spinbox); } } void lv_ex_spinbox_1(void) { spinbox = lv_spinbox_create(lv_scr_act(), NULL); lv_spinbox_set_range(spinbox, -1000, 90000); lv_spinbox_set_digit_format(spinbox, 5, 2); lv_spinbox_step_prev(spinbox); lv_obj_set_width(spinbox, 100); lv_obj_align(spinbox, NULL, LV_ALIGN_CENTER, 0, 0); lv_coord_t h = lv_obj_get_height(spinbox); lv_obj_t * btn = lv_btn_create(lv_scr_act(), NULL); lv_obj_set_size(btn, h, h); lv_obj_align(btn, spinbox, LV_ALIGN_OUT_RIGHT_MID, 5, 0); lv_theme_apply(btn, LV_THEME_SPINBOX_BTN); lv_obj_set_style_local_value_str(btn, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_SYMBOL_PLUS); lv_obj_set_event_cb(btn, lv_spinbox_increment_event_cb); btn = lv_btn_create(lv_scr_act(), btn); lv_obj_align(btn, spinbox, LV_ALIGN_OUT_LEFT_MID, -5, 0); lv_obj_set_event_cb(btn, lv_spinbox_decrement_event_cb); lv_obj_set_style_local_value_str(btn, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_SYMBOL_MINUS); }3.1 创建与初始化
lv_spinbox_create(lv_scr_act(), NULL)以当前屏幕为父对象创建一个新的 Spinbox。从 lv_spinbox.c 的创建函数可以看到,它内部首先调用lv_textarea_create(),再通过lv_obj_allocate_ext_attr()追加 Spinbox 专有扩展数据,随后设置默认值:value=0、digit_count=5、step=1、range_min/max=±99999、rollover=false,并应用LV_THEME_SPINBOX主题。默认显示 5 位整数(dec_point_pos=0表示无小数点)。
3.2 范围与数字格式
lv_spinbox_set_range(spinbox, -1000, 90000); lv_spinbox_set_digit_format(spinbox, 5, 2);set_range:设置取值区间[-1000, 90000](闭区间,边界包含)。底层实现会同步钳制当前值:lv_spinbox.c 中若value > range_max则令value = range_max,反之亦然,然后触发lv_spinbox_updatevalue()刷新显示。set_digit_format(5, 2):digit_count=5表示数字总位数(不含小数点与符号);separator_position=2表示小数点前有 2 位整数,即格式为XX.XXX(2 位整数 + 3 位小数)。实现中对参数做了防御性约束(lv_spinbox.c):位数上限LV_SPINBOX_MAX_DIGIT_COUNT=10;若separator_position >= digit_count则自动置 0(退化为纯整数);且当位数小于 10 时,会把范围钳制在±(10^digit_count - 1)内,避免显示溢出。
示例中范围上限 90000 虽然只有 5 位,但正值方向实际上限是 99999,符合10^5 - 1的约束。
3.3 步进方向:step_prev 的作用
lv_spinbox_step_prev(spinbox);这是本示例最容易被忽略的一行。step(步长)决定一次增/减操作改变多少值,同时决定光标当前停留在哪一位。step_prev()将步长乘以 10(lv_spinbox.c),即把编辑目标从"个位"上移到"更高一位"。
结合默认step=1:调用step_prev()后step=10,光标落在十位,后续每次increment/decrement改变 10。步长上移受范围限制:new_step不能超过max(|range_max|, |range_min|),在本例中即 90000,因此步长最多到 10000。对应地,step_next()将步长除以 10(不小于 1),把编辑位向低位移动(lv_spinbox.c)。
3.4 加减按钮与事件回调
按钮与 Spinbox 等高(h = lv_obj_get_height(spinbox)),分别通过LV_ALIGN_OUT_RIGHT_MID和LV_ALIGN_OUT_LEFT_MID对齐到 Spinbox 的左右两侧,间距 5px。
两个回调分别处理LV_EVENT_SHORT_CLICKED(单击)和LV_EVENT_LONG_PRESSED_REPEAT(长按重复触发)——这意味着按住按钮不放即可连续加减,这正是 Spinbox 适合做"快速调值"交互的原因。注意:第二个按钮通过lv_btn_create(lv_scr_act(), btn)复制第一个按钮创建,因此继承了尺寸、主题等属性,只需重新设置对齐、事件回调和符号(LV_SYMBOL_MINUS)。
按钮应用了LV_THEME_SPINBOX_BTN主题:在 lv_theme_material.c 中,该主题为按钮主部件添加背景与点击态样式;同时lv_theme.c的LV_THEME_SPINBOX分支会为 Spinbox 的背景(LV_SPINBOX_PART_BG)和光标(LV_SPINBOX_PART_CURSOR)应用主题样式(lv_theme.c)。
四、Spinbox 核心 API 速查
以下接口均声明于 lv_spinbox.h,对应实现位于 lv_spinbox.c:
| 分类 | 函数 | 说明 |
|---|---|---|
| 创建 | lv_spinbox_create(par, copy) | 创建 Spinbox;copy非空时复制既有实例 |
| 数值 | lv_spinbox_set_value(spinbox, i) | 直接设置值(越界自动钳制,实现) |
| 数值 | lv_spinbox_get_value(spinbox) | 返回int32_t整数值;带小数格式时需自行除以10^(小数位数)换算为浮点 |
| 格式 | lv_spinbox_set_digit_format(spinbox, digit_count, separator_position) | 总位数与小数点位置;separator_position=0表示整数模式 |
| 范围 | lv_spinbox_set_range(spinbox, range_min, range_max) | 闭区间取值范围 |
| 步进 | lv_spinbox_set_step(spinbox, step) | 设置单次增减步长 |
| 步进 | lv_spinbox_step_next()/lv_spinbox_step_prev() | 步长 /10 或 ×10,控制编辑位向低/高位移动 |
| 步进 | lv_spinbox_increment()/lv_spinbox_decrement() | 按当前步长加/减一次 |
| 回绕 | lv_spinbox_set_rollover(spinbox, b)/lv_spinbox_get_rollover(spinbox) | 启用后到达边界会回绕到另一端(默认关闭) |
| 填充 | lv_spinbox_set_padding_left(spinbox, padding) | 符号与首个数字之间插入padding个空格,用于对齐显示 |
样式部分定义了两个部件(lv_spinbox.h):LV_SPINBOX_PART_BG(背景,继承 Textarea 的背景部件)与LV_SPINBOX_PART_CURSOR(光标,复用LV_TEXTAREA_PART_CURSOR),可通过lv_obj_set_style_*系列接口按部件定制外观。
五、源码原理:继承自 Textarea 的数值编辑器
5.1 扩展数据结构
Spinbox 的专有状态全部存放在lv_spinbox_ext_t(lv_spinbox.h)中,它以lv_textarea_ext_t ta作为首个成员,从而完整继承文本框的扩展数据(文本缓冲、光标位置等):
typedef struct { lv_textarea_ext_t ta; /*Ext. of ancestor*/ int32_t value; int32_t range_max; int32_t range_min; int32_t step; uint8_t rollover : 1; uint16_t digit_count : 4; uint16_t dec_point_pos : 4; /*0 表示无小数点,纯整数*/ uint16_t digit_padding_left : 4; } lv_spinbox_ext_t;5.2 格式化输出逻辑
lv_spinbox_updatevalue()(lv_spinbox.c)是 Spinbox 的显示核心,每次数值/步长/格式变化后都会执行,流程如下:
- 符号处理:仅当
range_min < 0时显示+/-前缀(正值范围则隐藏符号,并让光标左移一位); - 左填充:写入
digit_padding_left个空格; - 前导补零:将数值转为无符号数字串后,在左侧补
0至digit_count位; - 小数点插入:按
dec_point_pos切分整数部分与小数部分,在中间写入.; - 刷新文本:通过
lv_textarea_set_text()输出; - 光标定位:根据当前
step的十进制数量级计算光标应停留的位,若该位在小数点之后则额外跳过小数点。
举例:示例初始值 0、格式5,2、无填充,输出为+00.000;截图中的+00.150即交互(单击加号 15 次)后的状态,其中5所在位是当前step=10对应的十位(小数第二位),被光标高亮——这与示例调用step_prev()将步长设为 10 的行为完全吻合。
5.3 增量/减量、零交叉与回绕
increment/decrement(lv_spinbox.c)的实现包含两个值得注意的细节:
- 零交叉特殊模式:当从负值跨过 0 时,结果取绝对值的相反符号。例如当前值为
-3、步长 10,value + step = 7 > 0且value < 0,则先令value = -(-3) = 3再叠加步长,得到13——即跨零后直接跳到正区间对应位,避免出现-0.000这类显示。 - 回绕(rollover):未启用时越界直接饱和到边界值;启用后,当
value == range_max时继续加会回到range_min(减同理反向回绕)。
5.4 输入控制:键盘与旋转编码器
在信号处理函数lv_spinbox_signal()(lv_spinbox.c)中,Spinbox 完整接入了 LVGL 的输入体系:
LV_SIGNAL_CONTROL(按键):LV_KEY_RIGHT对编码器执行increment、对其他输入执行step_next;LV_KEY_LEFT对应decrement/step_prev;LV_KEY_UP/LV_KEY_DOWN直接增/减;其他按键会透传给文本框添加字符(lv_textarea_add_char)。LV_SIGNAL_RELEASED(释放):若是编码器且在编辑模式下,释放一次会切换到下一位编辑(step_next),当步长已为 1 时则从最高位重新开始循环;若为指针输入,则根据光标点击的位置反算step数量级,实现"点到哪一位就编辑哪一位"。
这一设计在仓库的 lv_demo_keypad_encoder.c 中有直接印证:键鼠/旋钮演示将 Spinbox 与其他控件一起放入控件组,验证其旋钮操作路径;lv_demo_stress.c 也在压力测试中通过set_digit_format(6, 3)、set_step(10)、连续increment等组合验证其高频刷新稳定性。
六、MicroPython 版本
虽然index.rst标注 "No examples yet",但示例目录实际提供了 lv_ex_spinbox_1.py,API 与 C 版一一对应:
def event_handler(obj, event): if event == lv.EVENT.VALUE_CHANGED: print("Value: %d" % obj.get_value()) elif event == lv.EVENT.CLICKED: # For simple test: Click the spinbox to increment its value obj.increment() spinbox = lv.spinbox(lv.scr_act()) spinbox.set_digit_format(5, 3) spinbox.step_prev() spinbox.set_width(100) spinbox.align(None, lv.ALIGN.CENTER, 0, 0) spinbox.set_event_cb(event_handler)值得注意的两点差异:Python 版采用set_digit_format(5, 3)(3 位整数 + 2 位小数),并在事件回调中监听VALUE_CHANGED(值变化)与CLICKED(单击时直接调用obj.increment()做演示),展示了"点击 Spinbox 本体即可加值"与"读取回调值"两种典型用法。
七、在 HoloCubic 项目中的应用思路
HoloCubic 固件(2.Firmware/HoloCubic-fw)的 GUI 基于同版本 LVGL 构建,从源码结构看,其界面代码集中在gui_guider.c、setup_scr_home.c、setup_scr_scenes.c等文件,采用 GuiGuider 生成的屏幕管理方式。Spinbox 非常适合用于这类桌面站的参数设置场景,例如:
- 时钟/闹钟设置:用 Spinbox 分别设置小时、分钟的数值位;
- 网络与系统参数:设置亮度、音量、刷新间隔等需要精确数值的配置项;
- 配合旋转编码器:HoloCubic 这类带实体按键/旋钮的设备可借助本文 5.4 节的键盘控制路径,实现"旋钮选择位、按键增减"的完整调节体验。
具体集成方式:在目标屏幕的lv_*_gui_create流程中调用lv_ex_spinbox_1()或按同样模式新建 Spinbox,并在事件回调(LV_EVENT_VALUE_CHANGED)中读取lv_spinbox_get_value()后换算为实际参数写入应用逻辑。
八、相关资源汇总
- 示例文档:2.Firmware/Libraries/lvgl/src/lv_examples/src/lv_ex_widgets/lv_ex_spinbox/index.rst
- 示例源码(C / Python):lv_ex_spinbox_1.c、lv_ex_spinbox_1.py
- 控件头文件与实现:lv_spinbox.h、lv_spinbox.c
- 启用开关:lv_conf.h
- 主题样式:lv_theme.c、lv_theme_material.c
- 组合应用示例:lv_demo_keypad_encoder.c、lv_demo_stress.c
至此,从示例文档出发,我们已经完整覆盖了 Spinbox 的启用、创建、配置、交互到底层格式化与输入处理的全部环节,可以直接在 HoloCubic 固件或任意 LVGL 工程中复用这套模式实现数值输入界面。
- 嵌入式
- 物联网
- 智能硬件
- 嵌入式GUI
- 硬件开发
【免费下载链接】HoloCubic
带网络功能的伪全息透明显示桌面站
相关推荐
LVGL Spinbox 控件实战:从 Simple Spinbox 示例到源码级原理解析(基于 X-TRACK 仓库内置 LVGL)
LVGL Spinbox 控件实战:从 Simple Spinbox 示例到源码级原理解析(基于 X TRACK 仓库内置 LVGL) 本指南以 LVGL 官方
智能硬件嵌入式硬件开发X-TRACK 模拟器内嵌 LVGL Calendar 日历控件实战:从示例代码到源码级解析
X TRACK 模拟器内嵌 LVGL Calendar 日历控件实战:从示例代码到源码级解析 本篇文章以 X TRACK 仓库中 LVGL 模拟器自带的小部件示
智能硬件嵌入式硬件开发HoloCubic 项目 LVGL Checkbox 复选框控件实战指南:从 lv_ex_checkbox_1 示例到源码实现
HoloCubic 项目 LVGL Checkbox 复选框控件实战指南:从 lv_ex_checkbox_1 示例到源码实现 导读 本文以 HoloCubic
嵌入式物联网智能硬件嵌入式GUI硬件开发
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考