尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

LVGL进阶:改造更加节省内存的roller(滚轮)

LVGL进阶:改造更加节省内存的roller(滚轮)
📅 发布时间:2026/8/3 21:10:18

1.改造原因

LVGL内置的roller控件,在生成时申请比较多的内存,特别是无限模式时,需要7倍的文本选项内存。这是比较浪费的行为,违背了lvgl小巧/节约的初衷。

roller还有个麻烦之处,如果想要数字序列选项,还得写一个长长的字符串,比如要一个表示年份的roller,可能要写1900-2099的列表字符串,太过于低效而无聊。这点完全可以使用更加便捷的方式替代。虽然spinbox可以替代数字选项功能,但是用户使用上spinbox没有roller方便直观。

2.思考

原则上,滚轮只需要申请当前需要显示的内容 以及即将显示的上下两项。查看源码,LVGL的roller,内部生成label控件,通过移动label实现滚动。这个label占据了大量内存。

要想节省内存,完全可以不生成label,而是临时绘制各个选项,当滑动时,获取当前触点y坐标并记录下来,根据滑动的距离实时绘制文本。考虑到有项数多的情况,松开手后,根据松手前的速度,添加一个动画继续前进一段距离。

3.代码

自定义控件,名为numroller,也支持传统方式创建。

/** * Create the num roller * @param parent pointer to a object parent * @param min min value * @param max max value * @param digitbase 进制 比如:10=10进制,特殊0/1=汉字 * @param alignnum 对齐数目,比如=3 则 12->012 */ lv_obj_t* lv_numroller_create(lv_obj_t* parent, int min ,int max, char digitbase, uint8_t alignnum); /** * Create the string roller 类似于roller的方式创建 * @param parent pointer to a object parent * @param strlist string ,以 '\n'为分割符 */ lv_obj_t* lv_numroller_create_str(lv_obj_t* parent, const char* strlist);

定义控件类

/********************* * DEFINES *********************/ #define PRIVATE #define NUMROLLER_digitbase_HAN_SER 0 //汉字数列 ,比如二〇〇四 #define NUMROLLER_digitbase_HAN_NUM 1 //汉字数字 ,只能处理<10000的数 #define _NUMROLLER_digitbase_STR 0xff //使用字符列表项,与内置roller相同的功能 #define NUMROLLER_digitAlign_NO 0 //不对齐 /********************** * TYPEDEFS **********************/ /*Data of NUMROLLER*/ typedef struct { PRIVATE lv_obj_t obj; int minvalue; //最小值 int maxvalue; //最大值 char* unit; //每一项的后缀,当使用字符型项时,用于记录strlist int16_t sel_id; //当前选项 PRIVATE uint16_t show_num_limit; PRIVATE lv_coord_t opt_h; PRIVATE lv_coord_t sel_opt_h; PRIVATE lv_coord_t ypos; PRIVATE lv_point_t prpt ; PRIVATE lv_coord_t old_py; PRIVATE lv_coord_t sel_lsp; uint16_t *strlistlens; //当使用字符型项时,记录长度 ,minvalue=0,maxvalue=项目数量-1 PRIVATE int8_t anim_dir; uint8_t digitbase; //数据进制 ,特殊值: 0=中文序列 ,1=中文数字(带十百千万) uint8_t digitAlignNum; //数字对齐 PRIVATE int8_t init; uint8_t move; //滚动指示,用于判断sel_id的有效性 } lv_numroller_t;

主要内容是默认回调函数。

static void lv_numroller_event(const lv_obj_class_t* class_p, lv_event_t* e) { LV_UNUSED(class_p); lv_res_t res; /*Call the ancestor's event handler*/ res = lv_obj_event_base(MY_CLASS, e); if (res != LV_RES_OK) return; lv_obj_t* obj = lv_event_get_target(e); lv_numroller_t* roller = (lv_numroller_t*)obj; if (e->code == LV_EVENT_DRAW_MAIN) { lv_draw_ctx_t* draw_ctx = lv_event_get_draw_ctx(e); const lv_area_t content_area; lv_obj_get_content_coords(obj, &content_area); if (roller->opt_h == 0 || roller->init == 0) { roller->init = 1; lv_coord_t h = content_area.y2 - content_area.y1 + 1; lv_font_t* font = lv_obj_get_style_text_font(obj, LV_PART_MAIN); roller->opt_h = font->line_height + lv_obj_get_style_text_line_space(obj, LV_PART_MAIN); lv_font_t* selft = lv_obj_get_style_text_font(obj, LV_PART_SELECTED); roller->sel_lsp = lv_obj_get_style_text_line_space(obj, LV_PART_SELECTED); if (selft) { roller->sel_opt_h = selft->line_height + roller->sel_lsp; } else { roller->sel_opt_h = roller->opt_h; } roller->show_num_limit = (h + (roller->opt_h - 1)) / roller->opt_h +2 ; roller->ypos = get_ypos(roller, h, roller->sel_opt_h - roller->sel_lsp); } lv_coord_t midy = content_area.y1 + (content_area.y2 - content_area.y1 + 1) / 2; lv_area_t area; area.x1 = content_area.x1; area.x2 = content_area.x2; #if 1 { //sel opt area.y1 = midy - roller->sel_opt_h / 2; area.y2 = midy + roller->sel_opt_h / 2; lv_draw_rect_dsc_t draw_dsc; lv_draw_rect_dsc_init(&draw_dsc); lv_obj_init_draw_rect_dsc(obj, LV_PART_MAIN | LV_PART_SELECTED, &draw_dsc); lv_draw_rect(draw_ctx, &draw_dsc, &area); //sel } #endif lv_draw_label_dsc_t label_dsc; lv_draw_label_dsc_init(&label_dsc); lv_obj_init_draw_label_dsc(obj, LV_PART_MAIN, &label_dsc); int offset; { lv_coord_t mpy = roller->prpt.y - roller->old_py; if (mpy > 0) { offset = (mpy + roller->opt_h / 2) / roller->opt_h; } else { offset = (mpy - roller->opt_h / 2) / roller->opt_h; } } char* bufmid; char* bufstart = get_opt_str_align(roller , roller->sel_id - ( roller->show_num_limit -1 ) / 2 - offset, roller->sel_id - offset, &bufmid); if (bufstart == 0)return; //y文字滑动 area.y1 = content_area.y1 + roller->ypos + roller->prpt.y - roller->old_py - offset * roller->opt_h; area.y2 = content_area.y2; #if 1 lv_area_t mask ; lv_area_t a1; { a1.x1 = area.x1; a1.x2 = area.x2; a1.y1 = content_area.y1; a1.y2 = midy - roller->sel_opt_h / 2; #if LV_DRAW_COMPLEX //遮罩,因为是直接绘制,而不是使用子控件,所以需要对圆角外内容去除 int16_t mask_id; lv_draw_mask_radius_param_t* xmp; lv_coord_t rad = lv_obj_get_style_radius(obj, LV_PART_MAIN); if (rad > 1) { lv_draw_mask_radius_param_t mp; xmp = &mp; lv_draw_mask_radius_init(&mp, &obj->coords, rad, false); mask_id = lv_draw_mask_add(&mp, 0); } #endif //@XXX 求区域交集并非多余,不使用此法,会导致重复绘制而字体加重 if (_lv_area_intersect(&mask , draw_ctx->clip_area, &a1)) { //修改绘制区域,而不是文字区域,这样比较方便定位 const lv_area_t* old = draw_ctx->clip_area; draw_ctx->clip_area = &mask; lv_draw_label(draw_ctx, &label_dsc, &area, bufstart, NULL); draw_ctx->clip_area = old; } a1.y1 = midy + roller->sel_opt_h / 2; a1.y2 = content_area.y2; if (_lv_area_intersect(&mask, draw_ctx->clip_area, &a1)) { const lv_area_t* old = draw_ctx->clip_area; draw_ctx->clip_area = &mask; if (roller->sel_opt_h == roller->opt_h) { lv_draw_label(draw_ctx, &label_dsc, &area, bufstart, NULL); } else { lv_coord_t y = area.y1; area.y1 += (roller->show_num_limit-1 ) / 2 * (roller->opt_h) + (roller->sel_opt_h- roller->opt_h) ; lv_draw_label(draw_ctx, &label_dsc, &area, bufmid, NULL); area.y1 = y; } draw_ctx->clip_area = old; } #if LV_DRAW_COMPLEX if (rad > 1) { lv_draw_mask_free_param(xmp); lv_draw_mask_remove_id(mask_id); } #endif } lv_obj_init_draw_label_dsc(obj, LV_PART_SELECTED, &label_dsc); a1.y1 = midy - roller->sel_opt_h / 2 ; a1.y2 = midy + roller->sel_opt_h / 2; if (_lv_area_intersect(&mask, draw_ctx->clip_area, &a1)) { //sel opt txt const lv_area_t* clip_area_ori = draw_ctx->clip_area; draw_ctx->clip_area = &mask; if (roller->sel_opt_h == roller->opt_h) { lv_draw_label(draw_ctx, &label_dsc, &area, bufstart, NULL); } else { sel_id_to_str_align(roller , roller->sel_id - offset, bufstart); a1.y1 += roller->sel_lsp / 2; lv_draw_label(draw_ctx, &label_dsc, &a1, bufstart, NULL); } draw_ctx->clip_area = clip_area_ori; } #endif lv_mem_buf_release(bufstart); } else if (e->code == LV_EVENT_PRESSED) { roller->move = 1; lv_anim_del(obj, set_y_anim ); roller->anim_dir = 0; lv_indev_get_point( lv_indev_get_act(), &roller->prpt ); roller->old_py = roller->prpt.y; } else if (e->code == LV_EVENT_PRESSING) { lv_indev_t* indev = lv_indev_get_act(); lv_point_t p; lv_indev_get_vect(indev, &p); if (p.y) { lv_indev_get_point(indev, &roller->prpt); lv_obj_invalidate(obj); } } else if (e->code == LV_EVENT_RELEASED || e->code == LV_EVENT_PRESS_LOST) { int cyc = roller->maxvalue - roller->minvalue + 1; lv_coord_t mpy = roller->prpt.y - roller->old_py; if (mpy == 0) { if (roller->sel_id >= cyc) roller->sel_id -= cyc; else if (roller->sel_id < 0) roller->sel_id += cyc; roller->move = 0; return; } if (mpy>0) { roller->sel_id -= (mpy + roller->opt_h / 2) / roller->opt_h; } else { roller->sel_id -= (mpy - roller->opt_h / 2) / roller->opt_h; } while (roller->sel_id < 0) roller->sel_id += cyc; while (roller->sel_id >= cyc) roller->sel_id -= cyc; lv_point_t p; lv_indev_get_vect(lv_indev_get_act(), &p); if (p.y > 1)roller->anim_dir = 1;else if (p.y < -1 )roller->anim_dir = -1; if (roller->anim_dir) { lv_coord_t time = p.y>0? p.y:-p.y; if (time > 100 )time=100; lv_anim_t a; lv_anim_init(&a); lv_anim_set_var(&a, obj); lv_anim_set_exec_cb(&a, set_y_anim ); lv_anim_set_values(&a, roller->sel_id, roller->sel_id + p.y ); lv_anim_set_time(&a, time*10 ); lv_anim_set_ready_cb(&a, scroll_anim_ready_cb); //lv_anim_set_path_cb(&a, lv_anim_path_ease_out); lv_anim_start(&a); } else { roller->move = 0; } roller->prpt.y = 0; roller->old_py = 0; lv_obj_invalidate(obj); } else if (e->code == LV_EVENT_SIZE_CHANGED || e->code==LV_EVENT_STYLE_CHANGED) { roller->init = 0; //lv_obj_invalidate(obj); } }

4.效果

经测试,相同的UI,内存使用大约只相当于内置roller的1/3.

相关新闻

  • 2026最新!初中生必看的5款好用英语听力APP推荐
  • EdgeRemover:三步彻底卸载Microsoft Edge的终极开源工具,释放Windows系统性能
  • 学术写作工具选型指南:什么时候选择降重,什么时候需要降 AIGC - 爱学习的肖博

最新新闻

  • 彻底解决Flash Attention安装失败:CUDA环境与编译错误全解析
  • 2026岳阳政企宣传片制作公司排行榜TOP5 | 党建宣传片 | 政府汇报片 | 会议拍摄 | 视频直播 | 招商宣传片服务商评测对比 - 政企影像扫地僧
  • aardio动态控件与网格布局实战:实现数据驱动的高效桌面界面
  • Android 13屏幕亮度调整:利用Overlay机制定制背光参数
  • 如何利用ManiSkill GPU并行仿真平台优化机器人学习性能
  • 中小微企业免费进销存软件选型指南:五款主流工具深度横评

日新闻

  • 112、LLC谐振变换器的输入电压瞬态仿真分析
  • 2026深圳疑难签证办理指南:拒签再签/商务签/高端定制机构怎么选 - 互联网科技品牌测评
  • C-LODOP在Edge等现代浏览器中的部署、适配与实战应用

周新闻

  • 怀化母婴除甲醛公司测甲醛中心怎么选:康之居母婴除甲醛标准、流程、避坑指南 - 信誉隆金银铂奢回收
  • 三步打造你的终极音乐中心:foobox-cn网络电台功能完整指南
  • Lance湖仓格式:为多模态AI工作流设计的终极数据存储方案

月新闻

  • ClickHouse版本管理深度实战:4步构建零风险升级与回滚体系
  • Java 23 种设计模式:从踩坑到精通 | 番外:责任链模式 —— 物流审批流程实战
  • 华硕笔记本性能解放指南:G-Helper轻量级控制工具全面解析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号