OpenEdv-开源电子网

 找回密码
 立即注册
查看: 4732|回复: 6

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

[复制链接]

221

主题

221

帖子

0

精华

高级会员

Rank: 4

积分
762
金钱
762
注册时间
2021-5-18
在线时间
28 小时
发表于 2021-11-15 14:10:08 | 显示全部楼层 |阅读模式
LittlevGL 是一款开源的GUI库,其内存使用小,功能强大,提供各种控件供用户使用。
现在有个项目,需要实现类似于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);
    }
}

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

原文链接:http://bj.dyrs.com.cn/story/202111/1166005

正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

0

主题

6

帖子

0

精华

新手入门

积分
18
金钱
18
注册时间
2021-10-31
在线时间
3 小时
发表于 2021-11-21 22:26:23 | 显示全部楼层
目前正在学习lvgl,也要用到切换菜单和外部按键处理,觉得你的这个菜单运行方式很不错,能发一份精简的源码我仿真运行一下吗?非常感谢!!
回复 支持 反对

使用道具 举报

0

主题

6

帖子

0

精华

新手入门

积分
18
金钱
18
注册时间
2021-10-31
在线时间
3 小时
发表于 2021-11-21 22:53:06 | 显示全部楼层
非常感谢,楼主能发个详细点的吗?
回复 支持 反对

使用道具 举报

0

主题

6

帖子

0

精华

新手入门

积分
18
金钱
18
注册时间
2021-10-31
在线时间
3 小时
发表于 2021-11-21 22:53:37 | 显示全部楼层
能有源码下载学习吗?
回复 支持 反对

使用道具 举报

0

主题

17

帖子

0

精华

初级会员

Rank: 2

积分
160
金钱
160
注册时间
2020-9-4
在线时间
46 小时
发表于 2022-6-30 22:54:23 | 显示全部楼层
学习学习
回复 支持 反对

使用道具 举报

5

主题

94

帖子

0

精华

高级会员

Rank: 4

积分
934
金钱
934
注册时间
2017-4-8
在线时间
111 小时
发表于 2022-10-18 16:00:27 | 显示全部楼层
学习了
回复 支持 反对

使用道具 举报

2

主题

472

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
5720
金钱
5720
注册时间
2018-6-27
在线时间
502 小时
发表于 2023-2-20 08:24:37 | 显示全部楼层
学习学习,学习学习。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2024-6-7 20:21

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表