littlevgl_7.11源码分析(3)--Apple的学习笔记

一,前言

之前是从一个将obj放置到最前面的API开始的分析,从设置无效区域后发task调度信号,到最后执行重绘渲染及更新显示的整个过程。但是我记得里面有lv_refr_obj_and_children函数是关于渲染重绘的,我没有展开,因为里面进入lv_obj_design后都是style和mask相关的内容,基本上可以理解为这是渲染重绘的像素重组的功能。但是这些内容应该在初始化的时候设置过,然后再次应用。所以我有必要从头开始分析下。我选择了创建一个button开始分析源码。

二,lv_btn_create

关于对象大小和位置的初始化
按官网教程创建btn后需要设置位置,设置大小,设置回调函数等。而我改成了最简单的,只创建btn,调用btn = lv_btn_create(lv_scr_act(),NULL);想看看是否会出现图像。结果真的有一个按钮显示出来,位置在0,0处,说明初始化的时候就设置了默认值,而关于大小的默认值不是0,所以才能显示出来。lv_btn_create->lv_cont_create->lv_obj_create,其中lv_obj_create函数很重要。里面创建了new_obj分配了内存,然后就开始使用初始化值对new_obj进行填充了。

        new_obj = _lv_ll_ins_head(&parent->child_ll);
        LV_ASSERT_MEM(new_obj);
        if(new_obj == NULL) return NULL;

        _lv_memset_00(new_obj, sizeof(lv_obj_t));

刚刚说的对象大小就是在此函数中填充的,宽度和高度都和宏定义LV_OBJ_DEF_WIDTH有关,而它又和lv_conf.h中的LV_DPI有关。此宏定义的含义是每个inch要占用显示屏多少的像素值。

        new_obj->coords.y1    = parent->coords.y1;
        new_obj->coords.y2    = parent->coords.y1 + LV_OBJ_DEF_HEIGHT;
        if(lv_obj_get_base_dir(new_obj) == LV_BIDI_DIR_RTL) {
            new_obj->coords.x2    = parent->coords.x2;
            new_obj->coords.x1    = parent->coords.x2 - LV_OBJ_DEF_WIDTH;
        }
        else {
            new_obj->coords.x1    = parent->coords.x1;
            new_obj->coords.x2    = parent->coords.x1 + LV_OBJ_DEF_WIDTH;
        }

lv_obj.c的代码里面默认给对象的初始化宽度为100,高度为50。不仅仅是btn,其它obj的创建都是这个值。但是这个比例是核心代码中的,不是留给用户配置的,当然我现在看懂代码,知道含义了,想怎么改都行。哈哈,通过看源码破解了奥秘。

#define LV_OBJ_DEF_WIDTH    (LV_DPX(100))
#define LV_OBJ_DEF_HEIGHT   (LV_DPX(50))

样式初始化
刚刚看了关键的位置及大小有初始化,其实默认的按钮它是蓝色的圆角框,而且可以click点击,但是不能被拖动。这些click和draw属性在lv_obj_create函数中都能看到其初始化值。那么接下来我最关心的就是样式的应用,因为图片好看不好看就是要看code中是如何设置风格化的了。它这里的风格设置,用了theme主题,应用不同的主题,则显示出来的外形则不同。对于使用windows桌面采用不同的主题则界面效果不同,lvgl也是延续了这样的思路。我慢好奇的,这个具体实现机制是怎么样的呢!

    lv_style_list_init(&new_obj->style_list);
    if(copy == NULL) {
        /* 若无传入copy对象,并且没有parent则风格化参考LV_THEME_OBJ,否则参考LV_THEME_SCR */
        if(parent != NULL) lv_theme_apply(new_obj, LV_THEME_OBJ);
        else lv_theme_apply(new_obj, LV_THEME_SCR);
    }
    else {
        /* 若有传入copy对象,参考其风格进行样式格式化 */
        lv_style_list_copy(&new_obj->style_list, ©->style_list);
    }

好了,那么就来分析lv_theme_apply函数,简单来很容易理解,先删除之前的主题,再使用新的主题。这就是change的操作方法。

void lv_theme_apply(lv_obj_t * obj, lv_theme_style_t name)
{
    /* Remove the existing styles from all part of the object. */
    clear_styles(obj, name);

    /*Apply the theme including the base theme(s)*/

    apply_theme(act_theme, obj, name);
}

继续先看clear_styles,这里面好多case语句,写的不雅观,建议改成数组查表调用,数组的每一行也可以添加宏定义。

static void clear_styles(lv_obj_t * obj, lv_theme_style_t name)
{
    switch(name) {
        case LV_THEME_NONE:
            break;

        case LV_THEME_SCR:
            lv_obj_clean_style_list(obj, LV_OBJ_PART_MAIN);
            break;
        case LV_THEME_OBJ:
            lv_obj_clean_style_list(obj, LV_OBJ_PART_MAIN);
            break;
#if LV_USE_CONT
        case LV_THEME_CONT:
            lv_obj_clean_style_list(obj, LV_OBJ_PART_MAIN);
            break;
#endif

#if LV_USE_BTN
        case LV_THEME_BTN:
            lv_obj_clean_style_list(obj, LV_BTN_PART_MAIN);
            break;
#endif
。。。。。。

由于我那一句按钮初始化,其实传入的是参数是LV_THEME_OBJ,所以会调用lv_obj_clean_style_list(obj, LV_OBJ_PART_MAIN);,其实里面就是获取obj中style_list的地址,然后调用_lv_style_list_reset(style_dsc);后面还是动画功能的相关处理我先不管。
真正的清除原先的style功能就在下面的代码中实现。但是我不理解的有2个成员,1个是lv_style_list_t中的trans和local,是什么意思,另外就是清除lv_style_list_t只要清除如下几个属性就可以了吗?lv_style_list_t中其它成员不需要清除吗?

void _lv_style_list_reset(lv_style_list_t * list)
{
    LV_ASSERT_STYLE_LIST(list);

    if(list == NULL) return;

    if(list->has_local) {
        lv_style_t * local = lv_style_list_get_local_style(list);
        if(local) {
            lv_style_reset(local);
            lv_mem_free(local);
        }
    }

    if(list->has_trans) {
        lv_style_t * trans = _lv_style_list_get_transition_style(list);
        if(trans) {
            lv_style_reset(trans);
            lv_mem_free(trans);
        }
    }

    if(list->style_cnt > 0) lv_mem_free(list->style_list);
    list->style_list = NULL;
    list->style_cnt = 0;
    list->has_local = 0;
    list->has_trans = 0;
    list->skip_trans = 0;

    /* Intentionally leave `ignore_trans` as it is,
     * because it's independent from the styles in the list*/
}

主题样式初始化
我先带着问题继续看应用新的主题,说不定在应用新的主题的时候,其它值都会被重新设置一遍,那么刚刚的clean中就没必要清0了。

static void apply_theme(lv_theme_t * th, lv_obj_t * obj, lv_theme_style_t name)
{
    if(th->base) {
        apply_theme(th->base, obj, name);
    }

    /*apply_xcb is deprecated, use apply_cb instead*/
    if(th->apply_xcb) {
        th->apply_xcb(obj, name);
    }
    else if(th->apply_cb) {
        th->apply_cb(act_theme, obj, name);
    }
}

th->apply_cb其实是theme_apply函数,它是在lv_theme_material_init函数中初始化的回调函数。lv_config.h配置中设置了#define LV_THEME_DEFAULT_INIT lv_theme_material_initlv_init函数中对调用。

    lv_theme_t * th = LV_THEME_DEFAULT_INIT(LV_THEME_DEFAULT_COLOR_PRIMARY, LV_THEME_DEFAULT_COLOR_SECONDARY,
                                            LV_THEME_DEFAULT_FLAG,
                                            LV_THEME_DEFAULT_FONT_SMALL, LV_THEME_DEFAULT_FONT_NORMAL, LV_THEME_DEFAULT_FONT_SUBTITLE, LV_THEME_DEFAULT_FONT_TITLE);

theme_styles_t结构体对象中的成员蛮有意思的,除了前7个固定的,其它都可以配置,根据显示需要的元素进行配置,不同的元素都有自己特色的风格成员,例如日历, 则有日期等,例如arc就有旋钮特色成员。另外一个关键的就是这些成员的类似定义为lv_style_t,要有一个类型能满足所有成员,我猜测就是void *,因为可以随意绑定对象类型,结果查看了下是uint8 *差不多啦~

typedef struct {
    lv_style_t scr;
    lv_style_t bg;
    lv_style_t bg_click;
    lv_style_t bg_sec;
    lv_style_t btn;
    lv_style_t pad_inner;
    lv_style_t pad_small;

#if LV_USE_ARC
    lv_style_t arc_indic;
    lv_style_t arc_bg;
    lv_style_t arc_knob;
#endif

#if LV_USE_BAR
    lv_style_t bar_bg;
    lv_style_t bar_indic;
#endif

#if LV_USE_CALENDAR
    lv_style_t calendar_date_nums, calendar_header, calendar_daynames;
#endif
。。。。。。

typedef struct {
    uint8_t * map;
} lv_style_t;

然后看看lv_theme_material_init中除了设置theme.apply_cb回调函数,也包括basic_init等函数,分析下。

    basic_init();
    cont_init();
    btn_init();
    label_init();
    bar_init();
    img_init();
    line_init();
    led_init();
    ......
static void basic_init(void)
{
    lv_style_reset(&styles->scr);
    lv_style_set_bg_opa(&styles->scr, LV_STATE_DEFAULT, LV_OPA_COVER);
    lv_style_set_bg_color(&styles->scr, LV_STATE_DEFAULT, COLOR_SCR);
    lv_style_set_text_color(&styles->scr, LV_STATE_DEFAULT, COLOR_SCR_TEXT);
    lv_style_set_value_color(&styles->scr, LV_STATE_DEFAULT, COLOR_SCR_TEXT);
    lv_style_set_text_sel_color(&styles->scr, LV_STATE_DEFAULT, COLOR_SCR_TEXT);
    lv_style_set_text_sel_bg_color(&styles->scr, LV_STATE_DEFAULT, theme.color_primary);
    lv_style_set_value_font(&styles->scr, LV_STATE_DEFAULT, theme.font_normal);

lv_style_set_bg_opa我一开始没有搜索到。原来是_LV_OBJ_STYLE_SET_GET_DECLARE(BG_OPA, bg_opa, lv_opa_t, _opa)的宏定义展开的inline函数,在h文件中。这里_LV_OBJ_STYLE_SET_GET_DECLARE里面又进行了合并同类项,抽象化。说白了就是定了3个格式类似的函数。
这里我又学习到了一招,为了少复制黏贴code,提高工作效率,则采用此宏定义+不同字符的方法来定义函数,其实预编译的时候这些宏定义都会进行函数展开的。

#define _OBJ_GET_STYLE(prop_name, func_name, value_type, style_type)                                \
    static inline value_type lv_obj_get_style_##func_name(const lv_obj_t * obj, uint8_t part)       \
    {                                                                                               \
        return _lv_obj_get_style##style_type(obj, part, LV_STYLE_##prop_name);                      \
    }
#endif

#define _OBJ_SET_STYLE_LOCAL(prop_name, func_name, value_type, style_type)                                                      \
    static inline void lv_obj_set_style_local_##func_name(lv_obj_t * obj, uint8_t part, lv_state_t state, value_type value)     \
    {                                                                                                                           \
        _lv_obj_set_style_local##style_type(obj, part, LV_STYLE_##prop_name | (state << LV_STYLE_STATE_POS), value);            \
    }

#define _OBJ_SET_STYLE(prop_name, func_name, value_type, style_type)                                                     \
    static inline void lv_style_set_##func_name(lv_style_t * style, lv_state_t state, value_type value)                  \
    {                                                                                                                    \
        _lv_style_set##style_type(style, LV_STYLE_##prop_name | (state << LV_STYLE_STATE_POS), value);                   \
    }

#define _LV_OBJ_STYLE_SET_GET_DECLARE(prop_name, func_name, value_type, style_type)                                      \
    _OBJ_GET_STYLE(prop_name, func_name, value_type, style_type)                                                         \
    _OBJ_SET_STYLE_LOCAL(prop_name, func_name, value_type, style_type)                                                   \
    _OBJ_SET_STYLE(prop_name, func_name, value_type, style_type)

style->map的初始化
所以lv_style_set_bg_opa函数最后会调用_lv_style_set_opa函数。这个函数写的蛮有意思的。它需要为lv_style_t中的uint8* map来赋值,并且还能保证能找到不同长度的style成员。第一次设置style的话id为0,通过最后3个memcpy可以看出map中要保存3个成员属性+透明度+结束符,抽象理解就是属性+属性值+结束符。而里面的new_prop_size,end_mark_size和总size的计算很容易理解,就是每个成员的长度,这些size就是在3次memcpy中用的。style_resize可以理解为通过malloc重新*map分配空间。然后在style->map地址开始装数据。装载顺序入下图。

image.png

void _lv_style_set_opa(lv_style_t * style, lv_style_property_t prop, lv_opa_t opa)
{
    int32_t id = get_property_index(style, prop);
    /*The property already exists but not sure it's state is the same*/
    if(id >= 0) {
        lv_style_attr_t attr_found;
        lv_style_attr_t attr_goal;

        attr_found = get_style_prop_attr(style, id);
        attr_goal = (prop >> 8) & 0xFFU;
        /* 若找到属性,则重置属性值 */
        if(LV_STYLE_ATTR_GET_STATE(attr_found) == LV_STYLE_ATTR_GET_STATE(attr_goal)) {
            _lv_memcpy_small(style->map + id + sizeof(lv_style_property_t), &opa, sizeof(lv_opa_t));
            return;
        }
    }

    /*Add new property if not exists yet*/
    uint8_t new_prop_size = sizeof(lv_style_property_t) + sizeof(lv_opa_t);
    lv_style_property_t end_mark = _LV_STYLE_CLOSING_PROP;
    uint8_t end_mark_size = sizeof(end_mark);

    uint16_t size = _lv_style_get_mem_size(style);
    if(size == 0) size += end_mark_size;

    size += new_prop_size;
    if(!style_resize(style, size)) return;

    _lv_memcpy_small(style->map + size - new_prop_size - end_mark_size, &prop, sizeof(lv_style_property_t));
    _lv_memcpy_small(style->map + size - sizeof(lv_opa_t) - end_mark_size, &opa, sizeof(lv_opa_t));
    _lv_memcpy_small(style->map + size - end_mark_size, &end_mark, sizeof(end_mark));
}

现在我算是学习到了,结构不同的打包放入指针,通过成员的类型长度也可以找到不同的成员。这个感觉和protobuf原理有些类似,反正就是解析数据一类的。上面代码中的if的功能就是若找到属性,则重置属性值,直接return退出,否则要走下面的添加属性及属性值的步骤。
然后看看成员不同格式的组合是如何进行解析查找的。get_property_index函数,我只保留了结构。

    uint8_t prop_id;
    while((prop_id = get_style_prop_id(style, i)) != _LV_STYLE_CLOSING_PROP) {
        if(prop_id == id_to_find) {
            id_guess = ......
        }

        i = get_next_prop_index(prop_id, i);
    }
    return id_guess;

get_style_prop_id会调用如下关键语句,这容易理解,获取第i个熟悉,然后返回属性值。

    lv_style_property_t prop;
    _lv_memcpy_small(&prop, &style->map[idx], sizeof(lv_style_property_t));
    return prop;

若属性和当前查找的不同,则需要获取下一个属性,上面的截图我已经分析过了1个字节属性+2个字节属性值+结尾符。那么我要找下一个属性应该加几个字节应该知道了吧!lvgl设计的更加好,因为它是3类属性和值混合存放的,当然属性类别的长度是固定的,而属性值的长度根据属性类别而不同。所以不是我说的固定值,而是在get_prop_size函数中可以看出会增加不同的size。

static inline size_t get_next_prop_index(uint8_t prop_id, size_t idx)
{
    return idx + get_prop_size(prop_id);
}
static inline size_t get_prop_size(uint8_t prop_id)
{
    prop_id &= 0xF;
    size_t size = sizeof(lv_style_property_t);
    if(prop_id < LV_STYLE_ID_COLOR) size += sizeof(lv_style_int_t);
    else if(prop_id < LV_STYLE_ID_OPA) size += sizeof(lv_color_t);
    else if(prop_id < LV_STYLE_ID_PTR) size += sizeof(lv_opa_t);
    else size += sizeof(const void *);
    return size;
}

三,小结

今天从按钮的创建开始,重点分析了主题样式的初始化,毕竟做引擎的目的就是减少修改,而换皮技术就是一个很灵活的GUI引擎必备的功能,这里分析了利用属性类+属性值的混合属性保存及解析方法。真的感觉像是我以前做的tcp报文数据解析类似,这样的设计有意思,但是它为什么要这样设计,而不是每种属性类和值都做成独立的结构体对象呢?我想了下,估计是为了使用的时候更便利,可扩展性更强,并且我们设计代码不就是要抽象嘛!所以它已经做的很抽象了。唯一我觉得不好的就是查询算法不太好。

你可能感兴趣的:(littlevgl_7.11源码分析(3)--Apple的学习笔记)