4. 按钮控件(lv_btn):按钮的创建与样式、点击事件处理、按钮状态(按下、释放、聚焦)、按钮矩阵(lv_btnmatrix)的使用。

按钮,说白了就是界面上那个你点一下就会触发动作的小方块。在 LVGL 里,lv_btn 是最基础的交互控件,但你别小看它——我见过不少项目,按钮的点击反馈没做好,用户总觉得界面「卡顿」或者「不跟手」。这一节,咱们就把按钮从里到外捋一遍。

4.1 按钮的创建与基础样式

创建一个按钮非常简单,几行代码就能跑起来。我个人习惯先创建一个屏幕对象,然后在上面添加按钮。

lv_obj_t *btn = lv_btn_create(lv_scr_act());  // 在当前屏幕创建按钮
lv_obj_set_size(btn, 120, 50);                 // 设置宽高
lv_obj_center(btn);                            // 居中显示

嗯,这里要注意:lv_btn_create 的父对象可以是任意容器,不一定非要是屏幕。如果你想让按钮放在一个面板里,把父对象传进去就行。

样式方面,LVGL 的按钮默认是扁平风格,但你可以通过 lv_style_t 来定制。我在项目中遇到过客户要求按钮要有「磨砂玻璃」效果,其实就是调了背景透明度加模糊,但 LVGL 本身不支持模糊,所以我用半透明背景加圆角模拟了那个质感。

核心样式属性:

  • 背景颜色:lv_style_set_bg_color()
  • 圆角半径:lv_style_set_radius()
  • 边框宽度与颜色:lv_style_set_border_width() / lv_style_set_border_color()
  • 阴影:lv_style_set_shadow_width() / lv_style_set_shadow_color()
  • 文本颜色与字体:lv_style_set_text_color() / lv_style_set_text_font()

举个例子,给按钮加个蓝色背景、白色文字、圆角加阴影:

static lv_style_t style_btn;
lv_style_init(&style_btn);
lv_style_set_bg_color(&style_btn, lv_color_hex(0x2196F3));
lv_style_set_text_color(&style_btn, lv_color_white());
lv_style_set_radius(&style_btn, 8);
lv_style_set_shadow_width(&style_btn, 10);
lv_style_set_shadow_color(&style_btn, lv_color_hex(0x888888));

lv_obj_add_style(btn, &style_btn, 0);  // 0 表示默认状态

4.2 点击事件处理

按钮不响应点击,那跟一张贴图有什么区别?LVGL 的事件系统非常灵活,你可以给按钮绑定一个事件回调。

void btn_event_cb(lv_event_t *e) {
    lv_event_code_t code = lv_event_get_code(e);
    if(code == LV_EVENT_CLICKED) {
        LV_LOG_USER("按钮被点击了!");
        // 这里写你的业务逻辑
    }
}

lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_CLICKED, NULL);

你想想看,除了 LV_EVENT_CLICKED,还有哪些常用事件?

事件代码 触发时机 我常用的场景
LV_EVENT_PRESSED 手指按下瞬间 改变按钮颜色,给用户即时反馈
LV_EVENT_RELEASED 手指抬起 恢复按钮颜色,或触发非点击类动作
LV_EVENT_CLICKED 按下并抬起,且未滑动取消 主要业务逻辑触发点
LV_EVENT_LONG_PRESSED 长按超过设定时间 快捷操作,比如删除确认
LV_EVENT_VALUE_CHANGED 按钮状态变化(如切换开关) 复选框、开关按钮用得多

小技巧: 如果你需要区分「点击」和「长按」,可以在同一个回调里判断事件码。我曾经在一个遥控器项目里,短按是切换频道,长按是进入设置,用户反馈说手感很自然。

4.3 按钮状态:按下、释放、聚焦

LVGL 的按钮有几种内置状态,每种状态都可以单独设置样式。说白了,就是让按钮在不同情况下「变脸」。

  • LV_STATE_DEFAULT:默认状态,啥也没发生。
  • LV_STATE_PRESSED:正在被按下。
  • LV_STATE_FOCUSED:通过键盘或编码器聚焦到该按钮。
  • LV_STATE_DISABLED:禁用状态,不可点击。

为什么要区分状态?我举个例子:你做一个智能家居面板,按钮按下时变暗,释放后恢复,聚焦时加个外发光——用户一看就知道当前操作的是哪个按钮。

static lv_style_t style_pr;
lv_style_init(&style_pr);
lv_style_set_bg_color(&style_pr, lv_color_hex(0x1565C0));  // 按下时深蓝

static lv_style_t style_focus;
lv_style_init(&style_focus);
lv_style_set_border_width(&style_focus, 3);
lv_style_set_border_color(&style_focus, lv_color_hex(0xFFEB3B));  // 聚焦时黄色边框

lv_obj_add_style(btn, &style_pr, LV_STATE_PRESSED);
lv_obj_add_style(btn, &style_focus, LV_STATE_FOCUSED);

注意: 状态样式的添加顺序很重要。LVGL 会按照状态的优先级来应用样式,如果你同时按下了又聚焦,LV_STATE_PRESSED 的样式会覆盖 LV_STATE_FOCUSED 的部分属性。我曾经在这里踩过坑——聚焦边框在按下时消失了,后来发现是样式优先级没搞清楚。

4.4 按钮矩阵 (lv_btnmatrix)

如果你需要一排按钮,比如数字键盘、菜单栏,一个一个创建 lv_btn 太麻烦了。这时候 lv_btnmatrix 就派上用场了。它本质上是一个按钮组,用一个控件管理多个按钮。

创建按钮矩阵的核心是定义一个字符串数组,每个字符串代表一个按钮的文本,"\n" 表示换行。

static const char *btnm_map[] = {
    "1", "2", "3", "\n",
    "4", "5", "6", "\n",
    "7", "8", "9", "\n",
    "0", "确认", ""
};

lv_obj_t *btnm = lv_btnmatrix_create(lv_scr_act());
lv_btnmatrix_set_map(btnm, btnm_map);
lv_obj_set_size(btnm, 300, 200);
lv_obj_center(btnm);

你想想看,这个矩阵里的每个按钮其实都有独立的索引。点击事件通过 LV_EVENT_VALUE_CHANGED 来捕获,然后通过 lv_btnmatrix_get_selected_btn() 获取当前点击的是哪个按钮。

void btnm_event_cb(lv_event_t *e) {
    lv_obj_t *obj = lv_event_get_target(e);
    uint32_t id = lv_btnmatrix_get_selected_btn(obj);
    const char *txt = lv_btnmatrix_get_btn_text(obj, id);
    LV_LOG_USER("按下了按钮 %d: %s", (int)id, txt);
}

lv_obj_add_event_cb(btnm, btnm_event_cb, LV_EVENT_VALUE_CHANGED, NULL);

按钮矩阵的常用配置:

  • lv_btnmatrix_set_btn_width():设置某个按钮的相对宽度,比如让「确认」按钮占两列。
  • lv_btnmatrix_set_one_checked():开启单选模式,类似收音机按钮组。
  • lv_btnmatrix_set_ctrl_map():批量设置每个按钮的控制标志,比如禁用某个按钮。

我个人习惯在制作数字键盘时用按钮矩阵,因为布局整齐,代码量少。但要注意一点:按钮矩阵的所有按钮共享同一套样式。如果你需要某个按钮长得不一样(比如「确认」按钮是绿色的),那就得用 lv_btnmatrix_has_btn_ctrl() 配合自定义绘制,或者干脆单独创建一个 lv_btn 放在旁边。

4.5 知识体系总览

下面这张图把按钮相关的核心知识点串起来了,你可以对照着回顾一下。

按钮控件知识体系 按钮控件知识体系 lv_btn / lv_btnmatrix 创建与样式 事件处理 状态管理 lv_btn_create / lv_style_t 背景 / 圆角 / 边框 / 阴影 LV_EVENT_CLICKED / PRESSED lv_obj_add_event_cb 回调 DEFAULT / PRESSED / FOCUSED lv_obj_add_style(state) 按钮矩阵 lv_btnmatrix 用字符串数组定义布局,共享样式,适合键盘/菜单

这张图从左到右展示了按钮的三个核心维度:怎么创建和美化、怎么响应操作、怎么管理不同状态。最下面补充了按钮矩阵这个「批量生产」的工具。你写代码的时候,脑子里可以过一遍这个结构,不容易漏掉东西。

我的经验: 刚开始学 LVGL 时,我总喜欢把所有样式写在一个全局样式里,结果按钮一多就乱套。后来我养成了习惯:每个按钮组(比如导航栏、弹窗、键盘)各自维护一套局部样式,互不干扰。代码清晰了,调试也快。