LVGL 开发指南*
第一章 概述*
LVGL(Light and Versatile Graphics Library)是一个开源的嵌入式图形库,专为资源受限的设备设计,如微控制器和小型嵌入式系统。它提供了丰富的图形界面组件和功能,支持多种显示和输入设备,适用于需要图形用户界面的嵌入式应用。
目前APUS支持LVGL 8.3.3版本,对其底层绘图做了GA加速。
关于LVGL的更详细介绍请参考官方文档。
第二章 LVGL WMS*
LVGL WMS (Window Manager System) 是一套多窗口管理系统,帮助用户管理多个窗口界面,并实现各窗口之间的滑动效果。
主要原理*
其主要原理是需要滑动时,先将当前窗口和目标窗口的帧存保存起来,然后根据滑动时各个时间点的滑动偏移,组合两个保存下来的帧存,输出到最终的显示帧存中。
使用说明*
例如,创建3个窗口,窗口位置分别是左上、右、下,如下图所示,左上窗口可以向右或向下滑动,右窗口可以向左滑动,下窗口可以向上滑动。
另外,WMS支持多个场景,例如,上面的3个窗口所在的场景我们把他称作主场景,主场景的下窗口可以创建一个按键,点击进入另一个场景:场景1。场景1有两个窗口,分别是左窗口和右窗口,左窗口可以向右移动,右窗口可以向左滑动,右窗口可以创建一个按键,点击返回主场景的下窗口。
示例代码*
为了使用WMS,需要实现每个窗口对应的创建和销毁函数,以及窗口管理代码。
窗口示例代码*
-
主场景左上窗口:wms_scene0_lefttop.c
#include <lvgl/lvgl.h> static lv_obj_t *lv_win_at_scene0_lefttop_create(uint32_t win_id); lv_wms_window_t g_win_at_scene0_lefttop = { .create_func = lv_win_at_scene0_lefttop_create, .window = NULL, }; static void lv_win_at_scene0_lefttop_self_destroy(void) { if (g_win_at_scene0_lefttop.window == NULL) { return; } // 反初始化WMS lv_wms_deinit(g_win_at_scene0_lefttop.window); lv_obj_del(g_win_at_scene0_lefttop.window); g_win_at_scene0_lefttop.window = NULL; } static lv_obj_t *lv_win_at_scene0_lefttop_create(uint32_t win_id) { if (g_win_at_scene0_lefttop.window) { // 即使当前screen并没有被销毁,也需要更新一下相邻screen的信息来确保不同scene下的正确滑动关系 lv_wms_update_neighbor_setting(g_win_at_scene0_lefttop.window, win_id); lv_scr_load(g_win_at_scene0_lefttop.window); return g_win_at_scene0_lefttop.window; } // 创建一个screen object g_win_at_scene0_lefttop.window = lv_obj_create(NULL); // 在这里添加布局代码 lv_obj_set_style_bg_color(g_win_at_scene0_lefttop.window, lv_palette_main(LV_PALETTE_BLUE), LV_PART_MAIN); // 初始化WMS lv_wms_init(g_win_at_scene0_lefttop.window); // 设置销毁函数, WMS并不会自动销毁object lv_wms_self_destroy_func_set(g_win_at_scene0_lefttop.window, lv_win_at_scene0_lefttop_self_destroy); // 更新相邻screen的信息 lv_wms_update_neighbor_setting(g_win_at_scene0_lefttop.window, win_id); // 加载当前screen lv_scr_load(g_win_at_scene0_lefttop.window); return g_win_at_scene0_lefttop.window; }
-
主场景右窗口:wms_scene0_right.c
#include <lvgl/lvgl.h> static lv_obj_t *lv_win_at_scene0_right_create(uint32_t win_id); lv_wms_window_t g_win_at_scene0_right = { .create_func = lv_win_at_scene0_right_create, .window = NULL, }; static void lv_win_at_scene0_right_self_destroy(void) { if (g_win_at_scene0_right.window == NULL) { return; } // 反初始化WMS lv_wms_deinit(g_win_at_scene0_right.window); lv_obj_del(g_win_at_scene0_right.window); g_win_at_scene0_right.window = NULL; } static lv_obj_t *lv_win_at_scene0_right_create(uint32_t win_id) { if (g_win_at_scene0_right.window) { // 即使当前screen并没有被销毁,也需要更新一下相邻screen的信息来确保不同scene下的正确滑动关系 lv_wms_update_neighbor_setting(g_win_at_scene0_right.window, win_id); lv_scr_load(g_win_at_scene0_right.window); return g_win_at_scene0_right.window; } // 创建一个screen object g_win_at_scene0_right.window = lv_obj_create(NULL); // 在这里添加布局代码 lv_obj_set_style_bg_color(g_win_at_scene0_right.window, lv_palette_main(LV_PALETTE_GREEN), LV_PART_MAIN); // 初始化WMS lv_wms_init(g_win_at_scene0_right.window); // 设置销毁函数, WMS并不会自动销毁object lv_wms_self_destroy_func_set(g_win_at_scene0_right.window, lv_win_at_scene0_right_self_destroy); // 更新相邻screen的信息 lv_wms_update_neighbor_setting(g_win_at_scene0_right.window, win_id); // 加载当前screen lv_scr_load(g_win_at_scene0_right.window); return g_win_at_scene0_right.window; }
-
主场景下窗口:wms_scene0_bottom.c
#include <lvgl/lvgl.h> #include "wms_win_manager.h" static lv_obj_t *lv_win_at_scene0_bottom_create(uint32_t win_id); lv_wms_window_t g_win_at_scene0_bottom = { .create_func = lv_win_at_scene0_bottom_create, .window = NULL, }; static void lv_win_at_scene0_bottom_self_destroy(void) { if (g_win_at_scene0_bottom.window == NULL) { return; } // 反初始化WMS lv_wms_deinit(g_win_at_scene0_bottom.window); lv_obj_del(g_win_at_scene0_bottom.window); g_win_at_scene0_bottom.window = NULL; } static void event_cb(lv_event_t *e) { lv_wms_scene_enter_scene1(); // 按键进入场景1 } static lv_obj_t *lv_win_at_scene0_bottom_create(uint32_t win_id) { if (g_win_at_scene0_bottom.window) { // 即使当前screen并没有被销毁,也需要更新一下相邻screen的信息来确保不同scene下的正确滑动关系 lv_wms_update_neighbor_setting(g_win_at_scene0_bottom.window, win_id); lv_scr_load(g_win_at_scene0_bottom.window); return g_win_at_scene0_bottom.window; } // 创建一个screen object g_win_at_scene0_bottom.window = lv_obj_create(NULL); // 在这里添加布局代码 // 添加按键 lv_obj_t *btn = lv_btn_create(g_win_at_scene0_bottom.window); lv_obj_align(btn, LV_ALIGN_CENTER, 0, 0); lv_obj_set_width(btn, 50); lv_obj_set_height(btn, 50); static lv_style_t style_btn; lv_style_init(&style_btn); lv_style_set_radius(&style_btn, 5); lv_style_set_border_color(&style_btn, lv_color_white()); lv_style_set_border_opa(&style_btn, LV_OPA_50); lv_obj_add_style(btn, &style_btn, LV_STATE_DEFAULT); lv_obj_add_event_cb(btn, event_cb, LV_EVENT_CLICKED, NULL); // 添加按键事件 lv_obj_set_style_bg_color(g_win_at_scene0_bottom.window, lv_palette_main(LV_PALETTE_ORANGE), LV_PART_MAIN); // 初始化WMS lv_wms_init(g_win_at_scene0_bottom.window); // 设置销毁函数, WMS并不会自动销毁object lv_wms_self_destroy_func_set(g_win_at_scene0_bottom.window, lv_win_at_scene0_bottom_self_destroy); // 更新相邻screen的信息 lv_wms_update_neighbor_setting(g_win_at_scene0_bottom.window, win_id); // 加载当前screen lv_scr_load(g_win_at_scene0_bottom.window); return g_win_at_scene0_bottom.window; }
-
场景1左窗口:wms_scene1_left.c
#include <lvgl/lvgl.h> static lv_obj_t *lv_win_at_scene1_left_create(uint32_t win_id); lv_wms_window_t g_win_at_scene1_left = { .create_func = lv_win_at_scene1_left_create, .window = NULL, }; static void lv_win_at_scene1_left_self_destroy(void) { if (g_win_at_scene1_left.window == NULL) { return; } // 反初始化WMS lv_wms_deinit(g_win_at_scene1_left.window); lv_obj_del(g_win_at_scene1_left.window); g_win_at_scene1_left.window = NULL; } static lv_obj_t *lv_win_at_scene1_left_create(uint32_t win_id) { if (g_win_at_scene1_left.window) { // 即使当前screen并没有被销毁,也需要更新一下相邻screen的信息来确保不同scene下的正确滑动关系 lv_wms_update_neighbor_setting(g_win_at_scene1_left.window, win_id); lv_scr_load(g_win_at_scene1_left.window); return g_win_at_scene1_left.window; } // 创建一个screen object g_win_at_scene1_left.window = lv_obj_create(NULL); // 在这里添加布局代码 lv_obj_set_style_bg_color(g_win_at_scene1_left.window, lv_palette_main(LV_PALETTE_PURPLE), LV_PART_MAIN); // 初始化WMS lv_wms_init(g_win_at_scene1_left.window); // 设置销毁函数, WMS并不会自动销毁object lv_wms_self_destroy_func_set(g_win_at_scene1_left.window, lv_win_at_scene1_left_self_destroy); // 更新相邻screen的信息 lv_wms_update_neighbor_setting(g_win_at_scene1_left.window, win_id); // 加载当前screen lv_scr_load(g_win_at_scene1_left.window); return g_win_at_scene1_left.window; }
-
场景1右窗口:wms_scene1_right.c
#include <lvgl/lvgl.h> #include "wms_win_manager.h" static lv_obj_t *lv_win_at_scene1_right_create(uint32_t win_id); lv_wms_window_t g_win_at_scene1_right = { .create_func = lv_win_at_scene1_right_create, .window = NULL, }; static void lv_win_at_scene1_right_self_destroy(void) { if (g_win_at_scene1_right.window == NULL) { return; } // 反初始化WMS lv_wms_deinit(g_win_at_scene1_right.window); lv_obj_del(g_win_at_scene1_right.window); g_win_at_scene1_right.window = NULL; } static void event_cb(lv_event_t *e) { lv_wms_scene_exit_scene1(); // 退出场景1 } static lv_obj_t *lv_win_at_scene1_right_create(uint32_t win_id) { if (g_win_at_scene1_right.window) { // 即使当前screen并没有被销毁,也需要更新一下相邻screen的信息来确保不同scene下的正确滑动关系 lv_wms_update_neighbor_setting(g_win_at_scene1_right.window, win_id); lv_scr_load(g_win_at_scene1_right.window); return g_win_at_scene1_right.window; } // 创建一个screen object g_win_at_scene1_right.window = lv_obj_create(NULL); // 在这里添加布局代码 // 添加按键 lv_obj_t *btn = lv_btn_create(g_win_at_scene1_right.window); lv_obj_align(btn, LV_ALIGN_CENTER, 0, 0); lv_obj_set_width(btn, 50); lv_obj_set_height(btn, 50); static lv_style_t style_btn; lv_style_init(&style_btn); lv_style_set_radius(&style_btn, 5); lv_style_set_border_color(&style_btn, lv_color_black()); lv_style_set_border_opa(&style_btn, LV_OPA_50); lv_obj_add_style(btn, &style_btn, LV_STATE_DEFAULT); lv_obj_add_event_cb(btn, event_cb, LV_EVENT_CLICKED, NULL); // 添加按键事件 lv_obj_set_style_bg_color(g_win_at_scene1_right.window, lv_palette_main(LV_PALETTE_LIGHT_BLUE), LV_PART_MAIN); // 初始化WMS lv_wms_init(g_win_at_scene1_right.window); // 设置销毁函数, WMS并不会自动销毁object lv_wms_self_destroy_func_set(g_win_at_scene1_right.window, lv_win_at_scene1_right_self_destroy); // 更新相邻screen的信息 lv_wms_update_neighbor_setting(g_win_at_scene1_right.window, win_id); // 加载当前screen lv_scr_load(g_win_at_scene1_right.window); return g_win_at_scene1_right.window; }
窗口管理示例代码*
-
窗口管理代码:wms_win_manager.c
#include <lvgl/lvgl.h> extern lv_wms_window_t g_win_at_scene0_lefttop; extern lv_wms_window_t g_win_at_scene0_right; extern lv_wms_window_t g_win_at_scene0_bottom; extern lv_wms_window_t g_win_at_scene1_left; extern lv_wms_window_t g_win_at_scene1_right; // 声明一个用于指代WMS Window的window ID枚举,需要避开_INVALID_WIN_ID,即0 enum { WMS_WIN_ID_INVALID = _INVALID_WIN_ID, WMS_WIN_ID_AT_SCENE0_LEFTTOP, WMS_WIN_ID_AT_SCENE0_RIGHT, WMS_WIN_ID_AT_SCENE0_BOTTOM, WMS_WIN_ID_AT_SCENE1_LEFT, WMS_WIN_ID_AT_SCENE1_RIGHT, }; // 声明一个用于指代WMS Scene的scene ID枚举,需要避开_INVALID_SCENE_ID,即0 enum { WMS_SCENE_ID_INVALID = _INVALID_SCENE_ID, WMS_SCENE0_ID, WMS_SCENE1_ID, }; // 定义一个用于将Window ID和WMS Window关联起来的表 static const lv_wms_window_map_t WMS_WINDOW_MAP[] = { {WMS_WIN_ID_AT_SCENE0_LEFTTOP, &g_win_at_scene0_lefttop}, {WMS_WIN_ID_AT_SCENE0_RIGHT, &g_win_at_scene0_right}, {WMS_WIN_ID_AT_SCENE0_BOTTOM, &g_win_at_scene0_bottom}, {WMS_WIN_ID_AT_SCENE1_LEFT, &g_win_at_scene1_left}, {WMS_WIN_ID_AT_SCENE1_RIGHT, &g_win_at_scene1_right}, }; #define WMS_WINDOW_COUNT (sizeof(WMS_WINDOW_MAP)/sizeof(WMS_WINDOW_MAP[0])) // 定义一个用于描述主Scene上下左右滑动关系的二维数组 #define SCENE0_X_ELEMS 2 #define SCENE0_Y_ELEMS 2 static uint32_t s_scene0[SCENE0_Y_ELEMS][SCENE0_X_ELEMS] = { {WMS_WIN_ID_AT_SCENE0_LEFTTOP, WMS_WIN_ID_AT_SCENE0_RIGHT}, {WMS_WIN_ID_AT_SCENE0_BOTTOM,} }; #define SCENE1_X_ELEMS 2 #define SCENE1_Y_ELEMS 1 static uint32_t s_scene1[SCENE1_Y_ELEMS][SCENE1_X_ELEMS] = { {WMS_WIN_ID_AT_SCENE1_LEFT, WMS_WIN_ID_AT_SCENE1_RIGHT}, }; // 定义一个包含所有WMS Scene的数组, 包括Scene ID,该Scene的主窗口,该Scene的横竖窗口数量,以及Scene内Window的滑动关系 static const lv_wms_scene_t WMS_SCENE_MAP[] = { {WMS_SCENE0_ID, WMS_WIN_ID_AT_SCENE0_LEFTTOP, SCENE0_X_ELEMS, SCENE0_Y_ELEMS, (uint32_t *)s_scene0}, {WMS_SCENE1_ID, WMS_WIN_ID_AT_SCENE1_LEFT, SCENE1_X_ELEMS, SCENE1_Y_ELEMS, (uint32_t *)s_scene1}, }; #define WMS_SCENE_COUNT (sizeof(WMS_SCENE_MAP)/sizeof(WMS_SCENE_MAP[0])) void lv_wms_scene_init(void) { // 初始化Scene管理器,同时加载默认Scene的默认Window lv_wms_scene_config_t config = { .p_win_map_table = (lv_wms_window_map_t *)WMS_WINDOW_MAP, .p_scene_map_table = (lv_wms_scene_t *)WMS_SCENE_MAP, .win_amount = WMS_WINDOW_COUNT, .scene_amount = WMS_SCENE_COUNT, .startup_scene_id = WMS_SCENE0_ID, .startup_win_id = WMS_WIN_ID_AT_SCENE0_LEFTTOP, // 启动时显示的窗口,即主窗口 }; lv_wms_scene_startup(&config); } // 进入场景1 void lv_wms_scene_enter_scene1(void) { lv_wms_scene_enter_into(WMS_SCENE1_ID); } // 退出场景1 void lv_wms_scene_exit_scene1(void) { lv_wms_scene_exit_cur_win(); }
-
窗口管理头文件:wms_win_manager.h
#ifndef __WMS_WIN_MANAGER_H__ #define __WMS_WIN_MANAGER_H__ void lv_wms_scene_init(void); void lv_wms_scene_enter_scene1(void); void lv_wms_scene_exit_scene1(void); #endif
WMS 启动*
在LVGL初始化完成后,调用lv_wms_scene_init()初始化即可进入WMS的主窗口。