BI架构(多级菜单与UI)
是否还在因为移植一些UI烦恼?(当初移植LVGL也就区区4000error而已,也就找不到一些屏幕的UI 驱动芯片代码罢了/(ㄒoㄒ)/~~)是否还在考虑屏幕菜单的实现可行性?
这次介绍我本身写的多级菜单与UI显示(同B站built_in的BI架构讲授)。
我们所必要的装备:至少必要你的屏幕画点函数,文本与图片显示代码。(也就是说至少屏幕驱动你必须有,要先点亮屏幕)
思绪引导:
利用的是数组指针布局体的思绪,主要是利用布局体去存储菜单子项与菜单层级的一些区分标记,然后利用指针去指向当前布局体下的布局体,类似于链表的思绪,然后通过这个布局体数组去存储我们的菜单层,通过调用菜单层,菜单子项与菜单指向等等以及回调函数,可以或许形成一个简易的菜单布局,并且与文字显示与图片显示绑定。
https://i-blog.csdnimg.cn/direct/446a6f0f88a54165a4faffe7fb355e16.png
创建我的菜单项:
// 菜单项结构体
typedef struct
{
const char *name; // 菜单项名称
void (*func)(void); // 菜单项回调函数
} MENU_Item_t; 创建我的菜单层:
// 菜单结构体
typedef struct MENU_t
{
MENU_Item_t *item; // 菜单项数组
uint8_t item_num; // 菜单项数量
uint8_t menu_flag; // 菜单层级标志
uint8_t cur_item; // 当前选择的菜单项
struct MENU_t *sub_menu;// 指向第二级菜单的指针
struct MENU_t *sub1_menu; // 指向第三级菜单的指针
} MENU_t; 文字菜单必要的菜单项名称与实现函数:
// 菜单项
MENU_Item_t menu_item[] =
{
{"Item1", Item1_Callback},
{"Item2", Item2_Callback},
{"Item3", Item3_Callback},
{"Item4", Item4_Callback},
{"Item5", Item5_Callback},
{"Item6", Item6_Callback},
};
// 二级菜单项
MENU_Item_t second_menu_item[] =
{
{"second_Item1", SUB1_Callback},
{"second_Item2", SUB2_Callback},
{"second_Item3", SUB3_Callback},
};
// 三级菜单项
MENU_Item_t third_menu_item[] =
{
{"third_Item1", Third1_Callback},
{"third_Item2", Third2_Callback},
{"third_Item3", Third3_Callback},
{"third_Item4", Third4_Callback},
}; void Item2_Callback(void)
{
// 处理Item2的回调函数
menu.cur_item = 1;
}
void Item3_Callback(void)
{
// 处理Item3的回调函数
menu.cur_item = 2;
}
//记得你编写的回调函数都需要初始化 我的BI架构,选择的是有一个简单的移动方框特效在切换选择子项,选择当前子项的时间是方框框住这个子项名称,到屏幕可见的菜单子项的时间能翻滚,显示更多的菜单子项(这种特效都可以本身修改的,我讲的是思绪):
//文字菜单显示
void MENU_Text_Display(void)
{
uint8_t i; //绘制文本菜单项变量
uint8_t x=0,y=0; //显示屏横竖大小变量
uint8_t menu_display; //显示标志位
uint16_t pos; //定义像素变量
char str; //菜单项名字
OLED_Clear(); //清空屏幕
static uint8_t old_item=0; //旧选项框参数
uint8_t old_y = y + old_item * OLED_Rectangle_Hight; //旧选项框坐标
uint8_t new_y = y + menu.cur_item * OLED_Rectangle_Hight; //新选项框坐标
uint8_t visiable_items = (menu.item_num<3) ? menu.item_num :3; //动态设置屏幕可见菜单子项数量
uint8_t start_index = (menu.cur_item<2) ? 0 :menu.cur_item - 2; //根据当前选项计算起始项
if(start_index + visiable_items > menu.item_num) //防止越界
start_index = menu.item_num - visiable_items;
for(i=0;i<visiable_items;i++)
{
if(start_index + i < menu.item_num) //防止越界
{
sprintf((char*)&str,"%s",menu.item.name);
OLED_ShowString(x+5,y+i* OLED_Rectangle_Hight+2,str,OLED_8X16); //绘制首次菜单项
}
}
OLED_DrawRectangle(x,new_y,OLED_Width,OLED_Rectangle_Hight,OLED_UNFILLED); //绘制选项框
if(menu.cur_item >=3)
{
pos = 26; //选项框固定
menu_display = 1; //显示大于屏幕项标志位置1
}
if(old_item != menu.cur_item)
{
for(pos= old_y; pos!= new_y;pos += (new_y>old_y) ? 2:-2)
{
OLED_Clear();
for(i=0;i<visiable_items;i++)
{
if(start_index + i < menu.item_num) //防止越界
{
sprintf((char*)&str,"%s",menu.item.name);
OLED_ShowString(x+5,y+i* OLED_Rectangle_Hight+2,str,OLED_8X16); //绘制首次菜单项
}
}
if(menu_display == 1)
OLED_DrawRectangle(x,pos - (OLED_Rectangle_Hight *(menu.cur_item -2)),OLED_Width,OLED_Rectangle_Hight,OLED_UNFILLED); //固定大于显示项在第三个显示
else
{
OLED_DrawRectangle(x,pos,OLED_Width,OLED_Rectangle_Hight,OLED_UNFILLED); //动态显示移动框
OLED_Update();
}
delay_ms(20);
}
old_item = menu.cur_item; //交换显示项
}
OLED_Update();
}
当然,也有图片显示的函数,
我们这时间的图片分配内存,直接上三张图片,一张左移,一张右移,一张显示,一样的通过old_item != menu.cur_item作为革新条件,在通过menu.cur_item - old_item的差值,看看是左移(菜单项- - ),右移(菜单项++),然后可以通过革新屏幕左右移动的位置,就可以实现简单的移动特效。而我们的显示图片又是与菜单子项挂钩的,所以直接image_data就可以实现图片切换。
//图片菜单显示
void MENU_Iamge_Display(uint8_t iamge_index)
{
static uint8_t old_item=0; //保存上一次选项变量
const uint8_t *image_data_level_1[] = {ear,game,chess,fish,badminton,music}; //一级菜单的图片
const uint8_t *image_data_level_2[] = {book,tool,brush}; //二级菜单的图片
const uint8_t *image_data_level_3[] = {caravan,shopping,telephone,telescope}; //三级菜单的图片
const uint8_t **image_data; //指向图片数据数组的指针
//根据菜单标志选择图片数组
if(menu.menu_flag == 0)
image_data = image_data_level_1;//一级菜单
else if(menu.menu_flag == 1)
image_data = image_data_level_2;//二级菜单
else if(menu.menu_flag ==2)
image_data = image_data_level_3;//三级菜单
//计算图片数据大小
size_t image_data_size;
if(menu.menu_flag == 0)
image_data_size = sizeof(image_data_level_1) / sizeof(image_data_level_1);
else if(menu.menu_flag == 1)
image_data_size = sizeof(image_data_level_2) / sizeof(image_data_level_2);
else if(menu.menu_flag == 2)
image_data_size = sizeof(image_data_level_3) / sizeof(image_data_level_3);
if(iamge_index < image_data_size)
{
OLED_ShowImage(32,0,64,64,image_data); //显示图片项
OLED_ShowImage(0,16,32,32,left); //显示左移箭头
OLED_ShowImage(96,16,32,32,right); //显示右移箭头
OLED_Update();
if(old_item != menu.cur_item)
{
if(menu.cur_item - old_item < 0 ) //刷新一下左移箭头
{
OLED_ClearArea(0,16,32,32);
OLED_Update();
delay_ms(200);
OLED_ShowImage(0,16,32,32,left);
}
else if(menu.cur_item - old_item > 0 ) //刷新一下右移箭头
{
OLED_ClearArea(96,16,32,32);
OLED_Update();
delay_ms(200);
OLED_ShowImage(96,16,32,32,right);
}
OLED_Update();
}
old_item = menu.cur_item; //保存上一次选项
}
else
return;
}
初始化我们的菜单:
一级菜单的时间,就只必要初始化一级菜单的菜单层级,菜单子项个数,菜单当前项,并且初始化菜单系统(menu.item = menu_item;)。我通过预处理命令,让我们上面界说的宏可以或许进行设置我们的多级菜单初始化,在第二,第三层菜单,我们可以通过malloc去给我们的菜单层级进行分配内存,同时通过指针的树状思绪去指向下一层菜单,有点像链表的思绪,进行初始化我们的多级菜单。
int MENU_Init(void)
{
// 初始化一级菜单
menu.item = menu_item;
menu.item_num = sizeof(menu_item) / sizeof(menu_item);
menu.cur_item = 0;
menu.menu_flag = 0;
#if MENU_LEVEL >= 2
menu.sub_menu = malloc(sizeof(MENU_t));
if (menu.sub_menu != NULL)
{
menu.sub_menu->item = second_menu_item;
menu.sub_menu->item_num = sizeof(second_menu_item) / sizeof(second_menu_item);
menu.sub_menu->cur_item = 0;
#if MENU_LEVEL == 3
menu.sub_menu->sub1_menu = malloc(sizeof(MENU_t));
if (menu.sub_menu->sub1_menu != NULL)
{
menu.sub_menu->sub1_menu->item = third_menu_item;
menu.sub_menu->sub1_menu->item_num = sizeof(third_menu_item) / sizeof(third_menu_item);
menu.sub_menu->sub1_menu->cur_item = 0; // 确保三级菜单的当前项初始化
}
else
{
// 处理内存分配失败的情况
free(menu.sub_menu); // 释放之前分配的内存
return -1; // 返回失败状态
}
#endif
}
else
return -1; // 返回失败状态
#endif
return 0; // 返回成功状态
}
进入(这里我直接将当前菜单项,切换菜单层级,设置菜单下一层级菜单项等参数传进去,通过深拷贝当前菜单的内容,方便我们后面返回) 和返回菜单层级函数:
//进入菜单函数
void MENU_INPUT(uint8_t cur_item_ok,MENU_t menu_ok,MENU_t *sub_menu_ok,uint8_t cur_item_next_ok,uint8_t menu_flag_ok)
{
menu.cur_item = cur_item_ok;
if(menu_flag_ok < MENU_LEVEL)
{
prev_menu = malloc(sizeof(MENU_t));
if(prev_menu != NULL)
{
*prev_menu = menu_ok; //深拷贝当前菜单内容
}
else
return;
}
menu = *sub_menu_ok; //切换到二级菜单
menu.cur_item = cur_item_next_ok; //设置当前项为0
menu.menu_flag = menu_flag_ok; //设置为二级菜单
MENU_Mode_chang();
}
//返回菜单函数
void MENU_RETURN(void)
{
if(menu.menu_flag == 1)
{
menu = *prev_menu; //返回到上一项
menu.cur_item = 0;
menu.menu_flag = 0;
}
else if(menu.menu_flag == 2)
{
menu = *prev_menu; //返回到上一项
menu.cur_item = 0;
menu.menu_flag = 1;
}
MENU_Mode_chang();
}
末了附上控制代码:
//菜单控制代码
void MENU_KeyScan(uint8_t key)
{
switch(key)
{
case 0:
MENU_Mode_chang();
break;
case 1:
if(menu.cur_item > 0)
{
menu.cur_item--;
MENU_Mode_chang();
}
break;
case 2:
if(menu.menu_flag < MENU_LEVEL)
{
if(menu.menu_flag == 0 )
{
MENU_INPUT(NULL,menu,menu.sub2_menu,0,1); //进入二级菜单
}
else if(menu.menu_flag == 1 )
{
MENU_INPUT(NULL,menu,menu.sub3_menu,0,2); //进入三级菜单
}
}
break;
case 3:
menu.item.func(); //执行当前项的回调函数
break;
case 4:
MENU_RETURN();
break;
case 5:
if(menu.cur_item < menu.item_num -1)
{
menu.cur_item++;
MENU_Mode_chang();
}
break;
default :
break;
}
} 我们只必要在我们单片机的main中进行调用我们的控制函数就可以实现功能(这边写的简单了点,想要深层编写可看我B站视频,出了三期讲授手把手从0带你写出来,大概私信我,给你份更加美满的讲授文档。)
结果展示与讲授视频(OLED与TFT屏幕,RGB触摸移动(没录视频)):
BI架构菜单编写(一)哔哩哔哩bilibili
BI架构菜单编写(二)哔哩哔哩bilibili
BI架构菜单编写(三)哔哩哔哩bilibili
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]