// 电池指示器模块 // 屏幕顶部药丸容器 + 电池图标(LVGL基元绘制) // 仅 Home/Img 界面使用,每次进入界面或切图时显示2秒后渐隐消失 #include "ui.h" #include "battery_ui.h" // === 刘海容器参数(小药丸形,悬浮) === #define NOTCH_W 50 #define NOTCH_H 20 #define NOTCH_Y 8 #define NOTCH_RADIUS 10 #define NOTCH_BG_COLOR 0x000000 #define NOTCH_BG_OPA 255 // === 电池图标参数 === #define BAT_W 24 #define BAT_H 12 #define BAT_BORDER 2 #define BAT_RADIUS 3 #define BAT_OUTLINE_CLR 0xBBBBBB #define BAT_CAP_W 3 #define BAT_CAP_H 6 #define BAT_CAP_RADIUS 1 #define BAT_PAD 1 // === 填充条参数 === #define FILL_MAX_W (BAT_W - 2 * BAT_BORDER - 2 * BAT_PAD) #define FILL_H (BAT_H - 2 * BAT_BORDER - 2 * BAT_PAD) #define FILL_RADIUS 1 // === 颜色 === #define BAT_COLOR_GREEN 0x4CAF50 #define BAT_COLOR_RED 0xF44336 #define BAT_LOW_THRESH 20 // === 显示时间和动画参数 === #define SHOW_DURATION_MS 2000 #define FADE_DURATION_MS 500 // === 容器跟踪 === #define MAX_SLOTS 2 typedef struct { lv_obj_t *notch; lv_obj_t *fill; int fill_x; int fill_y; } bat_slot_t; static bat_slot_t slots[MAX_SLOTS]; static int slot_count = 0; static lv_timer_t *hide_timer = NULL; static int cached_level = 100; // === 渐隐动画回调 === static void fade_anim_cb(void *obj, int32_t value) { lv_obj_set_style_opa((lv_obj_t *)obj, (lv_opa_t)value, 0); } static void start_fade_out(lv_obj_t *notch) { lv_anim_t a; lv_anim_init(&a); lv_anim_set_var(&a, notch); lv_anim_set_values(&a, LV_OPA_COVER, LV_OPA_TRANSP); lv_anim_set_time(&a, FADE_DURATION_MS); lv_anim_set_exec_cb(&a, fade_anim_cb); lv_anim_start(&a); } static void hide_timer_cb(lv_timer_t *timer) { for (int i = 0; i < slot_count; i++) { if (lv_obj_is_valid(slots[i].notch)) { start_fade_out(slots[i].notch); } } lv_timer_del(timer); hide_timer = NULL; } void battery_ui_add_to_screen(lv_obj_t *screen, int level) { if (level < 0) level = 0; if (level > 100) level = 100; cached_level = level; if (slot_count >= MAX_SLOTS) return; // 1. 药丸容器(初始隐藏) lv_obj_t *notch = lv_obj_create(screen); lv_obj_remove_style_all(notch); lv_obj_set_size(notch, NOTCH_W, NOTCH_H); lv_obj_set_align(notch, LV_ALIGN_TOP_MID); lv_obj_set_y(notch, NOTCH_Y); lv_obj_set_style_bg_color(notch, lv_color_hex(NOTCH_BG_COLOR), 0); lv_obj_set_style_bg_opa(notch, NOTCH_BG_OPA, 0); lv_obj_set_style_radius(notch, NOTCH_RADIUS, 0); lv_obj_set_style_border_width(notch, 0, 0); lv_obj_clear_flag(notch, LV_OBJ_FLAG_SCROLLABLE | LV_OBJ_FLAG_CLICKABLE); lv_obj_set_style_opa(notch, LV_OPA_TRANSP, 0); // 2. 电池主体 int bat_total_w = BAT_W + BAT_CAP_W; int bat_x = (NOTCH_W - bat_total_w) / 2; int bat_y = (NOTCH_H - BAT_H) / 2; lv_obj_t *body = lv_obj_create(notch); lv_obj_remove_style_all(body); lv_obj_set_size(body, BAT_W, BAT_H); lv_obj_set_pos(body, bat_x, bat_y); lv_obj_set_style_bg_opa(body, LV_OPA_TRANSP, 0); lv_obj_set_style_border_color(body, lv_color_hex(BAT_OUTLINE_CLR), 0); lv_obj_set_style_border_width(body, BAT_BORDER, 0); lv_obj_set_style_radius(body, BAT_RADIUS, 0); lv_obj_clear_flag(body, LV_OBJ_FLAG_SCROLLABLE | LV_OBJ_FLAG_CLICKABLE); // 3. 电池帽 lv_obj_t *cap = lv_obj_create(notch); lv_obj_remove_style_all(cap); lv_obj_set_size(cap, BAT_CAP_W, BAT_CAP_H); lv_obj_set_pos(cap, bat_x + BAT_W, bat_y + (BAT_H - BAT_CAP_H) / 2); lv_obj_set_style_bg_color(cap, lv_color_hex(BAT_OUTLINE_CLR), 0); lv_obj_set_style_bg_opa(cap, LV_OPA_COVER, 0); lv_obj_set_style_radius(cap, BAT_CAP_RADIUS, 0); lv_obj_set_style_border_width(cap, 0, 0); lv_obj_clear_flag(cap, LV_OBJ_FLAG_SCROLLABLE | LV_OBJ_FLAG_CLICKABLE); // 4. 电量填充条 int fill_x = bat_x + BAT_BORDER + BAT_PAD; int fill_y = bat_y + BAT_BORDER + BAT_PAD; int fill_w = FILL_MAX_W * level / 100; uint32_t fill_color = (level <= BAT_LOW_THRESH) ? BAT_COLOR_RED : BAT_COLOR_GREEN; lv_obj_t *fill = lv_obj_create(notch); lv_obj_remove_style_all(fill); lv_obj_set_size(fill, (fill_w > 0) ? fill_w : 0, FILL_H); lv_obj_set_pos(fill, fill_x, fill_y); lv_obj_set_style_bg_color(fill, lv_color_hex(fill_color), 0); lv_obj_set_style_bg_opa(fill, (fill_w > 0) ? LV_OPA_COVER : LV_OPA_TRANSP, 0); lv_obj_set_style_radius(fill, FILL_RADIUS, 0); lv_obj_set_style_border_width(fill, 0, 0); lv_obj_clear_flag(fill, LV_OBJ_FLAG_SCROLLABLE | LV_OBJ_FLAG_CLICKABLE); slots[slot_count].notch = notch; slots[slot_count].fill = fill; slots[slot_count].fill_x = fill_x; slots[slot_count].fill_y = fill_y; slot_count++; } void battery_ui_show_briefly(void) { int fill_w = FILL_MAX_W * cached_level / 100; uint32_t fill_color = (cached_level <= BAT_LOW_THRESH) ? BAT_COLOR_RED : BAT_COLOR_GREEN; // 清理已销毁的slot int valid = 0; for (int i = 0; i < slot_count; i++) { if (lv_obj_is_valid(slots[i].notch)) { slots[valid] = slots[i]; valid++; } } slot_count = valid; // 取消渐隐动画并立即显示 for (int i = 0; i < slot_count; i++) { lv_obj_t *notch = slots[i].notch; lv_anim_del(notch, fade_anim_cb); lv_obj_set_style_opa(notch, LV_OPA_COVER, 0); lv_obj_t *fill = slots[i].fill; if (lv_obj_is_valid(fill)) { lv_obj_set_size(fill, (fill_w > 0) ? fill_w : 0, FILL_H); lv_obj_set_style_bg_color(fill, lv_color_hex(fill_color), 0); lv_obj_set_style_bg_opa(fill, (fill_w > 0) ? LV_OPA_COVER : LV_OPA_TRANSP, 0); } } // 重置定时器 if (hide_timer) { lv_timer_del(hide_timer); } hide_timer = lv_timer_create(hide_timer_cb, SHOW_DURATION_MS, NULL); lv_timer_set_repeat_count(hide_timer, 1); } void battery_ui_update_level(int level) { if (level < 0) level = 0; if (level > 100) level = 100; cached_level = level; }