高级会员
- 积分
- 762
- 金钱
- 762
- 注册时间
- 2021-5-18
- 在线时间
- 28 小时
|
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
|
|