跳转至

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的主窗口。