一,前言
之前是從一個(gè)將obj放置到最前面的API開始的分析,從設(shè)置無(wú)效區(qū)域后發(fā)task調(diào)度信號(hào),到最后執(zhí)行重繪渲染及更新顯示的整個(gè)過(guò)程版姑。但是我記得里面有l(wèi)v_refr_obj_and_children函數(shù)是關(guān)于渲染重繪的,我沒(méi)有展開迟郎,因?yàn)槔锩孢M(jìn)入lv_obj_design后都是style和mask相關(guān)的內(nèi)容剥险,基本上可以理解為這是渲染重繪的像素重組的功能。但是這些內(nèi)容應(yīng)該在初始化的時(shí)候設(shè)置過(guò)宪肖,然后再次應(yīng)用表制。所以我有必要從頭開始分析下。我選擇了創(chuàng)建一個(gè)button開始分析源碼控乾。
二么介,lv_btn_create
關(guān)于對(duì)象大小和位置的初始化
按官網(wǎng)教程創(chuàng)建btn后需要設(shè)置位置,設(shè)置大小蜕衡,設(shè)置回調(diào)函數(shù)等壤短。而我改成了最簡(jiǎn)單的,只創(chuàng)建btn慨仿,調(diào)用btn = lv_btn_create(lv_scr_act(),NULL);
想看看是否會(huì)出現(xiàn)圖像久脯。結(jié)果真的有一個(gè)按鈕顯示出來(lái),位置在0,0處镶骗,說(shuō)明初始化的時(shí)候就設(shè)置了默認(rèn)值桶现,而關(guān)于大小的默認(rèn)值不是0,所以才能顯示出來(lái)鼎姊。lv_btn_create->lv_cont_create->lv_obj_create
,其中l(wèi)v_obj_create函數(shù)很重要相赁。里面創(chuàng)建了new_obj分配了內(nèi)存相寇,然后就開始使用初始化值對(duì)new_obj進(jìn)行填充了。
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));
剛剛說(shuō)的對(duì)象大小就是在此函數(shù)中填充的钮科,寬度和高度都和宏定義LV_OBJ_DEF_WIDTH有關(guān)唤衫,而它又和lv_conf.h中的LV_DPI有關(guān)。此宏定義的含義是每個(gè)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的代碼里面默認(rèn)給對(duì)象的初始化寬度為100佳励,高度為50。不僅僅是btn蛆挫,其它obj的創(chuàng)建都是這個(gè)值赃承。但是這個(gè)比例是核心代碼中的,不是留給用戶配置的悴侵,當(dāng)然我現(xiàn)在看懂代碼瞧剖,知道含義了,想怎么改都行。哈哈抓于,通過(guò)看源碼破解了奧秘做粤。
#define LV_OBJ_DEF_WIDTH (LV_DPX(100))
#define LV_OBJ_DEF_HEIGHT (LV_DPX(50))
樣式初始化
剛剛看了關(guān)鍵的位置及大小有初始化,其實(shí)默認(rèn)的按鈕它是藍(lán)色的圓角框捉撮,而且可以click點(diǎn)擊怕品,但是不能被拖動(dòng)。這些click和draw屬性在lv_obj_create函數(shù)中都能看到其初始化值巾遭。那么接下來(lái)我最關(guān)心的就是樣式的應(yīng)用堵泽,因?yàn)閳D片好看不好看就是要看code中是如何設(shè)置風(fēng)格化的了。它這里的風(fēng)格設(shè)置恢总,用了theme主題迎罗,應(yīng)用不同的主題,則顯示出來(lái)的外形則不同片仿。對(duì)于使用windows桌面采用不同的主題則界面效果不同纹安,lvgl也是延續(xù)了這樣的思路。我慢好奇的砂豌,這個(gè)具體實(shí)現(xiàn)機(jī)制是怎么樣的呢厢岂!
lv_style_list_init(&new_obj->style_list);
if(copy == NULL) {
/* 若無(wú)傳入copy對(duì)象,并且沒(méi)有parent則風(fēng)格化參考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對(duì)象塔粒,參考其風(fēng)格進(jìn)行樣式格式化 */
lv_style_list_copy(&new_obj->style_list, ©->style_list);
}
好了,那么就來(lái)分析lv_theme_apply函數(shù)筐摘,簡(jiǎn)單來(lái)很容易理解卒茬,先刪除之前的主題,再使用新的主題咖熟。這就是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);
}
繼續(xù)先看clear_styles椎扬,這里面好多case語(yǔ)句牧氮,寫的不雅觀,建議改成數(shù)組查表調(diào)用宙帝,數(shù)組的每一行也可以添加宏定義确沸。
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
捌锭。。罗捎。观谦。。宛逗。
由于我那一句按鈕初始化坎匿,其實(shí)傳入的是參數(shù)是LV_THEME_OBJ,所以會(huì)調(diào)用lv_obj_clean_style_list(obj, LV_OBJ_PART_MAIN);
,其實(shí)里面就是獲取obj中style_list的地址,然后調(diào)用_lv_style_list_reset(style_dsc);后面還是動(dòng)畫功能的相關(guān)處理我先不管替蔬。
真正的清除原先的style功能就在下面的代碼中實(shí)現(xiàn)告私。但是我不理解的有2個(gè)成員,1個(gè)是lv_style_list_t中的trans和local承桥,是什么意思驻粟,另外就是清除lv_style_list_t只要清除如下幾個(gè)屬性就可以了嗎?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*/
}
主題樣式初始化
我先帶著問(wèn)題繼續(xù)看應(yīng)用新的主題蜀撑,說(shuō)不定在應(yīng)用新的主題的時(shí)候,其它值都會(huì)被重新設(shè)置一遍剩彬,那么剛剛的clean中就沒(méi)必要清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其實(shí)是theme_apply函數(shù),它是在lv_theme_material_init函數(shù)中初始化的回調(diào)函數(shù)喉恋。lv_config.h配置中設(shè)置了#define LV_THEME_DEFAULT_INIT lv_theme_material_init
lv_init函數(shù)中對(duì)調(diào)用沃饶。
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結(jié)構(gòu)體對(duì)象中的成員蠻有意思的,除了前7個(gè)固定的轻黑,其它都可以配置糊肤,根據(jù)顯示需要的元素進(jìn)行配置,不同的元素都有自己特色的風(fēng)格成員氓鄙,例如日歷馆揉, 則有日期等,例如arc就有旋鈕特色成員抖拦。另外一個(gè)關(guān)鍵的就是這些成員的類似定義為lv_style_t升酣,要有一個(gè)類型能滿足所有成員,我猜測(cè)就是void *蟋座,因?yàn)榭梢噪S意綁定對(duì)象類型拗踢,結(jié)果查看了下是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中除了設(shè)置theme.apply_cb回調(diào)函數(shù)驯遇,也包括basic_init等函數(shù)芹彬,分析下。
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我一開始沒(méi)有搜索到叉庐。原來(lái)是_LV_OBJ_STYLE_SET_GET_DECLARE(BG_OPA, bg_opa, lv_opa_t, _opa)的宏定義展開的inline函數(shù)舒帮,在h文件中。這里_LV_OBJ_STYLE_SET_GET_DECLARE里面又進(jìn)行了合并同類項(xiàng),抽象化玩郊。說(shuō)白了就是定了3個(gè)格式類似的函數(shù)肢执。
這里我又學(xué)習(xí)到了一招,為了少?gòu)?fù)制黏貼code译红,提高工作效率预茄,則采用此宏定義+不同字符的方法來(lái)定義函數(shù),其實(shí)預(yù)編譯的時(shí)候這些宏定義都會(huì)進(jìn)行函數(shù)展開的侦厚。
#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函數(shù)最后會(huì)調(diào)用_lv_style_set_opa函數(shù)耻陕。這個(gè)函數(shù)寫的蠻有意思的。它需要為lv_style_t中的uint8* map來(lái)賦值刨沦,并且還能保證能找到不同長(zhǎng)度的style成員诗宣。第一次設(shè)置style的話id為0,通過(guò)最后3個(gè)memcpy可以看出map中要保存3個(gè)成員屬性+透明度+結(jié)束符想诅,抽象理解就是屬性+屬性值+結(jié)束符召庞。而里面的new_prop_size,end_mark_size和總size的計(jì)算很容易理解侧蘸,就是每個(gè)成員的長(zhǎng)度裁眯,這些size就是在3次memcpy中用的。style_resize可以理解為通過(guò)malloc重新*map分配空間讳癌。然后在style->map地址開始裝數(shù)據(jù)穿稳。裝載順序入下圖。
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));
}
現(xiàn)在我算是學(xué)習(xí)到了逢艘,結(jié)構(gòu)不同的打包放入指針,通過(guò)成員的類型長(zhǎng)度也可以找到不同的成員骤菠。這個(gè)感覺(jué)和protobuf原理有些類似它改,反正就是解析數(shù)據(jù)一類的。上面代碼中的if的功能就是若找到屬性商乎,則重置屬性值央拖,直接return退出,否則要走下面的添加屬性及屬性值的步驟鹉戚。
然后看看成員不同格式的組合是如何進(jìn)行解析查找的鲜戒。get_property_index函數(shù),我只保留了結(jié)構(gòu)抹凳。
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會(huì)調(diào)用如下關(guān)鍵語(yǔ)句遏餐,這容易理解,獲取第i個(gè)熟悉赢底,然后返回屬性值失都。
lv_style_property_t prop;
_lv_memcpy_small(&prop, &style->map[idx], sizeof(lv_style_property_t));
return prop;
若屬性和當(dāng)前查找的不同柏蘑,則需要獲取下一個(gè)屬性,上面的截圖我已經(jīng)分析過(guò)了1個(gè)字節(jié)屬性+2個(gè)字節(jié)屬性值+結(jié)尾符粹庞。那么我要找下一個(gè)屬性應(yīng)該加幾個(gè)字節(jié)應(yīng)該知道了吧咳焚!lvgl設(shè)計(jì)的更加好,因?yàn)樗?類屬性和值混合存放的信粮,當(dāng)然屬性類別的長(zhǎng)度是固定的黔攒,而屬性值的長(zhǎng)度根據(jù)屬性類別而不同。所以不是我說(shuō)的固定值强缘,而是在get_prop_size函數(shù)中可以看出會(huì)增加不同的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;
}
三,小結(jié)
今天從按鈕的創(chuàng)建開始旅掂,重點(diǎn)分析了主題樣式的初始化赏胚,畢竟做引擎的目的就是減少修改,而換皮技術(shù)就是一個(gè)很靈活的GUI引擎必備的功能商虐,這里分析了利用屬性類+屬性值的混合屬性保存及解析方法觉阅。真的感覺(jué)像是我以前做的tcp報(bào)文數(shù)據(jù)解析類似,這樣的設(shè)計(jì)有意思秘车,但是它為什么要這樣設(shè)計(jì)典勇,而不是每種屬性類和值都做成獨(dú)立的結(jié)構(gòu)體對(duì)象呢?我想了下叮趴,估計(jì)是為了使用的時(shí)候更便利割笙,可擴(kuò)展性更強(qiáng),并且我們?cè)O(shè)計(jì)代碼不就是要抽象嘛眯亦!所以它已經(jīng)做的很抽象了伤溉。唯一我覺(jué)得不好的就是查詢算法不太好。