当前位置: 首页 > news >正文

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

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.

http://www.jsqmd.com/news/1322300/

相关文章:

  • 汕头招聘软件哪个好:【帅聘网】专业一流 - 17328623207
  • 学术写作工具选型指南:什么时候选择降重,什么时候需要降 AIGC - 爱学习的肖博
  • 汕头招聘软件哪个好:【帅聘网】专业出众 - 17328623207
  • 黑苹果实战终极指南:17个品牌兼容性+工具链完整解析
  • 2026 年现阶段南通知名的石雕石栏杆企业深度解析,小区围墙换上这玩意儿,居然比铁栏杆安全10倍还不贵?-辉耀石材 - 企业推荐管【认证】
  • 2026咸阳活动拍摄公司排行榜TOP5 | 会议拍摄 | 活动跟拍 | 视频直播 | 照片直播 | 年会拍摄服务商评测对比 - 政企影像扫地僧
  • C++实战:从零构建命令行文字冒险游戏“黑森林”
  • Avue动态字典加载:基于rowCell实现参数化字典数据刷新
  • 如何用 Mordecai 实现全文地理解析:从文本中提取地理信息的终极指南
  • 2026年SCR脱硝尿素溶液添加剂实力供应商:青岛中创环保设备有限公司的专业定位与选型逻辑 - 卓企推荐
  • 终极指南:ObjToSchematic将3D模型完美转换为Minecraft结构的完整教程
  • 从零实现C++小游戏:掌握游戏循环、面向对象与SFML应用
  • Pytest间接参数(indirect)详解:数据驱动与复杂环境准备的优雅解耦
  • 汕头招聘软件哪个好:【帅聘网】周到细致 - 18002239949
  • 天河网红装机店!百万粉丝抖音门店,ROG 全家桶现场装机全程看得见 - GrowUME
  • ChatGPT与PlantUML结合:自动化生成架构图的高效工作流
  • 2026年深圳军工PCB板源头厂家选型解析 - 优企名品
  • 港人深圳定制家私跨境全包:报价单背后的隐性成本专业解读 - 产品测评官
  • 2026娄底企业宣传片制作公司排行榜TOP5 | 品牌形象片 | 产品宣传片 | 招商宣传片 | TVC广告 | 企业年会片服务商评测对比 - 政企影像扫地僧
  • 计算机组成原理定点运算精讲:从补码到Booth算法实战解析
  • DFMEA系统分析实战:从功能拆解到失效预防的完整指南
  • Swiftlane插件开发教程:打造自定义自动化任务的完整指南
  • OpenSees完全指南:从入门到精通的结构工程有限元分析平台
  • 【华为OD机试真题 新系统】1066、语义化版本号对比 | 机试真题+思路参考+代码解析(C++、Java、Py、C语言、JS)
  • HandyControl导航控件终极指南:5种高效WPF导航方案详解
  • 如何5分钟掌握植物大战僵尸修改器:PvZ Toolkit终极免费开源工具指南
  • 10分钟训练专属AI声优:RVC变声器完全指南
  • 突破Blender渲染瓶颈:BlendLuxCore如何重塑你的创作流程
  • 贪心算法与动态规划:从分数背包到0-1背包的算法抉择
  • 7步重塑你的散热体验:FanControl的颠覆性风扇控制哲学