ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

LVGL 无触摸屏控件导航:Keypad and Encoder 演示工程源码解析与移植参考

LVGL 无触摸屏控件导航:Keypad and Encoder 演示工程源码解析与移植参考
  • 嵌入式
  • 物联网
  • 智能硬件
  • 嵌入式GUI
  • 硬件开发

【免费下载链接】HoloCubic

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

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

导读

LVGL(本仓库内为 v7.6.1)并不强制要求触摸屏才能操作界面——通过键盘(Keypad)或旋转编码器(Encoder)这两类输入设备,同样可以完成按钮、下拉框、滚轮、滑条、开关、文本框等全部常用控件的选择、进入与编辑。本文以仓库中 lv_demo_keypad_encoder 演示的 README 为主体,结合其 源码实现,完整讲解该演示的功能范围、启用步骤、分组导航机制、编辑模式与虚拟键盘工作流,并给出在 HoloCubic 这类带编码器硬件的嵌入式项目中的落地参考。读完本文,你将掌握“如何让一个不含触摸屏的 LVGL 界面支持完整导航与文本输入”的整套方案。

Demo 概览:无触摸屏的控件导航

原文档开篇即点明了该演示的核心价值:LVGL 允许在不使用触摸板(touchpad)的情况下,通过键盘和/或编码器来控制控件。演示的目的就是展示在没有触摸屏时,如何操作以下控件类型:

  • 按钮(Button)
  • 复选框(Checkbox)
  • 滑条(Slider)
  • 开关(Switch)
  • 数字微调框(Spinbox)
  • 下拉列表(Drop-down list)
  • 滚轮选择器(Roller)
  • 列表(List)
  • 文本框/文本输入(Text area / text input)

上图即仓库中该演示的运行界面截图(800×480),画面由两个 Tab 页组成:左侧 "Selectors" 页集中展示了上述各类选择型控件,右侧 "Text input" 页则演示无触摸屏下的文本输入。

如何运行该演示

原文档给出了两步运行指引,下面结合仓库实际配置展开为完整的启用流程。

第一步:在lv_ex_conf.h中开启演示宏

找到 lv_ex_conf.h,将演示开关从 0 改为 1:

/*Demonstrate the usage of encoder and keyboard*/ #define LV_USE_DEMO_KEYPAD_AND_ENCODER 1

需要说明的是,仓库中默认值为 0(在 lv_ex_conf_template.h 中同样默认为 0),即该演示在 HoloCubic 固件中默认未启用;如需验证,可临时打开再编译。

同时需要注意两个可选辅助开关,它们决定了演示是否会注册对应的模拟输入设备:

宏默认值作用
LV_EX_KEYBOARD0为部分示例添加 PC 键盘支持(需要lv_drivers仓库,即模拟器中的键盘驱动)
LV_EX_MOUSEWHEEL0为部分示例添加“编码器”(鼠标滚轮模拟)支持(同样依赖lv_drivers)

这两个宏在 lv_ex_conf.h 中定义。开启后,演示代码会在初始化时通过lv_drivers提供的keyboard_read/mousewheel_read注册 Keypad 与 Encoder 类型的输入设备(见下文源码解析)。

第二步:在初始化完成后调用演示入口

在lv_init()与各驱动初始化完成之后,调用:

lv_demo_keypad_encoder();

该函数声明位于 lv_demo_keypad_encoder.h,整个示例被#if LV_USE_DEMO_KEYPAD_AND_ENCODER包裹,宏未开启时函数体不参与编译。需要注意的是,演示内部会调用lv_tabview_create等在“当前活动屏幕”上建控件,因此调用前必须确保显示驱动已经初始化且存在默认显示。

源码结构剖析:演示是怎么搭起来的

演示由三个文件组成,位于 lv_demo_keypad_encoder 目录:

  • README.md—— 本文主体所依据的说明文档;
  • lv_demo_keypad_encoder.h—— 对外仅暴露lv_demo_keypad_encoder()入口;
  • lv_demo_keypad_encoder.c—— 全部界面与交互逻辑(约 300 行)。

初始化流程

入口函数lv_demo_keypad_encoder()(lv_demo_keypad_encoder.c)按顺序完成五件事:

  1. 创建输入分组并设置焦点回调:
g = lv_group_create(); lv_group_set_focus_cb(g, focus_cb);

分组(lv_group_t)是 LVGL 无触摸导航的核心对象,后面会专门讲解。

  1. 条件注册键盘输入设备(当LV_EX_KEYBOARD开启时):
lv_indev_drv_t kb_drv; lv_indev_drv_init(&kb_drv); kb_drv.type = LV_INDEV_TYPE_KEYPAD; kb_drv.read_cb = keyboard_read; lv_indev_t * kb_indev = lv_indev_drv_register(&kb_drv); lv_indev_set_group(kb_indev, g);
  1. 条件注册编码器输入设备(当LV_EX_MOUSEWHEEL开启时,用鼠标滚轮模拟):
lv_indev_drv_t enc_drv; lv_indev_drv_init(&enc_drv); enc_drv.type = LV_INDEV_TYPE_ENCODER; enc_drv.read_cb = mousewheel_read; lv_indev_t * enc_indev = lv_indev_drv_register(&enc_drv); lv_indev_set_group(enc_indev, g);
  1. 创建 Tabview 与两个 Tab 页:t1 = "Selectors"、t2 = "Text input",并把tv加入分组g。

  2. 填充各页内容:调用selectors_create(t1)、text_input_create(t2),最后弹出启动消息框msgbox_create()。

Selectors 页:八大选择型控件

selectors_create() 在页面上按“列居中”布局依次创建:

  • lv_btn_create—— 按钮,标签为 "Button";
  • lv_checkbox_create—— 复选框;
  • lv_slider_create—— 滑条,范围 0~10;
  • lv_switch_create—— 开关;
  • lv_spinbox_create—— 数字微调框;
  • lv_dropdown_create—— 下拉列表,并注册了dd_enc事件回调(对LV_EVENT_VALUE_CHANGED做出响应);
  • lv_roller_create—— 滚轮选择器;
  • lv_list_create—— 列表,高度超过页面时自动收缩,并添加了 "Apply / Close / Show / Hide / Delete / Copy / Paste" 七个带LV_SYMBOL_*图标的按钮项。

这八类控件正好覆盖了 LVGL 中“可编辑(editable)”与“不可编辑”两类交互形态,是验证分组导航能力最齐全的集合。

Text input 页:无触摸屏的文本输入

text_input_create() 创建两个单行文本框ta1、ta2(后者继承前者样式),初始隐藏光标并设置占位文本 "Type something"。真正有趣的是ta_event_cb:当文本框获得焦点且分组处于编辑模式时,会动态创建一个虚拟键盘lv_keyboard_create,把键盘与文本框绑定,并将焦点移交到键盘——这就是“无触摸屏输入文字”的完整闭环,详见后文事件回调一节。

启动消息框

msgbox_create() 在顶层图层弹出欢迎消息框 "Welcome to the keyboard and encoder demo",含 "Ok" / "Cancel" 两个按钮,并将消息框加入分组后直接聚焦它(lv_group_focus_obj(mbox))。如果启用了滚轮模拟,还会预先lv_group_set_editing(g, true)让分组进入编辑状态。顶层图层被设置为半透明灰色且可点击,用于在消息框显示期间屏蔽背景操作。

关键机制一:输入设备类型与数据约定

演示注册的两种设备类型定义在 lv_hal_indev.h:

LV_INDEV_TYPE_KEYPAD, /**< Keypad or keyboard*/ LV_INDEV_TYPE_ENCODER, /**< Encoder with only Left, Right turn and a Button*/

两者的读取数据约定也由同一个头文件定义:

  • Keypad 设备通过data->key上报当前按键(lv_hal_indev.h);
  • Encoder 设备通过data->enc_diff上报“自上次读取以来的步数”,通过data->state上报LV_INDEV_STATE_PR/LV_INDEV_STATE_REL(lv_hal_indev.h)。

在底层 lv_indev.c 的indev_encoder_proc()中可以看到:编码器按下(LV_INDEV_STATE_PR)且键值为LV_KEY_ENTER时触发“确认/进入”;LV_KEY_LEFT/LV_KEY_RIGHT被转换为enc_diff的增减,从而模拟编码器左右旋转;长按LV_KEY_ENTER则会切换分组的编辑模式(对可编辑控件生效)。这说明编码器本质上被抽象成了“左/右转 + 一个按钮”,与触摸无关。

关键机制二:分组(Group)导航与编辑模式

无触摸导航的全部秘密都藏在lv_group中。演示在 lv_group.c 的基础上使用了以下 API:

API行号作用
lv_group_create()/lv_group_add_obj(g, obj)L110创建分组并把控件加入分组(同一对象不可重复加入)
lv_group_set_focus_cb(g, cb)L310设置焦点变化回调,用于滚动页面让焦点控件可见
lv_group_focus_obj(obj)L212将焦点移动到指定对象(会先解除当前焦点并发出LV_EVENT_DEFOCUSED)
lv_group_set_editing(g, edit)L320切换/退出编辑模式

演示里的focus_cb(lv_demo_keypad_encoder.c)是理解“焦点如何在页面内滚动”的样板:每当焦点对象改变,它读取当前 Tab 页编号,调用lv_page_focus(t1/t2, obj, LV_ANIM_ON)把对应页面滚动到焦点控件位置,从而保证焦点永远可见。

而tv_event_cb(lv_demo_keypad_encoder.c)处理 Tab 切换:当 Tab 页改变或收到LV_EVENT_REFRESH时,先清空分组lv_group_remove_all_objs(g),再依据当前 Tab 把该页的控件结构体数组(selector_objs或textinput_objs)整体重新加入分组。这是“不同页共享一个分组、焦点随页重建”的经典写法。

关键机制三:虚拟键盘与事件驱动流程

ta_event_cb(lv_demo_keypad_encoder.c)完整展示了文本框在无触摸屏下的输入流程:

  1. 文本框获得焦点(LV_EVENT_FOCUSED)时显示光标;
  2. 若分组处于编辑模式,则创建虚拟键盘控件lv_keyboard_create,加入分组并绑定当前文本框lv_keyboard_set_textarea(kb, ta);
  3. 把焦点交给键盘,并通过lv_page_focus让页面滚动到文本标签处;
  4. 文本框失焦(LV_EVENT_DEFOCUSED)时隐藏光标。

kb_event_cb(lv_demo_keypad_encoder.c)则负责收尾:先调用lv_keyboard_def_event_cb(kb, e)走默认键盘逻辑,当收到LV_EVENT_APPLY(确认)或LV_EVENT_CANCEL(取消)时,把焦点交还给文本框、删除虚拟键盘并把 Tabview 高度恢复为整屏。

模拟器验证:鼠标滚轮模拟编码器

在没有真实编码器硬件时,仓库自带的 Windows 模拟器工程 LvglSimulator 提供了现成的验证途径——lv_drivers/indev中的 mousewheel.c 把鼠标滚轮映射为编码器:

  • SDL_MOUSEWHEEL事件:滚轮 y 增量取反后写入enc_diff(即“向下滚 = 正向旋转”);
  • SDL_BUTTON_MIDDLE(鼠标中键)按下/释放映射为编码器的按下/释放状态;
  • mousewheel_read()每次读取后把enc_diff清零,返回false表示无缓冲数据。

对应的 keyboard.c 则把 SDL 键码转换为 LVGL 的LV_KEY_RIGHT / LV_KEY_LEFT / LV_KEY_UP / LV_KEY_DOWN / LV_KEY_ENTER / LV_KEY_ESC等标准键。这意味着在模拟器里用滚轮 + 鼠标中键,或在真实硬件上用键盘方向键 + 回车,就能完整走一遍该演示。

在 HoloCubic 等嵌入式项目中的落地参考

HoloCubic 固件工程位于 2.Firmware/HoloCubic-fw,其中 lv_port_indev.c 给出了真实设备上注册编码器驱动的标准模板:

lv_indev_drv_init(&indev_drv); indev_drv.type = LV_INDEV_TYPE_ENCODER; indev_drv.read_cb = encoder_read; indev_encoder = lv_indev_drv_register(&indev_drv);

真实硬件场景下,只需在encoder_read()中填充data->enc_diff(累计旋转步数)与data->state(按下/释放),并在中断服务函数encoder_handler()里累加步数、记录按键状态——这正是本演示在模拟器中所做事情的硬件版。从源码结构可以推断,该演示与平台无关:它只依赖lv_drivers提供的read_cb接口,任何能输出“左右步数 + 按钮状态”的硬件(旋转编码器、游戏手柄拨盘、滚轮等)都能无缝接入。

常见问题与注意事项

  1. 宏不生效:LV_USE_DEMO_KEYPAD_AND_ENCODER必须在编译期置 1,且需要把lv_demo_keypad_encoder.c纳入编译(可通过lv_examples.h统一包含头文件,见 lv_examples.h);本仓库中该演示与 HoloCubic 主固件默认是关闭状态。
  2. 必须先有显示驱动:lv_indev_drv_register内部依赖默认显示器(见 lv_hal_indev.c),务必在lv_init()与显示、输入驱动初始化之后再调用演示入口。
  3. 编辑模式与长按:对文本框、微调框等可编辑控件,编码器需通过长按 Enter(代码中为lv_group_set_editing的切换逻辑)进入编辑模式后,旋转才用于修改数值/选择字符,短按 Enter 仍是“确认/按下”,这是初次上手最易困惑的交互差异。
  4. 模拟器依赖:若要在 Windows 模拟器中体验键盘/滚轮控制,需要同时打开LV_EX_KEYBOARD与LV_EX_MOUSEWHEEL两个辅助宏,并链接lv_drivers工程中的键盘与鼠标滚轮驱动。

以上内容全部以当前仓库实际源码与配置为准(LVGL v7.6.1),读者可直接打开 演示源码 逐行对照,或参考 HoloCubic 固件的输入设备移植模板 将这套交互范式落地到自己的编码器硬件上。

  • 嵌入式
  • 物联网
  • 智能硬件
  • 嵌入式GUI
  • 硬件开发

【免费下载链接】HoloCubic

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

项目地址:https://gitcode.com/gh_mirrors/ho/HoloCubic
点击查看免费下载
上一篇:如何在不同系统上正确安装 Cursor AI:完整指南
下一篇:Music Tag Web高级搜索功能:利用正则表达式精准定位音乐文件

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进