首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

BI架构(多级菜单与UI)

  • 25-03-04 23:22
  • 2189
  • 13904
blog.csdn.net

是否还在因为移植一些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. 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
  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
  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. }

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

我们这时候的图片分配内存,直接上三张图片,一张左移,一张右移,一张显示,一样的通过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. 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. }

初始化我们的菜单:

一级菜单的时候,就只需要初始化一级菜单的菜单层级,菜单子项个数,菜单当前项,并且初始化菜单系统(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. 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

注:本文转载自blog.csdn.net的built_in的文章"https://blog.csdn.net/m0_69390033/article/details/143793144"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2491) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

130
用户体验设计
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top