ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

LVGL 无触摸屏交互实战:Keypad and Encoder 演示源码解析与 X-TRACK 编码器接入

LVGL 无触摸屏交互实战:Keypad and Encoder 演示源码解析与 X-TRACK 编码器接入 智能硬件嵌入式硬件开发【免费下载链接】X-TRACKA GPS bicycle speedometer that supports offline maps and track recording项目地址https://gitcode.com/gh_mirrors/xt/X-TRACK点击查看免费下载导读本篇文章围绕 X-TRACK 仓库内置的 LVGLkeypad_encoder演示README.md展开系统讲解如何在不使用触摸屏的情况下仅凭按键Keypad与旋转编码器Encoder完成对按钮、下拉列表、滚动条、滑块、开关、文本输入等控件的完整操控。读完本文你将掌握LV_USE_DEMO_KEYPAD_AND_ENCODER的启用方法、lv_demo_keypad_encoder()的调用时机、LVGL 分组Group与导航/编辑双模式的底层原理并能在 X-TRACK 这类真实嵌入式项目中复刻同样的编码器交互方案。一、Demo 概览没有触摸屏也能玩转 LVGL 控件LVGL 的输入设备indev体系支持指针触摸、按键与编码器三种类型。keypad_encoder演示要证明的正是这一点即使设备没有触摸屏UI 依然可以完整可用。它通过一个 Tab 视图展示了按键/编码器导航下各类控件的正确处理方式Selectors选择器页表格Table、日历Calendar、按钮矩阵Button matrix、复选框Checkbox、滑块Slider、开关Switch、数值微调框Spinbox、下拉列表Dropdown、滚轮Roller以及带图标的按钮列表ListText input文本输入页两个单行文本框Text area与一个随焦点弹出的键盘Keyboard启动弹窗一个带“Ok / Cancel”按钮的消息框Message box演示启动时焦点如何被“冻结”在弹窗上。从 lv_demo_keypad_encoder.c 第 11 行可以看到整个演示源码都被#if LV_USE_DEMO_KEYPAD_AND_ENCODER包裹也就是说它并不是默认编译的需要先在配置中打开对应开关。二、三步跑起 Demo配置宏与调用入口原文档给出的运行步骤非常精炼只有两步但实际落地还需要把“编译包含”和“头文件引用”补齐这里一并展开1. 打开配置宏在lv_conf.h或等效的配置文件中将开关置 1#define LV_USE_DEMO_KEYPAD_AND_ENCODER 1在本仓库中该宏的默认值位于 lv_conf.hSimulator工程实际生效的配置#define LV_USE_DEMO_KEYPAD_AND_ENCODER 0同时 lv_conf_template.h 中保留了模板默认值而 lv_conf_internal.h 展示了宏的解析顺序优先使用 Kconfig 生成的CONFIG_LV_USE_DEMO_KEYPAD_AND_ENCODER否则回退到lv_conf.h中的定义再否则默认置 0#ifndef LV_USE_DEMO_KEYPAD_AND_ENCODER #ifdef CONFIG_LV_USE_DEMO_KEYPAD_AND_ENCODER #define LV_USE_DEMO_KEYPAD_AND_ENCODER CONFIG_LV_USE_DEMO_KEYPAD_AND_ENCODER #else #define LV_USE_DEMO_KEYPAD_AND_ENCODER 0 #endif #endif若你的工程通过 Kconfig 配置 LVGL如 Zephyr/ESP-IDF 场景可在 Kconfig 中找到config LV_USE_DEMO_KEYPAD_AND_ENCODER选项用 menuconfig 勾选即可二者殊途同归。2. 确保 demos 目录参与编译并包含头文件lvgl/demos/README.md 明确指出如果工具链不会自动把lvgl目录内的源文件加进编译需要确保demos文件夹被包含同时要在应用源码里引用demos/lv_demos.h//! main.c #include lvgl.h #include demos/lv_demos.h之所以要包含lv_demos.h是因为它充当了所有 demo 的“总闸门”lv_demos.h 会在宏开启时自动引入本 demo 的头文件#if LV_USE_DEMO_KEYPAD_AND_ENCODER #include keypad_encoder/lv_demo_keypad_encoder.h #endiflv_demo_keypad_encoder.h中只声明了一个导出函数void lv_demo_keypad_encoder(void);并包裹在extern C中以便 C 工程如 X-Track 的模拟器直接调用。3. 在lv_init()与驱动初始化之后调用lv_init(); /* LVGL 核心初始化 */ /* 初始化显示、输入设备等驱动 */ lv_demo_keypad_encoder(); /* 启动演示 */调用时机有两个硬性前提一是必须在lv_init()之后因为 demo 会创建控件与样式二是必须等输入设备驱动注册完毕因为 demo 入口函数会遍历所有已注册的输入设备并把按键/编码器设备绑定到分组上详见下一节。LVGL_Arduino.ino 中也有对应的注释示例// lv_demo_keypad_encoder(); // works, but I havent an encoder说明该 demo 在 Arduino 环境下同样可以直接跑通。三、源码级拆解lv_demo_keypad_encoder()到底做了什么完整的实现见 lv_demo_keypad_encoder.c整个文件只有 200 余行却覆盖了分组管理、设备绑定、控件创建、事件回调四条主线。下面逐段解读。3.1 创建分组并把输入设备绑定进组入口函数第 4777 行的第一件事是创建分组并设为默认组g lv_group_create(); lv_group_set_default(g);随后用一个for(;;)循环遍历所有已注册输入设备lv_indev_t * cur_drv NULL; for(;;) { cur_drv lv_indev_get_next(cur_drv); if(!cur_drv) break; if(cur_drv-driver-type LV_INDEV_TYPE_KEYPAD) { lv_indev_set_group(cur_drv, g); } if(cur_drv-driver-type LV_INDEV_TYPE_ENCODER) { lv_indev_set_group(cur_drv, g); } }这是整个 demo 最核心的“接线”逻辑遍历式绑定意味着无论系统注册了几个按键或编码器设备都会被纳入同一个分组g它们发出的按键/旋转事件最终都作用于当前获得焦点的对象。注意这里刻意没有绑定指针设备触摸屏从而保证演示场景的纯粹性。3.2 搭建 Tab 视图与两个页面tv lv_tabview_create(lv_scr_act(), LV_DIR_TOP, LV_DPI_DEF / 3); t1 lv_tabview_add_tab(tv, Selectors); t2 lv_tabview_add_tab(tv, Text input); selectors_create(t1); text_input_create(t2); msgbox_create();Tab 视图标签栏高度取LV_DPI_DEF / 3即随屏幕 DPI 自适应。三个静态函数selectors_create、text_input_create、msgbox_create分别负责填充两个页面与启动弹窗。Selectors 页密集的控件“演习场”selectors_create第 83139 行先用 Flex 布局让子控件纵向排列并居中然后依次创建表格写入 4×2 的00~31单元格、日历、按钮矩阵、复选框、滑块lv_slider_set_range(obj, 0, 10)限定 010、开关、数值微调框、下拉列表、滚轮以及一个带 6 个图标按钮的列表Apply / Close / Show / Hide / Delete / Copy / Paste。值得注意的细节是每个可能超出一屏的控件都被显式加了LV_OBJ_FLAG_SCROLL_ON_FOCUS标志lv_obj_add_flag(obj, LV_OBJ_FLAG_SCROLL_ON_FOCUS);这个标志的含义见 lvgl/docs/widgets/obj.md是当控件获得焦点时自动滚动父容器使其完整可见。在无触摸场景下这是“焦点移动到哪里屏幕就滚到哪里”的关键保障否则焦点可能落到屏幕之外而用户无从察觉。列表部分还有一个防溢出的兜底逻辑第 126130 行先lv_obj_update_layout(list)强制刷新布局若列表高度超过父容器内容区就把列表高度压缩到内容区高度避免列表内容被裁掉。Text input 页编码器点出软键盘text_input_create第 141160 行创建两个单行文本框占位符分别是Click with an encoder to show a keyboard与Type something同时创建一个初始隐藏的键盘控件lv_obj_t * kb lv_keyboard_create(lv_scr_act()); lv_obj_add_flag(kb, LV_OBJ_FLAG_HIDDEN); lv_obj_add_event_cb(ta1, ta_event_cb, LV_EVENT_ALL, kb); lv_obj_add_event_cb(ta2, ta_event_cb, LV_EVENT_ALL, kb);两个文本框共用同一个键盘实例与同一个事件回调ta_event_cb键盘对象通过lv_obj_add_event_cb的 user_data 参数传给回调。这种“一个键盘服务多个输入框”的写法在正式产品中非常实用。3.3 事件回调键盘的弹出与收回ta_event_cb第 198221 行实现了完整的键盘生命周期当事件码为LV_EVENT_CLICKED且当前输入设备类型是编码器LV_INDEV_TYPE_ENCODER时把键盘关联到被点击的文本框lv_keyboard_set_textarea(kb, ta)取消LV_OBJ_FLAG_HIDDEN显示键盘并把焦点与编辑模式切到键盘上同时把 Tab 视图高度压到LV_VER_RES / 2键盘对齐到屏幕底部中央——典型的“半屏输入法”布局当事件码为LV_EVENT_READY用户点击键盘的确认键或LV_EVENT_CANCEL取消/关闭键时重新隐藏键盘并把 Tab 视图高度恢复为LV_VER_RES。这里体现了 LVGL 输入设备类型判断的标准姿势lv_indev_get_act()取得当前活动输入设备lv_indev_get_type()取得其类型据此决定“只有编码器点击才弹键盘”——因为按键设备通常自带字符输入能力无需软键盘辅助。3.4 启动弹窗焦点冻结技巧msgbox_create第 162179 行在程序启动时弹出Hi消息框Welcome to the keyboard and encoder demo按钮为 Ok / Cancellv_group_focus_obj(lv_msgbox_get_btns(mbox)); lv_obj_add_state(lv_msgbox_get_btns(mbox), LV_STATE_FOCUS_KEY); lv_group_focus_freeze(g, true);其中lv_group_focus_freeze(g, true)是一个易被忽略但重要的细节冻结分组焦点让编码器/按键无法把焦点移出弹窗按钮防止用户误操作背后的控件。直到msgbox_event_cb收到LV_EVENT_VALUE_CHANGED用户按下 Ok 或 Cancel时才关闭弹窗、解除冻结并把焦点送回 Selectors 页第一个子控件第 181196 行lv_msgbox_close(msgbox); lv_group_focus_freeze(g, false); lv_group_focus_obj(lv_obj_get_child(t1, 0)); lv_obj_scroll_to(t1, 0, 0, LV_ANIM_OFF);四、原理深挖Group、特殊按键与导航/编辑双模式为什么 demo 里“按左右键就能改滑块、转编码器就能切换焦点”答案在 LVGL 官方文档 docs/overview/indev.md 的Keypad and encoder章节这里提炼四个核心机制。4.1 分组Group焦点的“管辖范围”想用按键/编码器控制的对象必须加入分组lv_group_t * g lv_group_create()创建分组lv_group_add_obj(g, obj)加入对象lv_indev_set_group(indev, g)把输入设备与分组绑定indev是lv_indev_drv_register()的返回值。一个分组内同时只有一个获得焦点的对象接收按键/编码器事件一个输入设备只能向一个分组发事件但一个分组可以接收多个输入设备的数据——这正是 demo 里按键、编码器共用分组g的合法性依据。4.2 预定义特殊按键LVGL 定义了带特殊语义的按键宏read_cb回调里应当把物理按键映射到这些宏上特殊键语义LV_KEY_NEXT/LV_KEY_PREV焦点移到下一个 / 上一个对象LV_KEY_ENTER触发LV_EVENT_PRESSED/CLICKED/LONG_PRESSED等事件LV_KEY_UP/LV_KEY_DOWN增加值 / 减小值或向上 / 向下移动LV_KEY_RIGHT/LV_KEY_LEFT增加值 / 减小值或向右 / 向左移动LV_KEY_ESC关闭或退出如收起下拉列表LV_KEY_DEL删除如文本框内右侧字符LV_KEY_BACKSPACE删除左侧字符LV_KEY_HOME/LV_KEY_END跳到开头 / 末尾文档还给出了工程建议按键设备一般只需映射LV_KEY_LEFT/RIGHT即可覆盖大多数控件编码器则只用LV_KEY_LEFT、LV_KEY_RIGHT与LV_KEY_ENTER三个键。4.3 导航模式与编辑模式Navigate / Edit由于编码器“按键”数量极少LVGL 引入双模式解决导航与编辑的矛盾导航模式Navigate编码器的LV_KEY_LEFT/RIGHT被翻译为LV_KEY_NEXT/PREV转动旋钮即切换焦点对象按下LV_KEY_ENTER进入编辑模式编辑模式EditLV_KEY_NEXT/PREV转而用于修改对象值。对于不可按压的对象如滑块短按ENTER即退出编辑模式对于短按本身有业务含义的对象如按钮则需要长按ENTER退出。demo 中第 212 行lv_group_set_editing(lv_obj_get_group(kb), kb)正是把键盘切到编辑模式让编码器可以直接在键盘按键间移动选择。4.4 默认分组与焦点样式交互控件可被自动加入默认分组创建分组后调用lv_group_set_default(g)demo 第 50 行再用lv_indev_set_group把输入设备绑到该组。获得焦点的对象会进入LV_STATE_FOCUSED状态进入编辑模式则进入LV_STATE_FOCUSED | LV_STATE_EDITED可用样式系统为这两个状态定制高亮外观让用户明确感知“当前选中了谁”。五、落地到 X-TRACK真实产品中的编码器接线keypad_encoderdemo 演示的是“框架层”能力而在 X-TRACK 项目里这套机制已经被真实使用——X-TRACK 是一款带离线地图与轨迹记录的 GPS 自行车码表其物理旋钮编码器正是 UI 的主交互手段。在固件侧lv_port_indev.cpp 按 LVGL 官方移植模板完成了编码器接入注册LV_INDEV_TYPE_ENCODER类型的输入设备read_cb指向encoder_read随后创建分组、绑定设备并设为默认分组indev_drv.type LV_INDEV_TYPE_ENCODER; indev_drv.read_cb encoder_read; lv_indev_t* indev lv_indev_drv_register(indev_drv); lv_group_t* group lv_group_create(); lv_indev_set_group(indev, group); lv_group_set_default(group);回调函数第 8599 行把编码器增量与按压状态填入lv_indev_data_t并对按压沿加入蜂鸣反馈data-enc_diff HAL::Encoder_GetDiff(); bool isPush HAL::Encoder_GetIsPush();>赞分享智能硬件嵌入式硬件开发【免费下载链接】X-TRACKA GPS bicycle speedometer that supports offline maps and track recording项目地址https://gitcode.com/gh_mirrors/xt/X-TRACK点击查看免费下载相关推荐X-TRACK地图缩放功能解析触摸交互与显示优化技术X TRACK是一款支持离线地图和轨迹记录的GPS自行车码表其地图缩放功能通过先进的触摸交互技术和显示优化算法为用户提供了流畅的地图浏览体验。这款开源项目的智能硬件嵌入式硬件开发openFrameworks 树莓派触摸屏开发入门rpiTouchExample 触摸事件与 ofAppEGLWindow 源码级解析openFrameworks 树莓派触摸屏开发入门rpiTouchExample 触摸事件与 ofAppEGLWindow 源码级解析 本篇技术指南围绕 op图形学音视频Cangjie-SIG/RGF_CJ触摸支持触摸屏的交互实现Cangjie SIG/RGF_CJ触摸支持触摸屏的交互实现 还在为Windows桌面应用的触摸交互开发而烦恼吗RGF_CJ框架提供了完整的触摸事件支持让图形学桌面应用上一篇OhMyOpenCode Atlas 主编排者提示词Kimi K2.7 变体完全解析从 task() 委派到 Final Verification Wave 的工程化编排协议下一篇ranger 控制台文件管理器版本演进全解析从 1.4.0 到 1.9.3 的关键能力变迁创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表