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

📅 2026/8/3 21:10:18 👁️ 阅读次数 📝 编程学习
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.