LVGL仪表盘逆时针
背景
LVGL 8.3 自带的lv_meter控件,目前只支持顺时针显示,但是,项目上刚好用到了逆时针。官网论坛有讨论通过arc + lv_meter 组合的方式实现。这个方法在模拟器上可以,但是放到板子上,刻度显示不出来,只显示一个完整的弧,这不是我要的效果,具体为何如此,深入研究了一番,没找到原因。唯一的差别在模拟器使用的SDL,板子使用的FB。
思路
鉴于arc + lv_meter 组合的方案没有通过,转而换个思路。由于arc是支持逆时针的,那就先研究arc的实现方式,然后在lv_meter代码中修改。按照这个思路,又研究了一番源码,找到了方法。
实现
lv_meter_indicator_t
typedef struct
{
lv_meter_scale_t *scale;
lv_meter_indicator_type_t type;
lv_opa_t opa;
int32_t start_value;
int32_t end_value;
union
{
// ... ...
struct
{
uint16_t width;
const void *src;
lv_color_t color;
int16_t r_mod;
uint16_t mode; // 0: clockwise; 1: counter clockwise
} arc;
// ... ...
} type_data;
} lv_meter_indicator_t;
增加mode字段,用于描述顺时针还是逆时针。
lv_meter_add_arc
lv_meter_indicator_t *lv_meter_add_arc(lv_obj_t *obj, lv_meter_scale_t *scale, uint16_t width, lv_color_t color,
int16_t r_mod, uint16_t mode)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_meter_t *meter = (lv_meter_t *)obj;
lv_meter_indicator_t *indic = _lv_ll_ins_head(&meter->indicator_ll);
LV_ASSERT_MALLOC(indic);
lv_memset_00(indic, sizeof(lv_meter_indicator_t));
indic->scale = scale;
indic->opa = LV_OPA_COVER;
indic->type = LV_METER_INDICATOR_TYPE_ARC;
indic->type_data.arc.width = width;
indic->type_data.arc.color = color;
indic->type_data.arc.r_mod = r_mod;
indic->type_data.arc.mode = mode;
lv_obj_invalidate(obj);
return indic;
}
增加输入参数,用于配置mode。
draw_arcs
static void draw_arcs(lv_obj_t *obj, lv_draw_ctx_t *draw_ctx, const lv_area_t *scale_area)
{
// ... ...
_LV_LL_READ_BACK(&meter->indicator_ll, indic)
{
lv_meter_scale_t *scale = indic->scale;
int32_t start = scale->rotation;
int32_t end = scale->rotation + scale->angle_range;
int32_t start_angle;
int32_t end_angle;
if (indic->type_data.arc.mode == 0)
{
start_angle = lv_map(indic->start_value, scale->min, scale->max, start, end);
end_angle = lv_map(indic->end_value, scale->min, scale->max, start, end);
}
else
{
start_angle = lv_map(indic->start_value, scale->min, scale->max, end, start);
end_angle = lv_map(indic->end_value, scale->min, scale->max, end, start);
}
arc_dsc.start_angle = start_angle;
arc_dsc.end_angle = end_angle;
part_draw_dsc.radius = r_out + indic->type_data.arc.r_mod;
part_draw_dsc.sub_part_ptr = indic;
part_draw_dsc.p1 = &scale_center;
lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc);
if (indic->type_data.arc.mode == 0)
{
lv_draw_arc(draw_ctx, &arc_dsc, &scale_center, part_draw_dsc.radius, start_angle, end_angle);
}
else
{
lv_draw_arc(draw_ctx, &arc_dsc, &scale_center, part_draw_dsc.radius, end_angle, start_angle);
}
lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc);
}
// ... ...
}
根据mode值,进行不同设置。
inv_arc
static void inv_arc(lv_obj_t *obj, lv_meter_indicator_t *indic, int32_t old_value, int32_t new_value)
{
bool rounded = lv_obj_get_style_arc_rounded(obj, LV_PART_ITEMS);
lv_area_t scale_area;
lv_obj_get_content_coords(obj, &scale_area);
lv_coord_t r_out = lv_area_get_width(&scale_area) / 2;
lv_point_t scale_center;
scale_center.x = scale_area.x1 + r_out;
scale_center.y = scale_area.y1 + r_out;
r_out += indic->type_data.arc.r_mod;
lv_meter_scale_t *scale = indic->scale;
int32_t start = scale->rotation;
int32_t end = scale->rotation + scale->angle_range;
int32_t start_angle;
int32_t end_angle;
if (indic->type_data.arc.mode == 0)
{
start_angle = lv_map(old_value, scale->min, scale->max, start, end);
end_angle = lv_map(new_value, scale->min, scale->max, start, end);
}
else
{
start_angle = lv_map(old_value, scale->min, scale->max, end, start);
end_angle = lv_map(new_value, scale->min, scale->max, end, start);
}
lv_area_t a;
lv_draw_arc_get_area(scale_center.x, scale_center.y, r_out, LV_MIN(start_angle, end_angle), LV_MAX(start_angle, end_angle), indic->type_data.arc.width, rounded, &a);
lv_obj_invalidate_area(obj, &a);
}
根据mode值,进行不同设置。