基於LittlevGL開源GUI庫的多級菜單界面設計。

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);
    }
}

這裏僅僅介紹了設置這個模塊的界面切換模式,以及實現方法,其中還有負責模塊管理模塊的設計,其主要思想與設置是一樣的,都是通過棧來管理的,只是管理模塊的設計稍微複雜一些。

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章