在某些频繁更新的界面中,如果某些显示元素一直没有变化,我们就可以将这些元素提取出来画到一个模拟的屏幕中,而将一些需要更新的元素画到另外的模拟屏幕,而后将两个模拟屏幕合并到真正的屏幕上,这样我们就节省了不变元素的重画时间,从而减轻了系统负担及加速画面更新。我们把这样的模拟屏幕就叫层,也可以说层就是屏幕的缓冲空间。
例如,如果我们用动画做为背景,将其他的一些元素也画到这一层中,就会出现当动画跳到第二帧后,动画上面的文本及图象都会被盖住。而有了层以后,我们就可以将不变的文本及图象放到新建的一个层中,将动画放到背景层中,每当有动画换帧时,只需将新帧画到背景层中,然后合并两个屏幕(动画刷新时会自动合并),这样上层的文本等就不会被盖住了。
另外,因为层的格式简单且统一,并且一般的图形系统都会用硬件加速合并,所以在层合并时加上些特效会很方便,如通透、半透明、剪切等。
下面将以mmi_phoart_entry_main_screen()为例说明关于层的一些内容。
1.首先,因为用到两个层,所以我们要开启多层
gdi_layer_multi_layer_enable();
等到退出该屏幕的时候需要gdi_layer_multi_layer_disable();
2.创建层:创建层需要先建一个层句柄,我们可以通过该句炳来控制层,函数gdi_layer_create用来创建层
gdi_layer_create(OFFSET_X,OFFSET_Y,WIDTH,HEIGHT,HANDLE_PTR);
OFFSET_X,OFFSET_Y是层的位置坐标,WIDTH,HEIGHT是层的大小;HANDLE_PTR是层句炳,用于返回所创建的层。不过,因为创建层时系统要为其分配动态内存空间,而系统保留的内存一般只够创建一个UI_device_width*UI_device_height大小的层,所以如果已有两个层,而需要再创建新层时,就需要用到函数gdi_layer_create_using_outside_memory(X,Y,WIDTH,HEIGHT,HANDLE_PTR,OUTMEM_PTR,OUTMEM_SIZE)
前5个参数和gdi_layer_create的参数相同,OUTMEM_PTR是存放层的BUFFER,OUTMEM_SIZE是层的大小。
3.激活层:在我们的图形系统中,任何时刻有且只能有一个层处于激活状态,所有的绘画都会默认画到这个激活层中,所以想要在层上绘画必须先将其激活。激活函数是gdi_layer_set_active(gdi_handle handle);不过,由于在多层的处理中需要在各个层之间切换激活,所以我们经常用到的是gdi_layer_push_and_set_active(gdi_handle handle),此函数会把当前的激活层入栈而激活参数层,等到下次需要激活栈中的层时,只需要用函数gdi_layer_pop_and_restore_active()激活就可以了。
4.基础层:系统开机的时候会为每个硬件屏幕创建一个基础层,基础层有以下几个特点:
(1)基础层由系统创建,无法删除。
(2)与硬件屏幕完全重合。
(3)系统默认的激活层,EntryNewScreen时系统会紫铜将基础层激活。
(4)显示更加快速,基础层存储于芯片内的flash中,所以在其上面绘画极快,一般我们会将刷新频繁的内容放在基础层上。
基于以上几点,通常在不使用多层的情况下,我们完全可以将基础层当成硬件屏幕来看待,也就是说普通程序完全可以忽略层的概念。另外,因为系统一般只在EntryNewScreen时才会自动将基础层激活,为避免特殊情况下使用层混乱,通常在新层上绘画完毕后我们会主动将基础层还原为激活状态。
其实,对于我们上面说到的层之间的切换激活而言,大多说情况下是基础层和新层之间的切换。为此需要用到gdi_layer_get_active(gdi_handle *handle_ptr)得到当前激活层句柄(多数情况下是基础层句柄),这样我们就可以切换激活层了。
5.合并:有了多个层,当然要合并到一起了。合并层的函数是gdi_layer_blt_previous(S32 x1, S32 y1, S32 x2, S32 y2),gui_BLT_double_buffer也具有同样的效果。另外,在合并前,我们还需要用函数gdi_layer_set_blt_layer(H1,H2,H3,H4) 来指明需要合并的层。
6。释放:由于空间的问题,我们创建的新层在用完后一定要释放,释放函数是:
gdi_layer_free(gdi_handle handle)。注意:层一般是在退出函数中释放。
说到这儿,我们就可以建立一个多层的屏幕了。下面我们在看看层的一些特效:
1.剪切:所谓剪切,就是在层中设一个限制区域,只有在这个区域中的绘画才是有效的,否则就会被自动忽略。剪切有两个显著的特点,一是每个层一定而且只能有一个剪切区域。二是剪切区域一经设置将永远生效。所以剪切区域用完后最好用gdi_layer_reset_clip()还原。剪切用函数gdi_layer_set_clip()来实现。
2.通透:所谓通透,就是如果我们将某种颜色设为通透色,在层合并的时候,系统会自动将层中与通透色相同的颜色忽略掉,这样我们在这一点上看到的是其下层的颜色。设置通透的函数是gdi_layer_set_source_key。另外在设置通透前我们通常用gdi_layer_clear_background将该层刷上某种颜色。
3.透明:gdi_layer_set_opacity(BOOL opacity_enable, U8 opacity_value)
opacity_enable指通明是否开启,opacity_value指透明度,范围是0至255,值越小越透明。
4.锁屏:由于某些元素要频繁的刷新,而如果我们每次都合并就会很浪费时间,因此我们可以设置两个计数器,一个用来加,一个用来减,当计数器为0时,我们再合并。这两个计数器就是 gdi_layer_lock_frame_buffer()和 gdi_layer_unlock_frame_buffer();
请参考以下进入一个新屏的函数:
static void mmi_entry_new_screen(void)
{
S8 buf_filename[FMGR_PATH_BUFFER_SIZE];
S32 image_width;
S32 image_height;
PU8 buf_ptr;
U8 *gui_buffer;
GDI_RESULT result;
S32 offset_x;
S32 offset_y;
U16 img_type;
UI_string_type title_string;
S32 str_width;
S32 str_height;
EntryNewScreen (SCR_ID_PHOART_MAIN,mmi_phoart_exit_main_screen,mmi_phoart_entry_main_screen, NULL);
gdi_layer_reset_clip();//设置剪切区域
gdi_layer_reset_text_clip();//设置剪切区域
//gui_set_font(&MMI_medium_font);
gui_buffer = GetCurrGuiBuffer(SCR_ID_PHOART_MAIN);
entry_full_screen();
gdi_layer_multi_layer_enable();//开启多层
gdi_layer_get_active(&g_phoart_cntx.base_layer_handle);//得到当前活动层
gdi_layer_get_source_key(&g_phoart_cntx.was_source_key_enable, &g_phoart_cntx.old_source_key);//得到当前活动层的通透属性
gdi_layer_set_source_key(FALSE, g_phoart_cntx.old_source_key);//设置通透
gdi_layer_create(0, 0, UI_device_width, UI_device_height, &g_phoart_cntx.osd_layer_handle);//创建新层
gdi_layer_push_and_set_active(g_phoart_cntx.osd_layer_handle);//激活新层
gdi_layer_clear_background(GDI_COLOR_TRANSPARENT);//刷色
gdi_layer_set_source_key(TRUE, GDI_COLOR_TRANSPARENT);//设置通透
//base layer
gdi_layer_pop_and_restore_active();//激活栈中的层
//gdi_layer_reset_clip();
//gdi_layer_reset_text_clip();
gdi_layer_clear_background(GDI_COLOR_RED);
gdi_layer_set_source_key(TRUE, GDI_COLOR_RED);
gdi_image_stop_animation_all();
gdi_image_draw_animation_id(50, 100, IMG_ID_PHOART_ICON_6, NULL);
//new layer
gdi_layer_push_and_set_active(g_phoart_cntx.osd_layer_handle);
// gdi_layer_reset_clip();
//gdi_layer_reset_text_clip();
gdi_layer_set_clip(0,0,50,50);
gdi_layer_clear_background(GDI_COLOR_TRANSPARENT);
gdi_layer_set_source_key(TRUE, GDI_COLOR_TRANSPARENT);
gui_set_font(&MMI_medium_font);
gui_set_text_color(g_phoart_cntx.text_color);
gui_set_text_border_color(g_phoart_cntx.text_border_color);
title_string = (UI_string_type) get_string(STR_ID_PHOART_APP);
gui_measure_string(title_string, &str_width, &str_height);
gdi_layer_set_clip((UI_device_width - str_width) >> 1,(MMI_title_height - str_height) >> 1,200,100);//设置剪切区域
gui_move_text_cursor((UI_device_width - str_width) >> 1, (MMI_title_height - str_height) >> 1);
gui_print_bordered_text(title_string);
mmi_phoart_draw_softkey((PS8) get_string(STR_GLOBAL_OPTIONS), (PS8) get_string(STR_GLOBAL_BACK), FALSE);
gdi_layer_pop_and_restore_active();
gdi_layer_set_blt_layer(g_phoart_cntx.base_layer_handle, g_phoart_cntx.osd_layer_handle, 0, 0);//指明需要合并的层
gdi_layer_blt_previous(0, 0, UI_device_width - 1, UI_device_height - 1);//合并层
}
---------------------------------------------------------------------------------
绘制窗体实际上可以看作是绘制轮廓和绘制组件两个部分。
我们先看这么一个文件 CustCoordinates.c
这个文件主要是定义了这么一个宏 g_categories_controls_map:
或者类似于这样的定义,这个数组就具体的将窗体的轮廓以及窗体的各个组件的位置作出了定义
下面我们以一个具体的例子作为说明:
const U8 category5[] =
{
5, // 这个代表组件的个数;
DM_BASE_LAYER_START, // 开始层;
DM_SCR_BG, // 背景;
DM_BASE_CONTROL_SET1, //表示窗体的基本组成——状态栏、标题和软按键
DM_MULTILINE_INPUTBOX1, //多行输入框
DM_CATEGORY_CONTROLLED_AREA // 输入法的部分;
};
这些组件被定义在枚举结构mmi_dm_control_ids_enum中。
const S16 coordinate_set5[] =
{
DM_FULL_SCREEN_COORDINATE_FLAG,
DM_CONTENT_COORDINATE_FLAG,
DM_FULL_SCREEN_COORDINATE_FLAG
};
这个数组是这些组件的属性;这个属性主要是指各个组件的坐标,高度,宽度;
这些数组里面定义的内容通过dm_get_coordinates() 这个函数映射到具体的绘制过程中;
在dm_get_coordinates 这个函数中我们可以看到,这些 DM_FULL_SCREEN_COORDINATE_FLAG常量实际上都被转化为坐标。
现在我们回到刚才的那个函数 ShowCategory..Screen() 中来。下图是个典型的窗口图:
以下为例子:
void ShowCategory353Screen(
U8 *title,
U16 title_icon,
U16 left_softkey,
U16 left_softkey_icon,
U16 right_softkey,
U16 right_softkey_icon,
S32 number_of_items,
U8 **list_of_items,
U16 *list_of_icons,
U8 **list_of_descriptions,
S32 flags,
S32 highlighted_item,
U8 *history_buffer)
{
dm_data_struct dm_data;
S32 i;
U8 h_flag;
gdi_layer_lock_frame_buffer(); // 锁定当前层的buffer;
// 创建一个列表式的窗口
create_fixed_icontext_menuitems();
associate_fixed_icontext_list();
// 并将窗口下的内容,包括标题栏,左右软件,以及各个子菜单的图标 显示出来;
ShowListCategoryScreen(
(UI_string_type) title,
get_image(title_icon),
get_string(left_softkey),
get_image(left_softkey_icon),
get_string(right_softkey),
get_image(right_softkey_icon),
number_of_items);
if (list_of_descriptions == NULL)
{
for (i = 0; i < number_of_items; i++)
{
add_fixed_icontext_item((UI_string_type) list_of_items[i], wgui_get_list_menu_icon(i, list_of_icons[i]));
wgui_pop_up_description_strings[i].text_strings[0] = NULL;
}
}
else
{
for (i = 0; i < number_of_items; i++)
{
add_fixed_icontext_item((UI_string_type) list_of_items[i], wgui_get_list_menu_icon(i, list_of_icons[i]));
wgui_pop_up_description_strings[i].text_strings[0] = (UI_string_type) list_of_descriptions[i];
}
}
h_flag = set_list_menu_category_history(MMI_CATEGORY52_ID, history_buffer);
// 高亮当前选中的ITEM
if (h_flag)
{
fixed_list_goto_item_no_redraw(MMI_fixed_list_menu.highlighted_item);
}
else
{
fixed_list_goto_item_no_redraw(highlighted_item);
}
MMI_title_string = (UI_string_type) title;
MMI_title_icon = (PU8) get_image(title_icon);
set_pop_up_descriptions(1, number_of_items, MMI_fixed_list_menu.highlighted_item);
gdi_layer_unlock_frame_buffer();
// 前面这整个一段是用来绘制一个列表窗口,这部分的内容基本上根据自己的选择有什么画什么;
// 而下面的内容是每个窗体都共有的部分;
ExitCategoryFunction = ExitListCategoryScreen;
dm_setup_category_functions(dm_redraw_category_screen,dm_get_category_history, dm_get_category_history_size);
dm_data.s32ScrId = (S32) GetActiveScreenId();
dm_data.s32CatId = MMI_CATEGORY52_ID;
dm_data.s32flags = 0;
dm_setup_data(&dm_data);
dm_redraw_category_screen();
}
因为MTK后面的代码的gui 部分是用 draw_manager 这个来控制的,因此所有的窗口的控件的实际绘制过程都是通过 dm_redraw_category_screen(); 这个函数来实现的;这个函数可以帮助我们绘制一些比较特殊的自己需要的组件,当然如果我们使用的组件已经包含在这个函数里面,那么直接使用。
可以分析一下这个函数的流程:
获取窗体模版的ID; // 这个在showcategory里面实现,dm_data.s32CatId = MMI_CATEGORY52_ID;
↓
根据模版的ID得到组件的集合和个数;
// control_set_ptr = dm_search_control_set((U16) g_dm_data.s32CatId, &DeafultCoordinateSet_p);
↓
根据模版ID得到组件属性标识的集合;
// UICtrlAccessPtr_p = dm_search_coordinate_set(g_dm_data.s32ScrId);
↓
锁定当前的frame,各个组件绘制前的任务准备就绪;// gdi_layer_lock_frame_buffer();
↓
绘制窗体之前,还可以改变窗体的大小;
// UICtrlAccessPtr_p = dm_get_cat_scr_coordinates(UICtrlAccessPtr_p, &dm_cat_scr_info);
↓
获取不同组件的不同属性,并根据不同的属性绘制出自己需要的窗口;
// for (u8CtrlCt = 1; u8CtrlCt <= u8NoOfUICtrls; u8CtrlCt++) 这个语句包含的内容
↓
全部绘制完毕,整合GDI layer,将当前frame 解锁;
上面已经说明了一个窗体的绘制过程。另外与窗体相关的函数还有:
Redrawcategory**screen(); 这个是窗体的刷新函数;
Exitcategory**screen() ; 这个是窗体的退出函数