BI架构(多级菜单与UI)

农民  论坛元老 | 2024-12-1 23:56:26 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 1007|帖子 1007|积分 3021

是否还在因为移植一些UI烦恼?(当初移植LVGL也就区区4000error而已,也就找不到一些屏幕的UI 驱动芯片代码罢了/(ㄒoㄒ)/~~)
是否还在考虑屏幕菜单的实现可行性?

这次介绍我本身写的多级菜单与UI显示(同B站built_in的BI架构讲授)。

我们所必要的装备:至少必要你的屏幕画点函数,文本与图片显示代码。(也就是说至少屏幕驱动你必须有,要先点亮屏幕

思绪引导:
        利用的是数组指针布局体的思绪,主要是利用布局体去存储菜单子项与菜单层级的一些区分标记,然后利用指针去指向当前布局体下的布局体,类似于链表的思绪,然后通过这个布局体数组去存储我们的菜单层,通过调用菜单层,菜单子项与菜单指向等等以及回调函数,可以或许形成一个简易的菜单布局,并且与文字显示与图片显示绑定。



创建我的菜单项:
  1. // 菜单项结构体
  2. typedef struct
  3. {
  4.    const char *name;   // 菜单项名称
  5.    void (*func)(void); // 菜单项回调函数
  6. } MENU_Item_t;
复制代码
创建我的菜单层:
  1. // 菜单结构体
  2. typedef struct MENU_t
  3. {
  4.    MENU_Item_t *item;        // 菜单项数组
  5.    uint8_t item_num;         // 菜单项数量
  6.    uint8_t menu_flag;        // 菜单层级标志
  7.    uint8_t cur_item;         // 当前选择的菜单项
  8.    struct MENU_t *sub_menu;  // 指向第二级菜单的指针
  9.    struct MENU_t *sub1_menu; // 指向第三级菜单的指针
  10. } MENU_t;
复制代码
文字菜单必要的菜单项名称与实现函数:
  1. // 菜单项
  2. MENU_Item_t menu_item[] =
  3.     {
  4.         {"Item1", Item1_Callback},
  5.         {"Item2", Item2_Callback},
  6.         {"Item3", Item3_Callback},
  7.         {"Item4", Item4_Callback},
  8.         {"Item5", Item5_Callback},
  9.         {"Item6", Item6_Callback},
  10. };
  11. // 二级菜单项
  12. MENU_Item_t second_menu_item[] =
  13.     {
  14.         {"second_Item1", SUB1_Callback},
  15.         {"second_Item2", SUB2_Callback},
  16.         {"second_Item3", SUB3_Callback},
  17. };
  18. // 三级菜单项
  19. MENU_Item_t third_menu_item[] =
  20.     {
  21.         {"third_Item1", Third1_Callback},
  22.         {"third_Item2", Third2_Callback},
  23.         {"third_Item3", Third3_Callback},
  24.         {"third_Item4", Third4_Callback},
  25. };
复制代码
  1. void Item2_Callback(void)
  2. {
  3.    // 处理Item2的回调函数
  4.    menu.cur_item = 1;
  5. }
  6. void Item3_Callback(void)
  7. {
  8.    // 处理Item3的回调函数
  9.    menu.cur_item = 2;
  10. }
  11. //记得你编写的回调函数都需要初始化
复制代码
我的BI架构,选择的是有一个简单的移动方框特效在切换选择子项,选择当前子项的时间是方框框住这个子项名称,到屏幕可见的菜单子项的时间能翻滚,显示更多的菜单子项(这种特效都可以本身修改的,我讲的是思绪):
  1. //文字菜单显示
  2. void MENU_Text_Display(void)
  3. {
  4.         uint8_t i;                                //绘制文本菜单项变量
  5.         uint8_t x=0,y=0;                //显示屏横竖大小变量
  6.         uint8_t menu_display;        //显示标志位
  7.         uint16_t pos;                        //定义像素变量
  8.         char str[20];                        //菜单项名字
  9.        
  10.         OLED_Clear();                        //清空屏幕
  11.        
  12.         static uint8_t old_item=0;                                                                        //旧选项框参数
  13.         uint8_t old_y = y + old_item * OLED_Rectangle_Hight;                 //旧选项框坐标
  14.         uint8_t new_y = y + menu.cur_item * OLED_Rectangle_Hight;        //新选项框坐标
  15.        
  16.         uint8_t visiable_items = (menu.item_num<3) ? menu.item_num :3;                //动态设置屏幕可见菜单子项数量
  17.         uint8_t start_index = (menu.cur_item<2) ? 0 :menu.cur_item - 2;                //根据当前选项计算起始项
  18.        
  19.         if(start_index + visiable_items > menu.item_num)                                        //防止越界
  20.                 start_index = menu.item_num - visiable_items;
  21.        
  22.         for(i=0;i<visiable_items;i++)
  23.         {
  24.                 if(start_index + i < menu.item_num)                                                                                                //防止越界
  25.                 {
  26.                         sprintf((char*)&str,"%s",menu.item[start_index + i].name);
  27.                         OLED_ShowString(x+5,y+i* OLED_Rectangle_Hight+2,str,OLED_8X16);                                //绘制首次菜单项
  28.                 }
  29.         }
  30.         OLED_DrawRectangle(x,new_y,OLED_Width,OLED_Rectangle_Hight,OLED_UNFILLED);                        //绘制选项框
  31.        
  32.         if(menu.cur_item >=3)
  33.         {
  34.                 pos = 26;                        //选项框固定
  35.                 menu_display = 1;        //显示大于屏幕项标志位置1
  36.                
  37.         }
  38.        
  39.         if(old_item != menu.cur_item)
  40.         {
  41.                 for(pos= old_y; pos!= new_y;pos += (new_y>old_y) ? 2:-2)
  42.                 {
  43.                         OLED_Clear();
  44.                         for(i=0;i<visiable_items;i++)
  45.                         {
  46.                                 if(start_index + i < menu.item_num)                                                                                                //防止越界
  47.                                 {
  48.                                         sprintf((char*)&str,"%s",menu.item[start_index + i].name);
  49.                                         OLED_ShowString(x+5,y+i* OLED_Rectangle_Hight+2,str,OLED_8X16);                                //绘制首次菜单项
  50.                                 }
  51.                         }
  52.                 if(menu_display == 1)
  53.                         OLED_DrawRectangle(x,pos - (OLED_Rectangle_Hight *(menu.cur_item -2)),OLED_Width,OLED_Rectangle_Hight,OLED_UNFILLED);                //固定大于显示项在第三个显示
  54.                 else
  55.                 {
  56.                         OLED_DrawRectangle(x,pos,OLED_Width,OLED_Rectangle_Hight,OLED_UNFILLED);                                                                                                        //动态显示移动框
  57.                         OLED_Update();
  58.                 }
  59.                         delay_ms(20);
  60.                 }
  61.                 old_item = menu.cur_item;                //交换显示项
  62.        
  63.         }
  64.         OLED_Update();
  65. }
复制代码

当然,也有图片显示的函数,
我们这时间的图片分配内存,直接上三张图片,一张左移,一张右移,一张显示,一样的通过old_item != menu.cur_item作为革新条件,在通过menu.cur_item - old_item的差值,看看是左移(菜单项- - ),右移(菜单项++),然后可以通过革新屏幕左右移动的位置,就可以实现简单的移动特效。而我们的显示图片又是与菜单子项挂钩的,所以直接image_data[image_index]就可以实现图片切换。
  1. //图片菜单显示
  2. void MENU_Iamge_Display(uint8_t iamge_index)
  3. {
  4.         static uint8_t old_item=0;                                                                                                                        //保存上一次选项变量
  5.         const uint8_t *image_data_level_1[] = {ear,game,chess,fish,badminton,music};                //一级菜单的图片
  6.         const uint8_t *image_data_level_2[] = {book,tool,brush};                                                        //二级菜单的图片
  7.         const uint8_t *image_data_level_3[] = {caravan,shopping,telephone,telescope};                //三级菜单的图片
  8.         const uint8_t **image_data;                                                                                                                        //指向图片数据数组的指针
  9.        
  10.         //根据菜单标志选择图片数组
  11.         if(menu.menu_flag == 0)
  12.                 image_data = image_data_level_1;//一级菜单
  13.         else if(menu.menu_flag == 1)
  14.                 image_data = image_data_level_2;//二级菜单
  15.         else if(menu.menu_flag ==2)
  16.                 image_data = image_data_level_3;//三级菜单
  17.        
  18.         //计算图片数据大小
  19.         size_t image_data_size;
  20.         if(menu.menu_flag == 0)
  21.                 image_data_size = sizeof(image_data_level_1) / sizeof(image_data_level_1[0]);
  22.         else if(menu.menu_flag == 1)
  23.                 image_data_size = sizeof(image_data_level_2) / sizeof(image_data_level_2[0]);
  24.         else if(menu.menu_flag == 2)
  25.                 image_data_size = sizeof(image_data_level_3) / sizeof(image_data_level_3[0]);
  26.        
  27.         if(iamge_index < image_data_size)
  28.         {
  29.                 OLED_ShowImage(32,0,64,64,image_data[iamge_index]);                        //显示图片项
  30.                 OLED_ShowImage(0,16,32,32,left);                                                        //显示左移箭头       
  31.                 OLED_ShowImage(96,16,32,32,right);                                                        //显示右移箭头
  32.                 OLED_Update();
  33.                
  34.                 if(old_item != menu.cur_item)
  35.                 {
  36.                         if(menu.cur_item - old_item < 0 )                                                //刷新一下左移箭头
  37.                         {
  38.                                 OLED_ClearArea(0,16,32,32);
  39.                                 OLED_Update();
  40.                                 delay_ms(200);
  41.                                 OLED_ShowImage(0,16,32,32,left);
  42.                        
  43.                         }
  44.                         else if(menu.cur_item - old_item > 0 )                                        //刷新一下右移箭头
  45.                         {
  46.                                 OLED_ClearArea(96,16,32,32);
  47.                                 OLED_Update();
  48.                                 delay_ms(200);
  49.                                 OLED_ShowImage(96,16,32,32,right);
  50.                        
  51.                         }
  52.                         OLED_Update();
  53.                 }
  54.                 old_item = menu.cur_item;                                                                        //保存上一次选项
  55.         }
  56.         else
  57.                 return;
  58. }
复制代码

初始化我们的菜单:
一级菜单的时间,就只必要初始化一级菜单的菜单层级菜单子项个数菜单当前项,并且初始化菜单系统(menu.item = menu_item;)。我通过预处理命令,让我们上面界说的宏可以或许进行设置我们的多级菜单初始化,在第二,第三层菜单,我们可以通过malloc去给我们的菜单层级进行分配内存,同时通过指针的树状思绪去指向下一层菜单,有点像链表的思绪,进行初始化我们的多级菜单。

  1. int MENU_Init(void)
  2. {
  3.    // 初始化一级菜单
  4.    menu.item = menu_item;
  5.    menu.item_num = sizeof(menu_item) / sizeof(menu_item[0]);
  6.    menu.cur_item = 0;
  7.    menu.menu_flag = 0;
  8. #if MENU_LEVEL >= 2
  9.    menu.sub_menu = malloc(sizeof(MENU_t));
  10.    if (menu.sub_menu != NULL)
  11.    {
  12.       menu.sub_menu->item = second_menu_item;
  13.       menu.sub_menu->item_num = sizeof(second_menu_item) / sizeof(second_menu_item[0]);
  14.       menu.sub_menu->cur_item = 0;
  15. #if MENU_LEVEL == 3
  16.       menu.sub_menu->sub1_menu = malloc(sizeof(MENU_t));
  17.       if (menu.sub_menu->sub1_menu != NULL)
  18.       {
  19.          menu.sub_menu->sub1_menu->item = third_menu_item;
  20.          menu.sub_menu->sub1_menu->item_num = sizeof(third_menu_item) / sizeof(third_menu_item[0]);
  21.          menu.sub_menu->sub1_menu->cur_item = 0; // 确保三级菜单的当前项初始化
  22.       }
  23.       else
  24.       {
  25.          // 处理内存分配失败的情况
  26.          free(menu.sub_menu); // 释放之前分配的内存
  27.          return -1;           // 返回失败状态
  28.       }
  29. #endif
  30.    }
  31.    else
  32.       return -1; // 返回失败状态
  33. #endif
  34.    return 0; // 返回成功状态
  35. }
复制代码

进入(这里我直接将当前菜单项,切换菜单层级,设置菜单下一层级菜单项等参数传进去,通过深拷贝当前菜单的内容,方便我们后面返回) 和返回菜单层级函数:

  1. //进入菜单函数
  2. 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)
  3. {
  4.         menu.cur_item = cur_item_ok;
  5.        
  6.         if(menu_flag_ok < MENU_LEVEL)
  7.         {
  8.                 prev_menu[menu_flag_ok] = malloc(sizeof(MENU_t));
  9.                 if(prev_menu[menu_flag_ok] != NULL)
  10.                 {
  11.                         *prev_menu[menu_flag_ok] = menu_ok;                        //深拷贝当前菜单内容
  12.                 }
  13.                 else
  14.                         return;
  15.         }
  16.         menu = *sub_menu_ok;                                                //切换到二级菜单
  17.         menu.cur_item = cur_item_next_ok;                        //设置当前项为0
  18.         menu.menu_flag = menu_flag_ok;                                //设置为二级菜单
  19.        
  20.         MENU_Mode_chang();
  21. }
  22. //返回菜单函数
  23. void MENU_RETURN(void)
  24. {
  25.         if(menu.menu_flag == 1)
  26.         {
  27.                 menu = *prev_menu[menu.menu_flag];                //返回到上一项
  28.                 menu.cur_item = 0;
  29.                 menu.menu_flag = 0;                                               
  30.         }
  31.         else if(menu.menu_flag == 2)
  32.         {
  33.                 menu = *prev_menu[menu.menu_flag];                //返回到上一项
  34.                 menu.cur_item = 0;
  35.                 menu.menu_flag = 1;                                               
  36.         }
  37.         MENU_Mode_chang();
  38. }
复制代码

末了附上控制代码:
  1. //菜单控制代码
  2. void MENU_KeyScan(uint8_t key)
  3. {
  4.         switch(key)
  5.         {
  6.                 case 0:
  7.                                 MENU_Mode_chang();
  8.                         break;
  9.                 case 1:
  10.                         if(menu.cur_item > 0)
  11.                         {
  12.                                 menu.cur_item--;
  13.                                 MENU_Mode_chang();
  14.                         }
  15.                         break;
  16.                 case 2:
  17.                         if(menu.menu_flag < MENU_LEVEL)
  18.                         {
  19.                                 if(menu.menu_flag == 0 )
  20.                                 {
  21.                                         MENU_INPUT(NULL,menu,menu.sub2_menu,0,1);                //进入二级菜单
  22.                                 }
  23.                                 else if(menu.menu_flag == 1 )
  24.                                 {
  25.                                         MENU_INPUT(NULL,menu,menu.sub3_menu,0,2);                //进入三级菜单
  26.                                 }
  27.                         }
  28.                         break;
  29.                 case 3:
  30.                         menu.item[menu.cur_item].func();        //执行当前项的回调函数
  31.                         break;
  32.                 case 4:
  33.                         MENU_RETURN();
  34.                         break;
  35.                 case 5:
  36.                         if(menu.cur_item < menu.item_num -1)
  37.                         {
  38.                                 menu.cur_item++;
  39.                                 MENU_Mode_chang();
  40.                         }
  41.                         break;
  42.                 default :
  43.                         break;
  44.         }
  45. }
复制代码
我们只必要在我们单片机的main中进行调用我们的控制函数就可以实现功能(这边写的简单了点,想要深层编写可看我B站视频,出了三期讲授手把手从0带你写出来,大概私信我,给你份更加美满的讲授文档。)

结果展示与讲授视频(OLED与TFT屏幕,RGB触摸移动(没录视频)):
BI架构菜单编写(一)哔哩哔哩bilibili
BI架构菜单编写(二)哔哩哔哩bilibili
BI架构菜单编写(三)哔哩哔哩bilibili

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

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

本版积分规则

农民

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表