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

一,前言

继续分析按钮的创建及显示的过程。昨天很大的篇幅都是在研究lv_style_t结构体的初始化填充,简单理解就是主题默认样式的创建,它包括很多数据,都保存在*map指向的内存中。

二,应用主题源码分析

关于apply_theme中调用theme_apply函数,那么就很容易理解了。就是把lv_style_t加入到lv_style_list_t结构体的lv_style_t指针成员中。lv_style_list_t中style_cnt是6个bit长度的,说明这个list可以保存很多lv_style_t样式成员。最后还对调用lv_obj_refresh_style刷新主题,此函数在这里不展开,后面会分析

        case LV_THEME_BTN:
            list = lv_obj_get_style_list(obj, LV_BTN_PART_MAIN);
            _lv_style_list_add_style(list, &styles->btn);
            break;
        ......
        lv_obj_refresh_style();

然后lv_theme_apply算是分析完了,又从最开始的函数btn创建开始分析,lv_btn_create先调用创建容器,然后创建容器调用创建基类对象。这个创建对象的过程真的和c++构造子类对象一样呀!学习到了,原来还可以这样设计。然后他们个初始化对象格式雷同,都是设置回调函数,然后设置对应的属性后进行主题应用。不过我个人认为,还可以再抽象些不同的内容打包为回调函数,然后用向父对象归递的方式实现lv_btn_create。因为这些调用代码展开后看上去就像是归递函数在运行呢~

lv_obj_t * lv_btn_create(lv_obj_t * par, const lv_obj_t * copy)
{
    lv_obj_t * btn;
    // 创建容器
    btn = lv_cont_create(par, copy);
    LV_ASSERT_MEM(btn);
    if(btn == NULL) return NULL;
......
    lv_obj_set_signal_cb(btn, lv_btn_signal);
    lv_obj_set_design_cb(btn, lv_btn_design);

    /*If no copy do the basic initialization*/
    if(copy == NULL) {
        /*Set layout if the button is not a screen*/
        if(par) {
            lv_obj_set_size(btn, LV_DPI, LV_DPI / 3);
            lv_btn_set_layout(btn, LV_LAYOUT_CENTER);
        }

        lv_obj_set_click(btn, true); /*Be sure the button is clickable*/

        lv_theme_apply(btn, LV_THEME_BTN);
    }
}
lv_obj_t * lv_cont_create(lv_obj_t * par, const lv_obj_t * copy)
{
    // 创建基类对象
    lv_obj_t * cont = lv_obj_create(par, copy);
    LV_ASSERT_MEM(cont);
    if(cont == NULL) return NULL;
......
    lv_obj_set_signal_cb(cont, lv_cont_signal);

    /*Init the new container*/
    if(copy == NULL) {
        /*Set the default styles if it's not screen*/
        if(par != NULL) {
            lv_theme_apply(cont, LV_THEME_CONT);
        }

    }
}

这里比较关键的就是signal_cb回调函数,什么时候去调用呢!就是在lv_obj_refresh_style中会调用,一开始分析apply_theme就提及了此函数。用signal_cb来设置obj的一些属性,比如lv_cont_signal->lv_cont_refr_layout->lv_cont_layout_col->lv_obj_align一路就是根据style设置列的布局,最后lv_obj_align中会修改obj的x,y位置。这里面等于也说到了style的使用。包括用到了obj的ext_attr属性,ext属性是属于每个obj的特色属性。ext_attr是void *,属于万能的地址链接。比如如下,不同对象申请的空间类型都不同。

lv_btn_ext_t * ext = lv_obj_allocate_ext_attr(btn, sizeof(lv_btn_ext_t));
lv_calendar_ext_t * ext = lv_obj_allocate_ext_attr(calendar, sizeof(lv_calendar_ext_t));
lv_bar_ext_t * ext = lv_obj_allocate_ext_attr(bar, sizeof(lv_bar_ext_t));
lv_canvas_ext_t * ext = lv_obj_allocate_ext_attr(new_canvas, sizeof(lv_canvas_ext_t));

代码看到现在,基本上对lv_obj_t基类结构体中的成员含义大多数能能理解了。lv_layout_t就是个uint8的枚举,因为控件对齐功能属于常用的,所以我就记录下,注释如下。其实这个signal_cb我现在立即就是一个个小的功能函数用switch case集成在一起,要使用哪个case的小功能就用signal_cb传入不同的参数。

void lv_cont_set_layout(lv_obj_t * cont, lv_layout_t layout)
{
    LV_ASSERT_OBJ(cont, LV_OBJX_NAME);
    // 获取当前对齐属性
    lv_cont_ext_t * ext = lv_obj_get_ext_attr(cont);
    if(ext->layout == layout) return;
    // 若设置属性与当前不同,则使用新的对齐属性
    ext->layout = layout;
    // 发送信号直接处理对齐算法
    /*Send a signal to refresh the layout*/
    cont->signal_cb(cont, LV_SIGNAL_CHILD_CHG, NULL);
}

然后说下lv_obj_refresh_style中先调用invalidate_style_cache(obj, part, prop);里面是设置此obj的所有属性及其子obj的所有属性为无效,这个用for循环不太好吧!干嘛要设置所有的都无效,某些要改动的设置为无效就好了呀?结果看到代码备注上已经写了,将来待优化,哈哈~
接着继续看btn创建中lv_obj_set_size设置。里面为什么要调用2次lv_obj_invalidate,我理解就是设置obj区域无效,obj怎么还分原来的obj和新的obj,再仔细想想是有道理的,因为这是设置区域,无效区域是要重绘的,删除老的大小的区域要重绘,设置完新的size的区域也要重绘。

void lv_obj_set_size(lv_obj_t * obj, lv_coord_t w, lv_coord_t h)
{
    LV_ASSERT_OBJ(obj, LV_OBJX_NAME);

    /* Do nothing if the size is not changed */
    /* It is very important else recursive resizing can
     * occur without size change*/
    if(lv_obj_get_width(obj) == w && lv_obj_get_height(obj) == h) {
        return;
    }

    /*Invalidate the original area*/
    lv_obj_invalidate(obj);

    /*Save the original coordinates*/
    lv_area_t ori;
    lv_obj_get_coords(obj, &ori);

    /*Set the length and height*/
    obj->coords.y2 = obj->coords.y1 + h - 1;
    if(lv_obj_get_base_dir(obj) == LV_BIDI_DIR_RTL) {
        obj->coords.x1 = obj->coords.x2 - w + 1;
    }
    else {
        obj->coords.x2 = obj->coords.x1 + w - 1;
    }

    /*Send a signal to the object with its new coordinates*/
    obj->signal_cb(obj, LV_SIGNAL_COORD_CHG, &ori);

    /*Send a signal to the parent too*/
    lv_obj_t * par = lv_obj_get_parent(obj);
    if(par != NULL) par->signal_cb(par, LV_SIGNAL_CHILD_CHG, obj);

    /*Tell the children the parent's size has changed*/
    lv_obj_t * i;
    _LV_LL_READ(obj->child_ll, i) {
        i->signal_cb(i, LV_SIGNAL_PARENT_SIZE_CHG,  &ori);
    }

    /*Invalidate the new area*/
    lv_obj_invalidate(obj);

    /*Automatically realign the object if required*/
#if LV_USE_OBJ_REALIGN
    if(obj->realign.auto_realign) lv_obj_realign(obj);
#endif
}

接下来貌似初始化赋值完成了,并没有直接调用渲染绘图,渲染绘图是在周期task中统一调度的,所以我第一篇理解的说设置一个task为中优先级才会启动绘图渲染的理解是错误的,它不是触发式的,就是周期扫描,有无效区则渲染。然后就看看渲染绘图函数吧!调用关系如下


image.png

lv_refr_obj_and_children->lv_refr_obj会调用obj->design_cb,我说过2个callback是比较重要的,关于填充color值就是lv_obj_design函数。关键的渲染绘图函数如下,初始化矩形,获取初始化时候的style,然后画矩形。

        lv_draw_rect_dsc_t draw_dsc;
        lv_draw_rect_dsc_init(&draw_dsc);
        /*If the border is drawn later disable loading its properties*/
        if(lv_obj_get_style_border_post(obj, LV_OBJ_PART_MAIN)) {
            draw_dsc.border_post = 1;
        }

        lv_obj_init_draw_rect_dsc(obj, LV_OBJ_PART_MAIN, &draw_dsc);

        lv_coord_t w = lv_obj_get_style_transform_width(obj, LV_OBJ_PART_MAIN);
        lv_coord_t h = lv_obj_get_style_transform_height(obj, LV_OBJ_PART_MAIN);
        lv_area_t coords;
        lv_area_copy(&coords, &obj->coords);
        coords.x1 -= w;
        coords.x2 += w;
        coords.y1 -= h;
        coords.y2 += h;
        // 进行画布填充绘制
        lv_draw_rect(&coords, clip_area, &draw_dsc);

lv_obj_init_draw_rect_dsc中就是为lv_draw_rect_dsc_t对象赋值,这里用到了很多获取style,这些style就是在初始化的时候赋值的。

void lv_obj_init_draw_rect_dsc(lv_obj_t * obj, uint8_t part, lv_draw_rect_dsc_t * draw_dsc)
{
    draw_dsc->radius = lv_obj_get_style_radius(obj, part);

    if(draw_dsc->bg_opa != LV_OPA_TRANSP) {
        draw_dsc->bg_opa = lv_obj_get_style_bg_opa(obj, part);
        if(draw_dsc->bg_opa > LV_OPA_MIN) {
            draw_dsc->bg_color = lv_obj_get_style_bg_color(obj, part);
            draw_dsc->bg_grad_dir =  lv_obj_get_style_bg_grad_dir(obj, part);
            if(draw_dsc->bg_grad_dir != LV_GRAD_DIR_NONE) {
                draw_dsc->bg_grad_color = lv_obj_get_style_bg_grad_color(obj, part);
                draw_dsc->bg_main_color_stop =  lv_obj_get_style_bg_main_stop(obj, part);
                draw_dsc->bg_grad_color_stop =  lv_obj_get_style_bg_grad_stop(obj, part);
            }
    ......
    }

接着的问题就是每个像素的color在哪个函数中绘制的,继续看code,如下图


image.png

lv_draw_rect函数进入后就会涉及到framebuffer对象的填充。
draw_bg主要就是绘制矩形的背景,然后还有绘制边框的等等。为了美观一般矩形绘制都是带圆角的,这里用了mask方法来绘制矩形,最终就是带圆角的。

            /*In not corner areas apply the mask only if required*/
            if(y > coords_bg.y1 + rout + 1 &&
               y < coords_bg.y2 - rout - 1) {
                mask_res = LV_DRAW_MASK_RES_FULL_COVER;
                if(simple_mode == false) {
                    _lv_memset(mask_buf, opa, draw_area_w);
                    mask_res = lv_draw_mask_apply(mask_buf, vdb->area.x1 + draw_area.x1, vdb->area.y1 + h, draw_area_w);
                }
            }
            /*In corner areas apply the mask anyway*/
            else {
                _lv_memset(mask_buf, opa, draw_area_w);
                mask_res = lv_draw_mask_apply(mask_buf, vdb->area.x1 + draw_area.x1, vdb->area.y1 + h, draw_area_w);
            }

lv_draw_mask_apply->lv_draw_mask_radius函数。里面有圆角mask的算法。
fill_normal函数就是填充color的,这里是绘制矩形,不是划线或者画文字的填充方法。如下是软件渲染的方法一次循环赋值。

LV_ATTRIBUTE_FAST_MEM static void fill_normal(const lv_area_t * disp_area, lv_color_t * disp_buf,
                                              const lv_area_t * draw_area,
                                              lv_color_t color, lv_opa_t opa,
                                              const lv_opa_t * mask, lv_draw_mask_res_t mask_res)
{
    /*Get the width of the `disp_area` it will be used to go to the next line*/
    int32_t disp_w = lv_area_get_width(disp_area);

    int32_t draw_area_w = lv_area_get_width(draw_area);
    int32_t draw_area_h = lv_area_get_height(draw_area);

    // 获取画布首地址
    lv_color_t * disp_buf_first = disp_buf + disp_w * draw_area->y1 + draw_area->x1;

    int32_t x;
    int32_t y;
    // 开始填充画布,当然这里面还有判断是否mask
    if(mask_res == LV_DRAW_MASK_RES_FULL_COVER) {
        if(opa > LV_OPA_MAX) {
            /*Software rendering*/
            for(y = 0; y < draw_area_h; y++) {
                lv_color_fill(disp_buf_first, color, draw_area_w);
                disp_buf_first += disp_w;
            }
    ......
}

三,总结

昨天看了style初始化及其存储的的数据结构,今天主要看了哪些样式是什么时候用的,怎么用的。因为我对风格化最感兴趣。主要就是在signal_cb函数中最一些变化进行设置,然后就是周期任务中绘制对象,画布填充color主要是lv_obj_design函数进行处理的。而画布显示到显示屏之前第一篇源码分析已经说过了,主要思路就是设置invalid区,则要重绘,然后有单framebuffer是双framebuffer(部分+完整)的3中重绘方式。
源码分析1到4系列,分析完了关于如何画一个按钮的大体流程,对于各个层次的对象结构体设计也有了一定的认识。反正我觉得在图像引擎中归递的设计方法看的比较多。然后就是看到c++的影子,比如创建子类的时候构造父类对象,以及用钩子函数充当虚方法。

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