LVGL轻量级图形库入门指南:嵌入式GUI开发必备技能

前言

大家好,今天给各位分享一个我在嵌入式GUI开发中的"神器"——LVGL图形库。作为嵌入式开发领域的"颜值担当",LVGL凭借其轻量级设计和强大功能,成为了众多开发者的首选。无论你是刚入门的小白,还是经验丰富的老手,这篇文章都能帮你快速上手LVGL,打造精美的嵌入式图形界面!

一、LVGL基础概念

1.1 什么是LVGL?

LVGL (Light and Versatile Graphics Library) 是一款免费开源的图形库,提供了创建嵌入式GUI所需的一切,具有易用的移动端风格图形元素、漂亮的视觉效果和低内存占用的特点。

1.2 LVGL数据流概览

LVGL的核心工作流程如下:

  1. 将LVGL添加到你的项目中
  2. 提供输入方式和显示驱动
  3. 连接时钟接口,让LVGL知道当前时间
  4. 定期调用定时器处理函数
  5. 构建一个或多个Widget树,以便LVGL渲染交互式UI

其中,定时器处理函数驱动LVGL的定时器执行以下周期性任务:

  • 刷新显示屏
  • 读取输入设备
  • 基于用户输入(和其他因素)触发事件
  • 运行动画
  • 执行用户创建的定时器

1.3 应用程序的工作

初始化后,应用程序的主要工作是:

  • 在需要时创建Widget树
  • 管理这些Widget生成的事件(通过用户交互等)
  • 在不再需要时删除它们

剩下的一切,都交给LVGL来处理!

二、重要概念详解

2.1 显示屏与屏幕的区别

初学LVGL,很多同学容易混淆以下两个概念:

  • 显示屏(Display Panel):物理显示像素的硬件
  • 显示对象(lv_display):RAM中代表LVGL将渲染到的显示屏的对象
  • 屏幕对象:Widget树中的"根"Widget,每个屏幕都"附加"到特定显示对象

2.2 默认显示屏与活动屏幕

  • 默认显示屏:创建的第一个显示(lv_display)对象
  • 活动屏幕:当前显示的屏幕(及其子Widget)

2.3 小部件(Widgets)

初始化LVGL后,应用程序创建一个Widget树,LVGL将其渲染到关联的显示屏以创建用户界面。

Widget是LVGL的"智能"图形元素,包括:

  • 基本Widget(简单矩形和屏幕)
  • 按钮
  • 标签
  • 复选框
  • 开关
  • 滑块
  • 图表等
2.3.1 创建Widget树

应用程序首先获取屏幕Widget的指针,然后将其他Widget添加为此屏幕的子Widget。Widget在创建时自动添加为其父Widget的子Widget。

任何Widget都可以包含其他Widget。例如,如果你希望按钮上有文字,可以创建一个标签Widget并将其添加为按钮的子Widget。

// 创建按钮
lv_obj_t * btn = lv_button_create(lv_screen_active());

// 创建标签并添加到按钮中
lv_obj_t * label = lv_label_create(btn);
lv_label_set_text(label, "按钮文字");

子Widget成为父Widget的"一部分",这种关系意味着:

  • 当父Widget移动时,其子Widget也跟随移动
  • 当父Widget被删除时,其子Widget也被删除
  • 子Widget仅在其父Widget的边界内可见

三、Widget操作详解

3.1 创建Widget

Widget通过调用如下形式的函数创建:

lv_obj_t * widget = lv_<类型>_create(parent);

例如:

// 创建一个滑块并添加到当前活动屏幕
lv_obj_t * slider1 = lv_slider_create(lv_screen_active());

3.2 修改Widget

所有Widget通用的属性通过以下形式的函数设置:

lv_obj_set_<属性名>(widget, <>);

例如:

// 设置滑块的位置和大小
lv_obj_set_x(slider1, 30);        // 设置X坐标为30
lv_obj_set_y(slider1, 10);        // 设置Y坐标为10
lv_obj_set_size(slider1, 200, 50); // 设置宽度为200,高度为50

特定类型的Widget还有自己的专有属性,通过以下形式的函数设置:

lv_<类型>_set_<属性名>(widget, <>);

例如:

// 设置滑块的值(带动画效果)
lv_slider_set_value(slider1, 70, LV_ANIM_ON);

3.3 删除Widget

删除任何Widget及其子Widget:

lv_obj_delete(widget);

四、事件处理

事件用于通知应用程序Widget发生了什么。你可以为Widget分配一个或多个回调函数,当Widget被点击、释放、拖动、删除等时调用。

// 为按钮添加点击事件回调
lv_obj_add_event_cb(btn, my_btn_event_cb, LV_EVENT_CLICKED, NULL);

// 事件回调函数
void my_btn_event_cb(lv_event_t * e)
{
    printf("按钮被点击了\n");
}

事件回调接收参数lv_event_t * e,包含当前事件代码和其他事件相关信息:

// 获取事件代码
lv_event_code_t code = lv_event_get_code(e);

// 获取触发事件的Widget
lv_obj_t * widget = lv_event_get_target(e);

五、部件与状态

5.1 部件(Parts)

Widget由一个或多个部件构成。例如,按钮只有一个部件叫做LV_PART_MAIN,而滑块有LV_PART_MAINLV_PART_INDICATORLV_PART_KNOB三个部件。

通过使用部件,你可以为Widget的子元素应用不同的样式。

5.2 状态(States)

Widget可以处于以下状态的组合:

  • LV_STATE_DEFAULT:正常,释放状态
  • LV_STATE_CHECKED:切换或选中状态
  • LV_STATE_FOCUSED:通过键盘、编码器或触摸板/鼠标点击获得焦点
  • LV_STATE_FOCUS_KEY:通过键盘或编码器获得焦点
  • LV_STATE_EDITED:被编码器编辑
  • LV_STATE_HOVERED:鼠标悬停
  • LV_STATE_PRESSED:被按下
  • LV_STATE_SCROLLED:正在滚动
  • LV_STATE_DISABLED:禁用状态

检查Widget是否处于给定状态:

if(lv_obj_has_state(widget, LV_STATE_PRESSED)) {
    // Widget正在被按下
}

添加或移除状态:

// 添加状态
lv_obj_add_state(widget, LV_STATE_CHECKED);

// 移除状态
lv_obj_remove_state(widget, LV_STATE_PRESSED);

六、样式与主题

6.1 样式(Styles)

样式实例包含背景颜色、边框宽度、字体等描述Widget外观的属性。

// 创建并初始化样式
static lv_style_t style1;
lv_style_init(&style1);

// 设置样式属性
lv_style_set_bg_color(&style1, lv_color_hex(0xa03080));  // 设置背景色
lv_style_set_border_width(&style1, 2);                   // 设置边框宽度为2像素

还可以为Widget添加本地样式属性:

// 为滑块的指示器部分在按下状态下设置背景色
lv_obj_set_style_bg_color(slider1, lv_color_hex(0x2080bb), LV_PART_INDICATOR | LV_STATE_PRESSED);

6.2 主题(Themes)

主题是Widget的一组默认样式。创建Widget时,会自动应用来自活动主题的样式。

应用程序的主题是在lv_conf.h中的编译时配置设置。

七、实战案例

7.1 Hello World标签

// 改变活动屏幕的背景颜色
lv_obj_set_style_bg_color(lv_screen_active(), lv_color_hex(0x003a57), LV_PART_MAIN);

// 创建一个白色标签,设置其文本并居中对齐
lv_obj_t * label = lv_label_create(lv_screen_active());
lv_label_set_text(label, "你好,世界");
lv_obj_set_style_text_color(lv_screen_active(), lv_color_hex(0xffffff), LV_PART_MAIN);
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);

7.2 带标签的按钮及点击事件

// 点击事件回调函数
static void btn_event_cb(lv_event_t * e)
{
    lv_event_code_t code = lv_event_get_code(e);
    lv_obj_t * btn = lv_event_get_target_obj(e);
    if(code == LV_EVENT_CLICKED) {
        static uint8_t cnt = 0;
        cnt++;

        // 获取按钮的第一个子元素(标签)并更改其文本
        lv_obj_t * label = lv_obj_get_child(btn, 0);
        lv_label_set_text_fmt(label, "按钮点击: %d", cnt);
    }
}

// 创建按钮
lv_obj_t * btn = lv_button_create(lv_screen_active());  // 在当前屏幕添加按钮
lv_obj_set_pos(btn, 10, 10);                         // 设置位置
lv_obj_set_size(btn, 120, 50);                       // 设置大小
lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_ALL, NULL);  // 分配回调函数

// 为按钮添加标签
lv_obj_t * label = lv_label_create(btn);
lv_label_set_text(label, "按钮");
lv_obj_center(label);

7.3 创建滑块并在标签上显示其值

static lv_obj_t * label;

// 滑块事件回调函数
static void slider_event_cb(lv_event_t * e)
{
    lv_obj_t * slider = lv_event_get_target_obj(e);

    // 更新文本
    lv_label_set_text_fmt(label, "%d", lv_slider_get_value(slider));
    lv_obj_align_to(label, slider, LV_ALIGN_OUT_TOP_MID, 0, -15);  // 对齐到滑块上方
}

// 在显示屏中央创建滑块
lv_obj_t * slider = lv_slider_create(lv_screen_active());
lv_obj_set_width(slider, 200);                       // 设置宽度
lv_obj_center(slider);                               // 对齐到父元素(屏幕)中央
lv_obj_add_event_cb(slider, slider_event_cb, LV_EVENT_VALUE_CHANGED, NULL);  // 分配事件函数

// 在滑块上方创建标签
label = lv_label_create(lv_screen_active());
lv_label_set_text(label, "0");
lv_obj_align_to(label, slider, LV_ALIGN_OUT_TOP_MID, 0, -15);  // 对齐到滑块上方

八、MicroPython支持

LVGL甚至可以与MicroPython一起使用:

# 初始化
import display_driver
import lvgl as lv

# 创建带标签的按钮
scr = lv.obj()
btn = lv.button(scr)
btn.align(lv.ALIGN.CENTER, 0, 0)
label = lv.label(btn)
label.set_text('你好,世界!')
lv.screen_load(scr)

最后

以上就是LVGL图形库的入门指南,希望对大家有所帮助!这个库虽然轻量级,但功能十分强大,尤其适合资源受限的嵌入式系统。我个人在多个项目中都使用了LVGL,体验非常棒,上手速度快,界面效果好,性能也相当不错。

如果你对文章内容有任何疑问,或者在使用LVGL过程中遇到什么困难,欢迎在评论区留言讨论。别忘了点赞、收藏、关注,我会持续分享更多嵌入式开发干货!

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐