4、控件系统迁移(上):按钮、标签、文本框的创建与属性映射
好,咱们进入控件迁移的正题了。说实话,控件是GUI的骨架,用户看到的、点到的、输入的全是控件。TouchGFX和LVGL在控件体系上思路差不多,但API风格差异挺大。我刚开始从TouchGFX切到LVGL时,最不适应的就是——怎么连个按钮都要多写好几行?
但用顺手了你会发现,LVGL的控件设计更贴近嵌入式底层,灵活度也更高。今天咱们先啃三个最基础的控件:按钮、标签、文本框。这三个搞定了,后面那些复杂的控件(列表、滑块、图表)就一通百通。
4.1 按钮(Button)的创建与属性映射
按钮是交互的起点。TouchGFX里创建一个按钮,通常是继承Button类,然后在构造函数里设图片或文本。LVGL更直接——你创建一个对象,设置样式,绑定点击回调,完事。
先看个对比,你感受一下:
| 功能 | TouchGFX | LVGL |
|---|---|---|
| 创建按钮 | Button btn; + 构造函数初始化 |
lv_btn_create(parent) |
| 设置位置 | btn.setXY(10, 20) |
lv_obj_set_pos(btn, 10, 20) |
| 设置大小 | btn.setWidthHeight(100, 40) |
lv_obj_set_size(btn, 100, 40) |
| 设置文本 | 需组合TextArea或使用ButtonWithLabel |
lv_label_set_text(lv_obj_get_child(btn, 0), "OK") |
| 点击回调 | 重写clickHandler虚函数 |
lv_obj_add_event_cb(btn, handler, LV_EVENT_CLICKED, NULL) |
我个人习惯在LVGL里这样写一个标准按钮:
/* 创建按钮 */
lv_obj_t *btn = lv_btn_create(lv_scr_act());
lv_obj_set_pos(btn, 50, 50);
lv_obj_set_size(btn, 120, 40);
/* 按钮上的标签(LVGL创建按钮时会自动附带一个标签子对象) */
lv_obj_t *label = lv_obj_get_child(btn, 0);
lv_label_set_text(label, "确认");
/* 设置点击回调 */
lv_obj_add_event_cb(btn, btn_click_cb, LV_EVENT_CLICKED, NULL);
/* 回调函数 */
static void btn_click_cb(lv_event_t *e) {
LV_LOG_USER("按钮被点击了");
/* 你的业务逻辑 */
}
lv_label作为子对象,索引为0。如果你用lv_btn_create后直接lv_obj_get_child(btn, 0)就能拿到标签。但如果你自己额外添加了子对象,索引会变,建议用lv_label_create(btn)显式创建,更可控。
我在项目里遇到过一个问题:TouchGFX的按钮有setTouchable(true/false)来控制是否可点击,LVGL里对应的是lv_obj_add_flag(btn, LV_OBJ_FLAG_CLICKABLE)和lv_obj_clear_flag(btn, LV_OBJ_FLAG_CLICKABLE)。嗯,这个细节容易漏,迁移时记得检查。
4.2 标签(Label)的创建与属性映射
标签就是显示文本的,TouchGFX里叫TextArea,LVGL里叫lv_label。名字不同,但干的活一样——显示静态或动态文本。
来看看属性映射表:
| 功能 | TouchGFX (TextArea) | LVGL (lv_label) |
|---|---|---|
| 创建 | TextArea ta; |
lv_label_create(parent) |
| 设置文本 | ta.setTypedText(TypedText(T_TEXT_ID)) |
lv_label_set_text(label, "Hello") |
| 动态文本 | ta.setWildcard(buffer) |
lv_label_set_text_fmt(label, "值: %d", val) |
| 文本颜色 | ta.setColor(colortype) |
lv_obj_set_style_text_color(label, lv_color_hex(0x333333), 0) |
| 对齐方式 | ta.setAlignment(Alignment::CENTER) |
lv_label_set_long_mode(label, LV_LABEL_LONG_WRAP) + lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_CENTER, 0) |
LVGL的标签有个很实用的功能——长文本模式。你可以设置文本超出宽度时自动换行、滚动或省略。TouchGFX里要实现类似效果,得自己写逻辑或者用TextAreaWithWildcard配合滚动容器,挺麻烦的。
/* 创建标签 */
lv_obj_t *label = lv_label_create(lv_scr_act());
lv_obj_set_pos(label, 20, 100);
lv_obj_set_size(label, 200, 40);
/* 设置文本 */
lv_label_set_text(label, "系统运行时间: 0s");
/* 设置文本颜色和对齐 */
lv_obj_set_style_text_color(label, lv_color_hex(0x2c3e50), 0);
lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_CENTER, 0);
/* 动态更新(比如每秒更新一次) */
static void update_label(lv_timer_t *timer) {
static uint32_t seconds = 0;
seconds++;
lv_label_set_text_fmt(lv_obj_get_user_data(timer), "系统运行时间: %d秒", seconds);
}
/* 创建定时器更新标签 */
lv_obj_set_user_data(label, label);
lv_timer_create(update_label, 1000, NULL);
lv_label_set_text内部会动态分配内存来存储文本副本。如果你频繁更新文本(比如每秒一次),建议用lv_label_set_text_static传入静态字符串指针,避免内存碎片。我吃过这个亏——跑了一周后系统莫名卡死,查了半天发现是标签更新导致的内存碎片。
4.3 文本框(Text Area)的创建与属性映射
文本框是用户输入的入口。TouchGFX里叫TextAreaWithWildcard(其实更接近显示),真正的输入框是Keyboard配合TextArea。LVGL直接提供了lv_textarea,自带光标、选中、密码模式,省事不少。
| 功能 | TouchGFX | LVGL |
|---|---|---|
| 创建 | 组合TextArea + Keyboard |
lv_textarea_create(parent) |
| 设置占位文本 | 需手动实现 | lv_textarea_set_placeholder_text(ta, "请输入...") |
| 获取输入内容 | 通过回调读取缓冲区 | const char *text = lv_textarea_get_text(ta) |
| 密码模式 | 需自定义 | lv_textarea_set_password_mode(ta, true) |
| 最大字符数 | 通过缓冲区大小限制 | lv_textarea_set_max_length(ta, 20) |
| 光标控制 | 无原生光标 | lv_textarea_set_cursor_pos(ta, 0) |
写个实际例子,创建一个带键盘输入的文本框:
/* 创建文本框 */
lv_obj_t *ta = lv_textarea_create(lv_scr_act());
lv_obj_set_pos(ta, 30, 200);
lv_obj_set_size(ta, 260, 40);
/* 设置属性 */
lv_textarea_set_placeholder_text(ta, "点击输入用户名...");
lv_textarea_set_max_length(ta, 16);
lv_textarea_set_one_line(ta, true);
/* 设置样式:边框、圆角、背景色 */
lv_obj_set_style_border_width(ta, 1, 0);
lv_obj_set_style_border_color(ta, lv_color_hex(0xcccccc), 0);
lv_obj_set_style_radius(ta, 4, 0);
lv_obj_set_style_bg_color(ta, lv_color_hex(0xffffff), 0);
/* 创建键盘(LVGL内置键盘) */
lv_obj_t *kb = lv_keyboard_create(lv_scr_act());
lv_keyboard_set_textarea(kb, ta); /* 关联文本框 */
你看,LVGL的文本框和键盘是解耦的,但通过lv_keyboard_set_textarea一键关联。TouchGFX里你得自己处理键盘按键映射、焦点切换、输入法状态……说实话,LVGL这套省心太多了。
lv_textarea_set_password_mode(ta, true),并且把密码显示字符改成你想要的,比如lv_textarea_set_password_bullet(ta, "*")。默认是圆点,有些客户要求用星号,这个接口很贴心。
4.4 控件属性映射总览
为了让你迁移时能快速查阅,我把三个控件的核心属性映射整理成了一张图。这张SVG图展示了从TouchGFX到LVGL的思维转换路径:
4.5 迁移避坑指南
最后,我把自己踩过的坑总结一下,你迁移时直接对照检查:
- 父子关系别搞反:TouchGFX里控件添加到父容器是隐式的(通过
Container::add),LVGL里lv_xxx_create(parent)的第一个参数就是父对象。如果你传NULL或lv_scr_act(),控件会添加到当前活动屏幕。我刚开始写的时候,经常忘记传父对象,结果控件显示不出来。 - 样式是全局的:LVGL的样式默认会继承。如果你给屏幕设了一个字体,所有子控件默认都用这个字体。TouchGFX里每个控件独立设置样式,迁移时记得检查样式冲突。
- 回调参数不同:TouchGFX的回调是
void handler(const AbstractButton& btn),LVGL是void handler(lv_event_t *e)。你需要通过lv_event_get_target(e)拿到触发事件的控件对象。 - 内存管理:LVGL的控件创建会动态分配内存,删除用
lv_obj_del(obj)。如果你频繁创建删除控件(比如动态菜单),记得调用lv_mem_monitor检查内存碎片。
lv_keyboard已经处理了所有按键映射和焦点切换。白写了200行代码。所以迁移前,先翻翻LVGL的控件文档,看看有没有现成的。