使用EEZ Studio移植lvgl项目到stm32-使用EEZ作为辅助完成lvgl项目的思路,以及部分问题的解决思路(三)

一、如何用“公式化”的想法来编写部件代码

作为一个图形化界面,为了一个简洁美观的界面,肯定要使用到很多的部件,但是想要着手配置部件的样式、事件、状态感觉会很繁杂,突发的灵感也会被这些扰乱。

从百问网lvgl中文手册和代码里我们可以看到,lvgl的版本很多,在工作和学习中,无法保证都是用同一个版本,很糟糕的是,lvgl的函数代码经常会改变,这也会导致一个问题,你在codeblock里是可以正常跑的,但是一到硬件上就会白屏,以下是我在上期评论中指出的问题。

(最近移植lvgl过程中遇到的问题:passing argument to parameter 'style' here翻了翻官方文档发现是使用了“过时”的代码,旧版本的代码在新版本竟然并不兼容,这有点坑,比如lv_obj_set_style_border_width是最新的代码,而lv_style_set_border_width这个旧代码如果放入你的文件就可能会导致白屏)

但是这不成问题,因为EEZ Studio内置了8.x和9.x版本,但是我们只将这个作为一个辅助工具,不管是SquareLine 还是EEZ都只是工具,那么怎么确保就算版本换了,平台换了我们也能游刃有余的来完成项目呢。

“公式化” (头-身子-腿)

在遇到一个不熟悉的部件时,本人第一步是先去AI生成一个大体的框架,但是请不要使用AI的代码,他们很多函数都是莫须有的,在以前我可能会一直去让AI去生成那个莫须有的函数,这也会导致代码冗杂,别说防御式编程,连自己都防的无懈可击了(笑)

所以作为公式化的第一步,要看框架,也可以去手册里看示例代码,但是看到框架压根很难看到要往里填什么,毕竟拿着一块拼图肯定是不知道完整图片长什么样子的。

这里给出一个消息框部件

不必在意细节代码,在部件创建时有一段代码,可能会导致问题,不过这会在后面讨论。

首先创建部件第一步肯定是lvgl的部件对象,在EEZ的代码里也是可以配置主体,这部分很简单,不在这里赘述。

 {
            lv_obj_t *obj = lv_msgbox_create(parent_obj, "", "", 0, true);
            lv_obj_set_pos(obj, 254, 116);
            lv_obj_set_size(obj, 304, 165);
            lv_obj_set_style_align(obj, LV_ALIGN_DEFAULT, LV_PART_MAIN | LV_STATE_DEFAULT);
            lv_obj_set_style_bg_color(obj, lv_color_hex(0xfff8f8f8), LV_PART_MAIN | LV_STATE_DEFAULT);
            lv_obj_set_style_bg_opa(obj, 120, LV_PART_MAIN | LV_STATE_DEFAULT);
 }

身子

有没有发现一个问题,你确实配置了主体,但是其他的东西什么都没动

回到EEZ里面发现,怎么没有配置其他部分样式的地方,只有在什么状态下,配置对应的样式(也有可能本人没有察觉到哪个地方可以配置= =|||,但不能每次都来回找,这也是我希望只用这些平台作为工具,后续如果开发者不再维护也不会对使用lvgl或者类似的图形界面造成影响)

正点原子的教程里,这些都是部件的组成部分,不过不用慌,下面的东西其实都是一类的。

其实创建部件都是“拼凑”出来的,想要创建部件只需要不断地创建(获取)再设置

/* 消息框标题 */
    lv_obj_t *title = lv_msgbox_get_title(msgbox);                                           /* 获取标题部分 */
    lv_obj_set_style_text_font(title, &lv_font_montserrat_20, LV_STATE_DEFAULT);             /* 设置字体 */
    lv_obj_set_style_text_color(title, lv_color_hex(0xff0000),LV_STATE_DEFAULT);             /* 设置文本颜色:红色 */

其实样式的配置都是大差不差的,每个部件间都存在细微的差别,有特定的函数来获取或者配置状态,所以即使有共性的东西,还请多看看帮助手册,知共性看个性。

单独的部件很难有很好的视觉效果,所以需要事件和状态等等需要配置初始状态的部分或者后续你想要与其他部件联动。

 

整体看下来每个部件配置很明确,头-身子-腿,在EEZ的辅助下”头“很好配置,如果你使用flow,你可以对这三部分都能配置,但这就需要单独对这个软件很熟练,不过我个人还是更倾向于掌握图形界面,而不仅仅只是lvgl。

在创建部件的时候,“公式化”对我个人还是挺好用的,至少改动的时候不会因为代码乱糟糟的心烦,说是公式化,其实就是写代码时习惯,防御式编程至少不防自己。·(>-<)·\\\<——

"公式化"前提

想要配置部件的时候请多多参考手册和一些商家提供的手册

 

二、动画效果的实现

为了实现动画效果,步骤也是创建对象,设置参数,以下代码只是示例,可省略

其实动画大体下来和部件相比没太大差别,比如在按键触发时只需要加入启动动画或是关闭动画即可

/动画函数//
/* 动画执行的回调函数 */
void rotate_anim(void * obj, int32_t  value) {
    lv_img_set_angle(obj,value);

}

void create_rotate_anim_CD(lv_obj_t * obj, bool start) {
    static lv_anim_t a; // 使用静态变量以保留动画状态
    static int32_t current_angle = 0; // 记录当前旋转角度

    lv_anim_init(&a);
    /* 设置动画的执行回调函数 */
    lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t) lv_img_set_angle); // 确保类型匹配

    /* 设置动画的目标对象 */
    lv_anim_set_var(&a, obj);

    /* 如果 start 参数为 true,则启动动画;否则,停止动画 */
    if(start) {
        /* 设置动画的起始值和结束值 */
        lv_anim_set_values(&a, current_angle, current_angle + 3600); // 从记录的角度开始,旋转一圈

        /* 设置动画的持续时间 */
        lv_anim_set_time(&a, 5000); // 动画持续时间5000毫秒,即5秒

        /* 设置动画的路径为默认线性 */
        lv_anim_set_path_cb(&a, lv_anim_path_linear);

        /* 设置动画重复无限次 */
        lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE);

        /* 启动动画 */
        lv_anim_start(&a);
    } else {
        // 停止动画
        lv_anim_del(obj, NULL); // 删除与对象关联的动画
        // 获取动画时间轴的总持续时间
        uint32_t playtime ;
        // 记录当前角度
        current_angle = 0.72*playtime;
    }
}

void create_rotate_anim_CD_lever_start(lv_obj_t * obj,bool start) {
    /* 初始化动画结构体 */
    lv_anim_t a;
    lv_anim_init(&a);

    /* 设置动画的执行回调函数 */
    lv_anim_set_exec_cb(&a, lv_img_set_angle);

    /* 设置动画的目标对象 */
    lv_anim_set_var(&a, obj);

    /* 设置动画的起始值和结束值 */
    lv_anim_set_values(&a, -480, 0); /* 从0度旋转到3600度,即一圈 */

    /* 设置动画的持续时间 */
    lv_anim_set_time(&a, 500); /* 动画持续时间5000毫秒,即5秒 */

    /* 设置动画的路径为默认线性 */
    lv_anim_set_path_cb(&a, lv_anim_path_linear);

    /* 设置动画重复无限次 */
    lv_anim_set_repeat_count(&a, 1);

/* 如果 start 参数为 true,则启动动画 */
    if(start) {
        lv_anim_start(&a);
    }
}

以下是动画路径的部分函数,和换屏动画类似,因为一般需求都是简单动画,所以使用AI直接生成框架往里头改动画效果参数我觉得是最高效的,而部件间的联动效果才是最该关注的

1 lv_anim_path_linear
功能 线性路径动画值从起始值线性变化到结束值
特点 值的变化是均匀的没有加速或减速效果
适用场景 适用于需要匀速变化的动画如简单的移动缩放等

 

2 lv_anim_path_ease_in
功能 缓入路径动画开始时变化较慢然后逐渐加速
特点 动画开始时较慢结束时较快
适用场景 适用于需要逐渐加速的动画效果如物体从静止开始移动

 

3 lv_anim_path_ease_out
功能 缓出路径动画开始时变化较快然后逐渐减速
特点 动画开始时较快结束时较慢
适用场景 适用于需要逐渐减速的动画效果如物体逐渐停止移动

 

4 lv_anim_path_ease_in_out
功能 缓入缓出路径动画开始时变化较慢中间加速结束时又逐渐减速
特点 动画开始和结束时较慢中间较快
适用场景 适用于需要平滑加速和减速的动画效果如物体的自然移动

 

5 lv_anim_path_overshoot
功能 超调路径动画值会超过结束值然后再回到结束值
特点 动画结束时会有轻微的反弹效果
适用场景 适用于需要强调动画结束的效果如按钮点击后的反弹效果

 

6 lv_anim_path_bounce
功能 弹跳路径动画值在结束时会有多次弹跳效果
特点 动画结束时会有多次反弹类似于小球落地后的弹跳
适用场景 适用于需要弹跳效果的动画如物体落地后的弹跳

 

7 lv_anim_path_step
功能 步进路径动画值在结束时直接跳到结束值
特点 动画值在动画结束时突然变化到结束值没有过渡效果
适用场景 适用于需要突然变化的动画效果如开关的切换

 

8 lv_anim_path_custom
功能 自定义路径允许用户定义自己的动画路径函数
特点 用户可以通过自定义函数实现任意复杂的动画路径
适用场景 适用于需要特殊动画效果的场景用户可以根据需求自定义路径

三、在项目中可能遇到的问题

这个问题其实卡了我很久,lvgl作为一个轻量化的图形界面,那么肯定少不了各个部件间的联动。

以前当我想到,如果按下一个按键的时候,其他部件的样式会不会改变,我上手试了试却无法完成。

拖了很久都没有解决,在翻阅手册和别人的代码终于发现了问题。

请看下面两个创建部件的代码

{
            stop_imgbtn = lv_imgbtn_create(parent_obj);
            lv_obj_set_pos(stop_imgbtn, 379, 420);
            lv_obj_set_size(stop_imgbtn, LV_SIZE_CONTENT, 25);
            lv_imgbtn_set_src(stop_imgbtn, LV_IMGBTN_STATE_RELEASED, NULL, &img_stop, NULL);
            lv_imgbtn_set_src(stop_imgbtn, LV_IMGBTN_STATE_PRESSED, NULL, &img_play, NULL);
            lv_imgbtn_set_src(stop_imgbtn, LV_IMGBTN_STATE_CHECKED_PRESSED, NULL, &img_stop, NULL);
            lv_imgbtn_set_src(stop_imgbtn, LV_IMGBTN_STATE_CHECKED_RELEASED, NULL, &img_play, NULL);
            lv_obj_add_flag(stop_imgbtn, LV_OBJ_FLAG_CHECKABLE);
//            lv_obj_clear_flag(stop_imgbtn, LV_OBJ_FLAG_CLICKABLE);
            lv_imgbtn_set_state(stop_imgbtn, LV_IMGBTN_STATE_PRESSED);
            lv_obj_add_event_cb(stop_imgbtn, STOPbtn_event_handler, LV_EVENT_VALUE_CHANGED, NULL);
        }
{
            lv_obj_t *obj = lv_line_create(parent_obj);
            static lv_point_t line_points[] = {
                { 0, 0 },
                { 592, 0 }
            };
            lv_line_set_points(obj, line_points, 2);
            lv_obj_set_pos(obj, 188, 396);
            lv_obj_set_size(obj, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
            lv_obj_set_style_line_width(obj, 3, LV_PART_MAIN | LV_STATE_DEFAULT);
            lv_obj_set_style_line_color(obj, lv_color_hex(0xffcdfcf4), LV_PART_MAIN | LV_STATE_DEFAULT);
        }

乍看下没有什么不对的,但是请注意一点,EEZ生成的变量都是动态的,也许刚开始学的朋友在学习到动态和静态时候觉得很懵。简单来说,动态就是随停随走,属于流动停车位(局部变量),而静态是专属的停车位,不会变动,且全部小区的人都得知道,这车位是你的(全局变量)。

说了这么多,那么这个动态和静态变量有什么用呢?在EEZ生成的代码里obj是一个动态的变量,而自己写得有名字的对象,我的建议是对于想要加入事件的部件尽量使用全局变量

在事件中,如果你想要像以下去改变其他部件的样式,那么全局变量是必不可少的。

lv_obj_t *FM_btn=NULL;//注意这个全局变量必须创建,这样才能改变下面的部件样式

void msgbox_event_cb(lv_event_t * e){

        lv_obj_t * target = lv_event_get_current_target(e);
        if(lv_msgbox_get_active_btn(target) == 0)                          /* 获取按钮索引 */
        {

            lv_obj_set_style_bg_color(FM_btn, lv_color_hex(0xffcccccc), LV_PART_MAIN | LV_STATE_DEFAULT);
            lv_obj_add_flag(msgbox, LV_OBJ_FLAG_HIDDEN);                   /* 隐藏消息框 */
        }
        if(lv_msgbox_get_active_btn(target) == 1)                          /* 获取按钮索引 */
        {
            lv_obj_add_flag(msgbox, LV_OBJ_FLAG_HIDDEN);                   /* 隐藏消息框 */
            loadScreen(SCREEN_ID_MOTOR);
        }
//        lv_msgbox_get_active_btn(target);//获取按钮索引
//        lv_msgbox_get_active_btn_text(target);


}

当你发现用模拟器或者硬件在跑着跑着的时候卡死或是崩溃,一般来说都是函数改变样式读到了一个空指针,这时候回到创建对象那里看看是否是这样的:

stop_imgbtn = lv_imgbtn_create(parent_obj);

而非lv_obj_t *obj = lv_line_create(parent_obj);

我在一些论坛有人提到来回换页会导致内存泄漏崩溃,在使用EEZ为基础的代码,本人并没有遇到这个问题,大部分遇到的都是全局调用相关的(一不留神就顺手把lv_obj_t 打上了,在一般事件处理的时候没有问题,但是想要改变样式就会崩溃)

大体目前遇到就这么多吧,动画部分参考LVGL Animations(动画)的简单使用,复杂的动画效果就需要自行制作了,在官网有更详细的介绍。

改变样式的简易例子

在按下消息框部件的Hold按钮,可以看到FM的按钮正常变灰,符合代码预期。

说实在LVGL内存还是太大,随便写了点界面烧录下载就要3分钟 //>_<\\

非常感谢看到这里,如果你喜欢,希望可以点赞收藏关注,谢谢喵!

 

作者:梦与青与浊

物联沃分享整理
物联沃-IOTWORD物联网 » 使用EEZ Studio移植lvgl项目到stm32-使用EEZ作为辅助完成lvgl项目的思路,以及部分问题的解决思路(三)

发表回复