• 嵌入式UI框架 LVGL 学习笔记 02 页面管理和主题定制


    LVGL页面切换

    LVGL中没有明确的页面切换方案,参考(2.6 #lvgl-多页面(screen)设定/切换)根据实际可用API,可以考虑两种方案:

    • 通过FLAG(LV_OBJ_FLAG_HIDDEN)隐藏或者显示lv_obj_add_flag/lv_obj_clear_flag根UI控件
    • 通过管理child节点的对象来实现lv_obj_set_parent/lv_obj_get_child
    • 通过屏幕显示obj切换 lv_scr_load/lv_scr_load_anim

    与参考文章不同的是,这里更建议使用1和2两种方式进行管理。具体场景如下:

    • 如果是简单页面,没有标题,页脚,侧边栏等复杂元素,建议使用3。
      优点:简单,可以自带动画。缺点:必须整个屏幕切换,重用性比较低。
    • 如果有复杂层次关系,即简单页面之外的需求,有两种选择:
      1 将所有同类别页面添加在同一个root点下,然后使用FLAG隐藏所有子页面,只显示需要的页面。优点:快速,层次关系明确。缺点:内存占用略高。(推荐这种)
      2 将所有同类别页面放在同一个数组下,每次设置子页面的parent为null,将需要显示的页面parent设置为root。优点:tree关系简单,内容占用可以优化。缺点:使用、管理节点麻烦。

    lv_win实现了一个上下结构的基础windows框架,可以在此基础上进行扩充使用。(存在全局参数限制,参考使用)

    一个页面切换的Demo实例(布局参考lv_win):

    #include "demo1.h"
    #include "lvgl/lvgl.h"
    
    lv_obj_t* gApp = NULL;
    lv_style_t btnRectStyle;
    
    lv_obj_t* nav_btns[3];
    lv_obj_t* nav_contents[3];
    
    void init_style() {
        lv_style_init(&btnRectStyle);
        lv_style_set_radius(&btnRectStyle, 0); // LV_RADIUS_CIRCLE
        lv_style_set_border_width(&btnRectStyle, 0);
        lv_style_set_shadow_width(&btnRectStyle, 0);
        lv_style_set_pad_all(&btnRectStyle, 0);
        lv_style_set_outline_width(&btnRectStyle, 0);
    }
    
    void nav_btns_cb(lv_event_t* e) {
        int idx = (int)lv_event_get_user_data(e);
        if ( idx>=0 && idx<3 ) {
            for (int i = 0; i < 3; i++) {
                lv_obj_add_flag(nav_contents[i], LV_OBJ_FLAG_HIDDEN);
            }
            // 显示对应的content
            lv_obj_clear_flag(nav_contents[idx], LV_OBJ_FLAG_HIDDEN);
        }
    }
    
    void init_app_home(lv_obj_t* pRoot) {    
        lv_obj_set_flex_flow(pRoot, LV_FLEX_FLOW_COLUMN);
    
        lv_obj_t* pUIHeader = lv_obj_create(pRoot);
        lv_obj_add_style(pUIHeader, &btnRectStyle, 0);
        lv_obj_set_size(pUIHeader, LV_PCT(100), 28);
        lv_obj_set_style_bg_color(pUIHeader, lv_color_hex(0x0D24F2), 0);
    
        lv_obj_t* pUIContent = lv_obj_create(pRoot);
        lv_obj_add_style(pUIContent, &btnRectStyle, 0);
        lv_obj_set_width(pUIContent, LV_PCT(100));
        lv_obj_set_flex_grow(pUIContent, 1);
        //lv_obj_set_style_bg_color(pUIContent, lv_color_hex(0xCCCCCC), 0);
        lv_obj_set_flex_flow(pUIContent, LV_FLEX_FLOW_ROW);
    
        // switch pages
        lv_obj_t* home_left = lv_obj_create(pUIContent);    
        lv_obj_set_size(home_left, 120, LV_PCT(100));
    
        lv_obj_t* home_right = lv_obj_create(pUIContent);
        lv_obj_add_style(home_left, &btnRectStyle, 0);
        lv_obj_set_size(home_right, 600, LV_PCT(100));
        lv_obj_set_flex_grow(home_right, 1);
    
        // 1. nav btns
        for (int i = 0; i < 3; i++) {
            nav_btns[i] = lv_btn_create(home_left);
            //lv_obj_set_style_bg_color(nav_btns[i], lv_color_make(0,0,200), 0);
            lv_label_set_text_fmt(lv_label_create(nav_btns[i]), "Btn-%d", i);
            lv_obj_set_size(nav_btns[i], 70, 30);
            lv_obj_set_pos(nav_btns[i], 5, 40*i+10);
            lv_obj_add_event_cb(nav_btns[i], nav_btns_cb, LV_EVENT_PRESSED, (void*)i);
        }
        // 2. content
        for (int i = 0; i < 3; i++) {
            nav_contents[i] = lv_label_create(home_right);
            lv_obj_set_size(nav_contents[i], 600, 400);
            lv_obj_set_pos(nav_contents[i], 0, 0);
            lv_label_set_text_fmt(nav_contents[i], "nav content: %d", i);
            lv_obj_add_flag(nav_contents[i], LV_OBJ_FLAG_HIDDEN);
        }
    
        // triger btn-0
        lv_event_send(nav_btns[0], LV_EVENT_PRESSED, 0);
    }
    
    // 入口函数
    void main_loop(void) {
        init_style();
        lv_obj_t* pScreen = lv_scr_act();
        gApp = lv_obj_create(pScreen);
    
        lv_obj_remove_style_all(gApp);
        lv_obj_set_size(gApp, LV_PCT(100), LV_PCT(100));
        init_app_home(gApp);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86

    全局主题定制

    LVGL全局默认主题虽然很不错,但是带有太多的圆角等元素,为了使用方便,尝试定义一个自己的主题。

    参考link:

    • https://forum.lvgl.io/t/how-to-create-custom-themes-in-lvgl-v8/7591
    • https://docs.lvgl.io/master/overview/style.html#extending-themes
    • https://docs.lvgl.io/master/overview/style.html#extending-the-current-theme

    下面是定制theme的demo。它相当于hook了原来的主题,对所有button添加了新的style_btn。
    V8版本没有一个办法能从头完全定制一个theme,需要在原来的theme上进行扩展/修改。
    不同的screen可以有不同的theme。

    static lv_style_t style_btn;
    
    /*Will be called when the styles of the base theme are already added
      to add new styles*/
    static void new_theme_apply_cb(lv_theme_t* th, lv_obj_t* obj)
    {
        LV_UNUSED(th);
    
        if (lv_obj_check_type(obj, &lv_btn_class)) {
            lv_obj_add_style(obj, &style_btn, 0);
        }
    }
    
    static void new_theme_init_and_set(void)
    {
        /*Initialize the styles*/
        lv_style_init(&style_btn);
        lv_style_set_bg_color(&style_btn, lv_palette_main(LV_PALETTE_GREEN));
        lv_style_set_border_color(&style_btn, lv_palette_darken(LV_PALETTE_GREEN, 3));
        lv_style_set_border_width(&style_btn, 3);
    
        /*Initialize the new theme from the current theme*/
        lv_theme_t* th_act = lv_disp_get_theme(NULL);
        static lv_theme_t th_new;
        th_new = *th_act;
    
        /*Set the parent theme and the style apply callback for the new theme*/
        lv_theme_set_parent(&th_new, th_act);
        lv_theme_set_apply_cb(&th_new, new_theme_apply_cb);
    
        /*Assign the new theme the the current display*/
        lv_disp_set_theme(NULL, &th_new);
    }
    
    
    void demo_my_test_1018(void)
    {
        lv_obj_t* btn;
        lv_obj_t* label;
    
        btn = lv_btn_create(lv_scr_act());
        lv_obj_align(btn, LV_ALIGN_TOP_MID, 0, 20);
    
        label = lv_label_create(btn);
        lv_label_set_text(label, "Original theme");
    
        new_theme_init_and_set();
    
        btn = lv_btn_create(lv_scr_act());
        lv_obj_align(btn, LV_ALIGN_BOTTOM_MID, 0, -20);
    
        label = lv_label_create(btn);
        lv_label_set_text(label, "New theme");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    默认的主题加载位置lv_disp_drv_register

    #if LV_USE_THEME_DEFAULT
        if(lv_theme_default_is_inited() == false) {
            disp->theme = lv_theme_default_init(disp, lv_palette_main(LV_PALETTE_BLUE), lv_palette_main(LV_PALETTE_RED),
                                                LV_THEME_DEFAULT_DARK, LV_FONT_DEFAULT);
        }
        else {
            disp->theme = lv_theme_default_get();
        }
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    总结:

    • 主题定制走的是hook的路子,通过add style进行修改。
    • 修改默认使用的主题,参考lv_disp_drv_register函数,通过disp的theme修改。
    • 官方建议在basic的基础上修改,default内容太多。lvgl/src/extra/themes/basic,default,mono

    心得总结

    • LVGL只适用页面不太多的场合,复杂UI项目尽可能考虑Linux下QT/GTK开发。
    • 页面管理,风格美化,字体图片资源,中文输入法等在正式项目中需要慎重设计和考虑。
    • UI初版可借助GUI工具 GUI Guider 或者 SquareLine Studio
    • LVGL代码缺少很多注释,文档也比较简略,论坛能解决问题有限。建议遇到问题时,多翻翻src文件夹,参看类似UI的实现。

    有一个比较火的开源项目参考: 开源GPS自行车码表 X-TRACK

    PS: 后续有机会了做一个基于STM32F4的综合Demo。

  • 相关阅读:
    软件设计师 计算机系统基础知识
    [附源码]Python计算机毕业设计Django教务管理系统
    Linux学习记录——이 基本指令(2)
    成集云 | 飞书审批同步金蝶云星空销售订单 | 解决方案
    【面试专栏】第四篇:Java基础:集合篇-Map、HashMap、Hashtable
    Oracle自治事务示例演示
    力扣:101. 对称二叉树(Python3)
    【C语言刷LeetCode】658. 找到 K 个最接近的元素(M)
    服装行业在快手打广告效果好吗?如何在短视频平台推广服装?
    基于YOLOv8深度学习的智能道路裂缝检测与分析系统【python源码+Pyqt5界面+数据集+训练代码】深度学习实战、目标检测、目标分割
  • 原文地址:https://blog.csdn.net/bbdxf/article/details/127753605