LittlevGL 是一款開源的GUI庫,其內存使用小,功能強大,提供各種控件供用戶使用,其官網鏈接如下:https://littlevgl.com/
現在有個項目,需要實現類似於Android 設置一樣的多級設置菜單。經過思考,最終設計方案如下:
首選有個main.c文件,負責當前模塊的界面切換,然後當前目錄的每一個界面都使用一個c文件實現,然後在其.h文件中提供四個接口,來控制當前的界面切換,這個四個接口分別是 創建,隱藏,按鍵分發,界面釋放。
然後定義一個結構體,結構體中包含四個函數指針,分別指向其所代表的界面的四個接口函數,然後通過壓棧的方式來管理界面。
上面扯得太含糊,下面來看具體代碼、
// settingPage 結構體的實現
struct settingPage{
void (*onCreat)(void); // 創建並顯示界面
void (*onRelece)(void); // 銷燬界面,以及釋放界面中所有元素所佔用的內存
void (*hidePage)(int hide);
void (*onKey)(int keyCode,int keyOption); // 第一個參數是 按鍵類型,說明是按了哪一個按鍵,第二個是按鍵動作,按下還是彈起
};
// main.c中定義的結構體,每個結構體代表一個界面
struct settingPage mainMenuPage;
struct settingPage accountInfoPage;
struct settingPage deviceStatusPage;
// 這個函數就是當前設置模塊初始化函數,這個函數中僅僅是初始化這個模塊,並不進行任何界面顯示動作
void SettingEarlyInit(IPOCApi* pocapi){
stackInit(); // 非常重要,這裏初始化一個棧,用來存放下面的settingPage結構體。
// 初始化每個結構體的指針。
mainMenuPage.onCreat = mainMenuInit;
mainMenuPage.onRelece = mainMenuRelece;
mainMenuPage.hidePage = hideMainMenu;
mainMenuPage.onKey = mainMenuOnKey;
accountInfoPage.onCreat = accountInfoInit;
accountInfoPage.onRelece = accountInfoRelece;
accountInfoPage.hidePage = accountInfoHide;
accountInfoPage.onKey = accountInfoOnKey;
deviceStatusPage.onCreat = deviceStatusCreatPage;
deviceStatusPage.onRelece = deviceStatusRelecePage;
deviceStatusPage.hidePage = deviceStatusHide;
deviceStatusPage.onKey = deviceStatusOnKey;
networkSettingPage.onCreat = networkSettingCreatPage;
networkSettingPage.onRelece = networkSettingRelecePage;
networkSettingPage.hidePage = networkSettingHide;
networkSettingPage.onKey = networkSettingOnKey;
……………… 刪除部分源碼
}
// 啓動設置模塊
void SettingCreat(){
stackPush(mainMenuPage); // 將mainMenuPage對象入棧,
// 獲取站定元素,然後調用其onCreat函數,這裏就會調用mainMenuInit函數,
// 然後在mainMenu.c的mainMenuInit函數中,會繪製設置的第一個界面,這樣設置界面就顯示出來了。
stackGetTop().onCreat();
}
// 用戶點擊設置界面中的菜單項的回調函數,這個函數會跳轉到下一級界面
lv_res_t menu_list_click(lv_obj_t *btn){
int free_num = lv_obj_get_free_num(btn); // 獲取按鍵的 free_num 這個free_num 表示當前安的是哪個界面的那個按鍵
stackGetTop().hidePage(1); // 調用當前界面的隱藏函數,此時當前界面上的所有元素會隱藏
switch(free_num){
case LIST_ACCOUNT_INFO:
stackPush(accountInfoPage); // 將自己添加到棧最上面
break;
case LIST_DEVICE_STATUS:
stackPush(deviceStatusPage);
break;
case LIST_NET_SETTING:
stackPush(networkSettingPage);
break;
}
stackGetTop().onCreat(); // 顯示點擊項目的下一頁、這樣下一個界面就顯示出來了,
return LV_RES_OK;
}
void releceSetting(){
// 用戶在設置的一級目錄中點擊返回會調用此函數
onModuleRelece(); // 這個會調用到模塊管理模塊中,高速模塊管理模塊,設置退出了。
}
// 用戶在設置的任意界面安返回按鈕,就會直接調用這個函數,來返回上一個界面
void back(){
if(Empty()) return; // 如果棧是空的,這裏直接返回,防止出現空指針異常
stackGetTop().hidePage(1); // 隱藏界面元素
stackGetTop().onRelece(); // 釋放界面元素所佔用的內存,C語音必須手動釋放申請的內存
stackPop(); // 將當期界面的結構體出棧
if(Empty()){ // 如果此時棧爲空,就說明設置要退出了,
releceSetting();
}else{
stackGetTop().hidePage(0); // 將當期界面的下一個界面顯示出來
}
}
// 隱藏設置界面,這個是在設置結果中,其他模塊需要彈窗的時候,需要調用這個函數來隱藏設置
void hideSetting(int hide){
if(!Empty()){
stackGetTop().hidePage(hide);
}
}
// 按鍵發送函數,當管理模塊將按鍵發送給設置模塊的時候,設置模塊需要將按鍵發送當自己模塊最上層的界面
void SettingOnKey(int keyCode,int keyOption){
stackGetTop().onKey(keyCode,keyOption);
}
struct.c 文件
#include<stdbool.h>
#include<stdlib.h>
#include "struct.h"
typedef struct stack Stack;
//創建棧
Stack *s;
//初始化棧
void stackInit(){
s=NULL;
}
//判斷棧是否爲空
bool Empty(){
if(s==NULL){
return true;
}else{
return false;
}
}
//入棧
void stackPush(struct settingPage element){
Stack *p = (Stack *)malloc(sizeof(Stack));
p->data=element;
p->next=s;
s=p;
}
//出棧
void stackPop(){
if(!Empty(s)){
Stack *item = s;
s=s->next;
free(item);
}
}
//取棧頂元素
struct settingPage stackGetTop(){
if(!Empty(s)) {
return s->data;
}
}
//銷燬棧
void stackDestroy(){
Stack *item = NULL;
do{
item = s;
s = s->next;
if(item != NULL){
free(item);
}
}while(item != NULL);
}
struct.h 文件
struct stack{
struct settingPage data;
struct stack *next;
};
void stackInit();
void stackPush(struct settingPage element);
void stackPop();
struct settingPage stackGetTop();
void stackDestroy();
bool Empty();
mainMenu.h
#include "setting.h"
void mainMenuInitData();
void mainMenuInit(); //創建設置的第一季菜單
void mainMenuRelece(); //隱藏並釋放設置的第一級菜單所佔用的內存
void hideMainMenu(int hide); //隱藏或者顯示設置的第一家菜單,1 表示隱藏,0 表示顯示。
void mainMenuOnKey(int keyCode,int keyOption);
mainMenu.c
#include "mainMenu.h"
lv_obj_t *menuPage;
lv_obj_t *myMenulist;
lv_group_t *groupMainMenu;
lv_obj_t *btnOK;
lv_obj_t *btnCancel;
void mainMenuInit(){
groupMainMenu = lv_group_create();
lv_group_set_focus_cb(groupMainMenu,NULL);
menuPage = lv_page_create(lv_scr_act(), NULL);
lv_obj_set_pos(menuPage,0,16);
lv_obj_set_size(menuPage,LV_HOR_RES,112);
lv_page_set_style(menuPage, LV_PAGE_STYLE_BG, &style_page);
lv_page_set_style(menuPage, LV_PAGE_STYLE_SCRL, &style_page);
myMenulist = lv_list_create(menuPage, NULL);
lv_obj_set_height(myMenulist, 80);
lv_obj_set_width(myMenulist, LV_HOR_RES);
lv_page_set_sb_mode(myMenulist, LV_SB_MODE_ON);
lv_list_set_style(myMenulist, LV_LIST_STYLE_BG, &lv_style_transp_tight);
lv_list_set_style(myMenulist, LV_LIST_STYLE_SCRL, &lv_style_transp_tight);
lv_list_set_style(myMenulist, LV_LIST_STYLE_BTN_REL, &style_btn_rel); // 設置點擊之前每一項的風格
lv_list_set_style(myMenulist, LV_LIST_STYLE_BTN_TGL_REL, &style_btn_pr); // 設置選中之後每一項的風格
lv_list_set_style(myMenulist, LV_LIST_STYLE_SB, &style_scroll_bar); // 滾動條風格
lv_obj_t *item;
item = lv_list_add(myMenulist, NULL, "賬戶信息", menu_list_click);
lv_btn_set_fit(item, false, false);
lv_obj_set_height(item,20);
lv_obj_set_free_num(item,LIST_ACCOUNT_INFO);
item = lv_list_add(myMenulist, NULL, "終端狀態", menu_list_click);
lv_btn_set_fit(item, false, false);
lv_obj_set_height(item,20);
lv_obj_set_free_num(item, LIST_DEVICE_STATUS);
item = lv_list_add(myMenulist, NULL, "網絡設置", menu_list_click);
lv_btn_set_fit(item, false, false);
lv_obj_set_height(item,20);
lv_obj_set_free_num(item,LIST_NET_SETTING);
lv_group_add_obj(groupMainMenu, myMenulist);
btnOK = lv_btn_create(menuPage, NULL);
btnCancel = lv_btn_create(menuPage, NULL);
lv_btn_set_style(btnOK,LV_BTN_STYLE_REL,&style_btn);
lv_btn_set_style(btnOK,LV_BTN_STYLE_PR,&style_btn_pre);
lv_btn_set_style(btnCancel,LV_BTN_STYLE_REL,&style_btn);
lv_btn_set_style(btnCancel,LV_BTN_STYLE_PR,&style_btn_pre);
lv_obj_set_size(btnOK,50,16);
lv_obj_set_size(btnCancel,50,16);
lv_obj_set_pos(btnOK,0,96);
lv_obj_set_pos(btnCancel,110,96);
lv_obj_t * labe_OK;
labe_OK = lv_label_create(btnOK, NULL);
lv_label_set_text(labe_OK, "確定");
lv_obj_t * labe_cancel;
labe_cancel = lv_label_create(btnCancel, NULL);
lv_label_set_text(labe_cancel, "返回");
}
void mainMenuRelece(){
lv_group_remove_obj(myMenulist);
lv_group_del(groupMainMenu);
lv_obj_t *obj;
do{
obj = lv_obj_get_child(lv_page_get_scrl(menuPage), NULL);
if(obj){
lv_obj_del(obj);
obj = lv_obj_get_child_back(lv_page_get_scrl(menuPage), NULL);
if(obj){
lv_obj_del(obj);
}
}
}while(obj);
lv_obj_del(menuPage);
}
void hideMainMenu(int hide){
lv_obj_set_hidden(menuPage,hide);
}
void mainMenuOnKey(int keyCode,int keyOption){
if(keyOption == 1){ // 按鍵按下
switch(keyCode){
case LV_GROUP_KEY_LEFT:
case LV_GROUP_KEY_ENTER:
lv_btn_set_style(btnOK,LV_BTN_STYLE_REL,&style_btn_pre);
break;
case LV_GROUP_KEY_ESC:
lv_btn_set_style(btnCancel,LV_BTN_STYLE_REL,&style_btn_pre);
break;
}
}else if(keyOption == 0){
switch(keyCode){
case LV_GROUP_KEY_UP:
case LV_GROUP_KEY_DOWN:
case LV_GROUP_KEY_NEXT:
case LV_GROUP_KEY_PREV:
lv_group_send_data(groupMainMenu,keyCode);
break;
case LV_GROUP_KEY_LEFT:
case LV_GROUP_KEY_ENTER:
lv_group_send_data(groupMainMenu,LV_GROUP_KEY_ENTER);
lv_btn_set_style(btnOK,LV_BTN_STYLE_REL,&style_btn);
break;
case LV_GROUP_KEY_ESC:
lv_btn_set_style(btnCancel,LV_BTN_STYLE_REL,&style_btn);
back();
}
}else{
lv_btn_set_style(btnOK,LV_BTN_STYLE_REL,&style_btn);
}
}
這裏僅僅介紹了設置這個模塊的界面切換模式,以及實現方法,其中還有負責模塊管理模塊的設計,其主要思想與設置是一樣的,都是通過棧來管理的,只是管理模塊的設計稍微複雜一些。