基于LittlevGL开源GUI库的多级菜单界面设计。

LittlevGL 是一款开源的GUI库,其内存使用小,功能强大,提供各种控件供用户使用,其官网链接如下:Littlevgl - Beginner's Photo Editor Blog

现在有个项目,需要实现类似于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);
    }
}

这里仅仅介绍了设置这个模块的界面切换模式,以及实现方法,其中还有负责模块管理模块的设计,其主要思想与设置是一样的,都是通过栈来管理的,只是管理模块的设计稍微复杂一些。

要在Arduino上使用LVGL来创建自定义键盘,可以按照以下步骤进行操作: 1. 首先,需要安装LVGL。可以在Arduino IDE中使用“管理器”来搜索并安装LVGL。 2. 创建一个新的Arduino项目,并将以下代码复制到您的Arduino IDE中: ```c #include <lvgl.h> #define KEYBOARD_WIDTH 240 #define KEYBOARD_HEIGHT 320 #define KEY_HEIGHT 40 #define KEY_WIDTH 40 #define KEY_SPACING 5 static lv_obj_t *keyboard; static lv_obj_t *textarea; static const char *keyboard_map[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "A", "S", "D", "F", "G", "H", "J", "K", "L", "\n", "Z", "X", "C", "V", "B", "N", "M", ",", ".", "DEL", " ", "\n" }; static void keyboard_event_cb(lv_obj_t *event_obj, lv_event_t event) { if (event == LV_EVENT_CLICKED) { lv_obj_t *clicked_key = lv_event_get_target(); const char *key_text = lv_list_get_btn_text(clicked_key); if (key_text[0] == '\n') { lv_textarea_add_char(textarea, '\n'); } else if (key_text[0] == 'D' && key_text[1] == 'E' && key_text[2] == 'L') { lv_textarea_del_char(textarea); } else { lv_textarea_add_text(textarea, key_text); } } } void setup() { lv_init(); lv_disp_drv_t disp_drv; lv_disp_drv_init(&disp_drv); lv_disp_buf_t disp_buf; static lv_color_t buf[LV_HOR_RES_MAX * LV_VER_RES_MAX / 10]; lv_disp_buf_init(&disp_buf, buf, NULL, LV_HOR_RES_MAX * LV_VER_RES_MAX / 10); disp_drv.hor_res = KEYBOARD_WIDTH; disp_drv.ver_res = KEYBOARD_HEIGHT; disp_drv.flush_cb = [](lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_p) { uint32_t size = (area->y2 - area->y1 + 1) * (area->x2 - area->x1 + 1); uint16_t data[size]; uint32_t i = 0; for (uint16_t y = area->y1; y <= area->y2; y++) { for (uint16_t x = area->x1; x <= area->x2; x++) { uint16_t color = lv_color_to16(*color_p); data[i++] = color; color_p++; } } // send data to display }; disp_drv.buffer = &disp_buf; lv_disp_drv_register(&disp_drv); keyboard = lv_list_create(lv_scr_act(), NULL); lv_obj_set_size(keyboard, KEYBOARD_WIDTH, KEYBOARD_HEIGHT); lv_obj_set_pos(keyboard, 0, 0); lv_list_set_style(keyboard, LV_LIST_STYLE_BG, &lv_style_transp_tight); lv_list_set_style(keyboard, LV_LIST_STYLE_SCRL, &lv_style_transp_tight); lv_list_set_style(keyboard, LV_LIST_STYLE_BTN_REL, &lv_style_btn_rel); lv_list_set_style(keyboard, LV_LIST_STYLE_BTN_PR, &lv_style_btn_pr); lv_list_set_style(keyboard, LV_LIST_STYLE_BTN_TGL_REL, &lv_style_btn_tgl_rel); lv_list_set_style(keyboard, LV_LIST_STYLE_BTN_TGL_PR, &lv_style_btn_tgl_pr); lv_obj_t *btnm = lv_btnm_create(keyboard, NULL); lv_btnm_set_map(btnm, keyboard_map); lv_btnm_set_style(btnm, LV_BTNM_STYLE_BG, &lv_style_transp_tight); lv_btnm_set_style(btnm, LV_BTNM_STYLE_BTN_REL, &lv_style_btn_rel); lv_btnm_set_style(btnm, LV_BTNM_STYLE_BTN_PR, &lv_style_btn_pr); lv_btnm_set_style(btnm, LV_BTNM_STYLE_BTN_TGL_REL, &lv_style_btn_tgl_rel); lv_btnm_set_style(btnm, LV_BTNM_STYLE_BTN_TGL_PR, &lv_style_btn_tgl_pr); lv_obj_align(btnm, NULL, LV_ALIGN_IN_BOTTOM_MID, 0, 0); lv_obj_set_event_cb(btnm, keyboard_event_cb); textarea = lv_textarea_create(lv_scr_act(), NULL); lv_obj_set_size(textarea, KEYBOARD_WIDTH - KEY_SPACING * 2, KEY_HEIGHT * 2); lv_obj_align(textarea, NULL, LV_ALIGN_IN_TOP_LEFT, KEY_SPACING, KEY_SPACING); } void loop() { lv_task_handler(); delay(5); } ``` 3. 在上面的代码中,我们定义了一个`keyboard_map`数组,其中包含键盘的各个按钮。然后我们创建了一个列表对象,用于显示键盘按钮,并将按钮映射到`keyboard_map`数组中的相应文本。我们还为键盘列表添加了一个事件回调函数,以便在用户单击按钮时向文本区域添加相应的文本。 4. 在`setup()`函数中,我们初始化了LVGL并创建了一个显示驱动程序。然后我们创建了`keyboard`列表对象和`textarea`文本区域对象,并将它们放置在屏幕上的适当位置。 5. 在`loop()`函数中,我们调用了`lv_task_handler()`函数,该函数处理所有LVGL的任务。我们还使用`delay()`函数来在每次循环之间添加一些时间延迟,以便让系统有时间处理所有任务。 6. 最后,将您的Arduino板连接到计算机并上传代码。您现在应该可以在LCD屏幕上看到一个自定义键盘,并且可以使用它来向文本区域添加文本。
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值