0


BI架构(多级菜单与UI)

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

是否还在考虑屏幕菜单的实现可行性?

这次介绍我自己写的多级菜单与UI显示(同B站built_in的BI架构教学)。

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

思路引导:

  1. 使用的是数组指针结构体的思路,主要是使用结构体去存储菜单子项与菜单层级的一些区分标志,然后使用指针去指向当前结构体下的结构体,类似于链表的思路,然后通过这个结构体数组去存储我们的菜单层,通过调用**菜单层,菜单子项与菜单指向等等以及回调函数**,能够形成一个简易的菜单结构,并且与文字显示与图片显示绑定。

建立我的菜单项:

  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. OLED_Clear(); //清空屏幕
  10. static uint8_t old_item=0; //旧选项框参数
  11. uint8_t old_y = y + old_item * OLED_Rectangle_Hight; //旧选项框坐标
  12. uint8_t new_y = y + menu.cur_item * OLED_Rectangle_Hight; //新选项框坐标
  13. uint8_t visiable_items = (menu.item_num<3) ? menu.item_num :3; //动态设置屏幕可见菜单子项数量
  14. uint8_t start_index = (menu.cur_item<2) ? 0 :menu.cur_item - 2; //根据当前选项计算起始项
  15. if(start_index + visiable_items > menu.item_num) //防止越界
  16. start_index = menu.item_num - visiable_items;
  17. for(i=0;i<visiable_items;i++)
  18. {
  19. if(start_index + i < menu.item_num) //防止越界
  20. {
  21. sprintf((char*)&str,"%s",menu.item[start_index + i].name);
  22. OLED_ShowString(x+5,y+i* OLED_Rectangle_Hight+2,str,OLED_8X16); //绘制首次菜单项
  23. }
  24. }
  25. OLED_DrawRectangle(x,new_y,OLED_Width,OLED_Rectangle_Hight,OLED_UNFILLED); //绘制选项框
  26. if(menu.cur_item >=3)
  27. {
  28. pos = 26; //选项框固定
  29. menu_display = 1; //显示大于屏幕项标志位置1
  30. }
  31. if(old_item != menu.cur_item)
  32. {
  33. for(pos= old_y; pos!= new_y;pos += (new_y>old_y) ? 2:-2)
  34. {
  35. OLED_Clear();
  36. for(i=0;i<visiable_items;i++)
  37. {
  38. if(start_index + i < menu.item_num) //防止越界
  39. {
  40. sprintf((char*)&str,"%s",menu.item[start_index + i].name);
  41. OLED_ShowString(x+5,y+i* OLED_Rectangle_Hight+2,str,OLED_8X16); //绘制首次菜单项
  42. }
  43. }
  44. if(menu_display == 1)
  45. OLED_DrawRectangle(x,pos - (OLED_Rectangle_Hight *(menu.cur_item -2)),OLED_Width,OLED_Rectangle_Hight,OLED_UNFILLED); //固定大于显示项在第三个显示
  46. else
  47. {
  48. OLED_DrawRectangle(x,pos,OLED_Width,OLED_Rectangle_Hight,OLED_UNFILLED); //动态显示移动框
  49. OLED_Update();
  50. }
  51. delay_ms(20);
  52. }
  53. old_item = menu.cur_item; //交换显示项
  54. }
  55. OLED_Update();
  56. }

当然,也有图片显示的函数,

我们这时候的图片分配内存,直接上三张图片,一张左移,一张右移,一张显示,一样的通过

  1. old_item != menu.cur_item

作为刷新条件,在通过

  1. menu.cur_item - old_item

的差值,看看是左移(菜单项- - ),右移(菜单项++),然后可以通过刷新屏幕左右移动的位置,就可以实现简单的移动特效。而我们的显示图片又是与菜单子项挂钩的,所以直接

  1. 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. if(menu.menu_flag == 0)
  11. image_data = image_data_level_1;//一级菜单
  12. else if(menu.menu_flag == 1)
  13. image_data = image_data_level_2;//二级菜单
  14. else if(menu.menu_flag ==2)
  15. image_data = image_data_level_3;//三级菜单
  16. //计算图片数据大小
  17. size_t image_data_size;
  18. if(menu.menu_flag == 0)
  19. image_data_size = sizeof(image_data_level_1) / sizeof(image_data_level_1[0]);
  20. else if(menu.menu_flag == 1)
  21. image_data_size = sizeof(image_data_level_2) / sizeof(image_data_level_2[0]);
  22. else if(menu.menu_flag == 2)
  23. image_data_size = sizeof(image_data_level_3) / sizeof(image_data_level_3[0]);
  24. if(iamge_index < image_data_size)
  25. {
  26. OLED_ShowImage(32,0,64,64,image_data[iamge_index]); //显示图片项
  27. OLED_ShowImage(0,16,32,32,left); //显示左移箭头
  28. OLED_ShowImage(96,16,32,32,right); //显示右移箭头
  29. OLED_Update();
  30. if(old_item != menu.cur_item)
  31. {
  32. if(menu.cur_item - old_item < 0 ) //刷新一下左移箭头
  33. {
  34. OLED_ClearArea(0,16,32,32);
  35. OLED_Update();
  36. delay_ms(200);
  37. OLED_ShowImage(0,16,32,32,left);
  38. }
  39. else if(menu.cur_item - old_item > 0 ) //刷新一下右移箭头
  40. {
  41. OLED_ClearArea(96,16,32,32);
  42. OLED_Update();
  43. delay_ms(200);
  44. OLED_ShowImage(96,16,32,32,right);
  45. }
  46. OLED_Update();
  47. }
  48. old_item = menu.cur_item; //保存上一次选项
  49. }
  50. else
  51. return;
  52. }

初始化我们的菜单:

一级菜单的时候,就只需要初始化一级菜单的菜单层级菜单子项个数菜单当前项,并且初始化菜单系统

  1. menu.item = menu_item;

)。我通过预处理命令,让我们上面定义的宏能够进行设置我们的多级菜单初始化,在第二,第三层菜单,我们可以通过

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

最后附上控制代码:

  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

标签: 架构 ui

本文转载自: https://blog.csdn.net/m0_69390033/article/details/143793144
版权归原作者 built_in 所有, 如有侵权,请联系我们删除。

“BI架构(多级菜单与UI)”的评论:

还没有评论