这个 Demo 基于 LVGL 实现滑动切换主页界面,完成了背景美化、图标高亮展示、动态标题说明和按钮按压反馈等交互效果。

cpp
#include "lvgl/lvgl.h"
#define NUM_ICONS 5
#define CENTER_ZOOM 420
#define SIDE_ZOOM 150
#define FAR_ZOOM 96
#define CENTER_OPA 255
#define SIDE_OPA 102
#define FAR_OPA 48
#define ICON_WIDTH 120
#define ICON_HEIGHT 120
#define CENTER_SIZE 196
#define SIDE_SIZE 84
#define FAR_SIZE 58
#define DRAG_STEP_X 160
#define ITEM_SPACING 220
#define VISIBLE_COUNT 3
#define HOME_BG_COLOR_HEX 0x123B22
#define INFO_TEXT_COLOR_HEX 0xF6D32D
#define ICON_BG_COLOR_HEX 0x2A1240
static lv_obj_t * container;
static lv_obj_t * icons[NUM_ICONS];
static lv_obj_t * icon_symbols_obj[NUM_ICONS];
static lv_obj_t * icon_info_label;
static lv_style_t style_icon;
static lv_style_t style_icon_pressed;
static lv_style_t style_icon_release_transition;
static float scroll_offset = 0.0f;
static int pressed_x = 0;
static float pressed_offset = 0;
static bool is_dragging = false;
static int current_center_idx = -1;
static const lv_color_t icon_colors[NUM_ICONS] = {
LV_COLOR_MAKE(0x87, 0xF5, 0xC2),
LV_COLOR_MAKE(0xFF, 0xDE, 0x73),
LV_COLOR_MAKE(0xFA, 0x4F, 0x0A),
LV_COLOR_MAKE(0xF7, 0x22, 0x22),
LV_COLOR_MAKE(0xB7, 0x9D, 0xFF),
};
static const char * icon_symbols[NUM_ICONS] = {
LV_SYMBOL_WIFI,
LV_SYMBOL_BELL,
LV_SYMBOL_BATTERY_FULL,
LV_SYMBOL_AUDIO,
LV_SYMBOL_BLUETOOTH,
};
static const char * icon_names[NUM_ICONS] = {
"WiFi",
"Bell",
"Battery",
"Audio",
"Bluetooth",
};
static float clampf(float value, float min_value, float max_value)
{
if (value < min_value) return min_value;
if (value > max_value) return max_value;
return value;
}
static float absf(float value)
{
return value < 0.0f ? -value : value;
}
static float lerpf(float start, float end, float t)
{
return start + (end - start) * t;
}
static float smoothstepf(float t)
{
t = clampf(t, 0.0f, 1.0f);
return t * t * (3.0f - 2.0f * t);
}
static lv_color_t darken_icon_filter(const lv_color_filter_dsc_t * dsc, lv_color_t color, lv_opa_t opa)
{
LV_UNUSED(dsc);
return lv_color_darken(color, opa);
}
static int get_center_icon_index(void)
{
int idx = (int)(scroll_offset + 0.5f);
if (idx < 0) idx = 0;
if (idx >= NUM_ICONS) idx = NUM_ICONS - 1;
return idx;
}
static void update_icon_info_label(void)
{
int idx;
if (icon_info_label == NULL) return;
idx = get_center_icon_index();
if (idx == current_center_idx) return;
current_center_idx = idx;
lv_label_set_text(icon_info_label, icon_names[idx]);
}
static void update_stack_order(const float * scores)
{
bool used[NUM_ICONS] = { false };
for (int order = 0; order < NUM_ICONS; order++) {
int pick = -1;
float best_score = -1000000.0f;
for (int i = 0; i < NUM_ICONS; i++) {
if (used[i]) continue;
if (pick < 0 || scores[i] < best_score) {
pick = i;
best_score = scores[i];
}
}
used[pick] = true;
lv_obj_move_foreground(icons[pick]);
}
}
static void recalc_layout(void)
{
lv_disp_t * disp = lv_disp_get_default();
lv_coord_t cx = disp ? lv_disp_get_hor_res(disp) / 2 : 400;
lv_coord_t cy = disp ? lv_disp_get_ver_res(disp) / 2 : 240;
float stack_scores[NUM_ICONS];
bool visible[NUM_ICONS] = { false };
for (int i = 0; i < NUM_ICONS; i++) {
stack_scores[i] = -1000000.0f;
}
for (int shown = 0; shown < VISIBLE_COUNT; shown++) {
int pick = -1;
float best_dist = 1000000.0f;
for (int i = 0; i < NUM_ICONS; i++) {
if (visible[i]) continue;
float dist = absf((float)i - scroll_offset);
if (pick < 0 || dist < best_dist) {
pick = i;
best_dist = dist;
}
}
if (pick >= 0) visible[pick] = true;
}
for (int i = 0; i < NUM_ICONS; i++) {
if (!visible[i]) {
lv_obj_add_flag(icons[i], LV_OBJ_FLAG_HIDDEN);
continue;
}
float dist = (float)i - scroll_offset;
float abs_dist = absf(dist);
float near_t = clampf(abs_dist, 0.0f, 1.0f);
float far_t = clampf(abs_dist - 1.0f, 0.0f, 1.0f);
float size = lerpf((float)CENTER_SIZE, (float)SIDE_SIZE, near_t);
float zoom = lerpf((float)CENTER_ZOOM, (float)SIDE_ZOOM, near_t);
float opa = lerpf((float)CENTER_OPA, (float)SIDE_OPA, near_t);
if (abs_dist > 1.0f) {
size = lerpf((float)SIDE_SIZE, (float)FAR_SIZE, far_t);
zoom = lerpf((float)SIDE_ZOOM, (float)FAR_ZOOM, far_t);
opa = lerpf((float)SIDE_OPA, (float)FAR_OPA, far_t);
}
lv_coord_t icon_size = (lv_coord_t)size;
lv_coord_t x = (lv_coord_t)(cx + dist * ITEM_SPACING - icon_size / 2);
lv_coord_t y = cy - icon_size / 2;
lv_opa_t obj_opa = (lv_opa_t)opa;
lv_opa_t shadow_opa = obj_opa > 60 ? (lv_opa_t)(obj_opa - 60) : 0;
lv_obj_clear_flag(icons[i], LV_OBJ_FLAG_HIDDEN);
lv_obj_set_size(icons[i], icon_size, icon_size);
lv_obj_set_pos(icons[i], x, y);
lv_obj_set_style_bg_opa(icons[i], obj_opa, 0);
lv_obj_set_style_shadow_opa(icons[i], shadow_opa, 0);
lv_obj_set_style_transform_zoom(icon_symbols_obj[i], (lv_coord_t)zoom, 0);
lv_obj_set_style_text_opa(icon_symbols_obj[i], obj_opa, 0);
lv_obj_update_layout(icon_symbols_obj[i]);
lv_obj_set_style_transform_pivot_x(icon_symbols_obj[i], lv_obj_get_width(icon_symbols_obj[i]) / 2, 0);
lv_obj_set_style_transform_pivot_y(icon_symbols_obj[i], lv_obj_get_height(icon_symbols_obj[i]) / 2, 0);
lv_obj_center(icon_symbols_obj[i]);
stack_scores[i] = size;
}
update_stack_order(stack_scores);
if (icon_info_label != NULL) lv_obj_move_foreground(icon_info_label);
update_icon_info_label();
}
static void scroll_anim_cb(void * var, int32_t value)
{
scroll_offset = (float)value / 100.0f;
recalc_layout();
}
static void start_drag(lv_event_t * e)
{
lv_indev_t * indev = lv_event_get_indev(e);
lv_point_t point;
if (indev == NULL) indev = lv_indev_get_act();
if (indev == NULL) return;
lv_anim_del(&scroll_offset, scroll_anim_cb);
lv_indev_get_point(indev, &point);
pressed_x = point.x;
pressed_offset = scroll_offset;
is_dragging = true;
}
static void update_drag(lv_event_t * e)
{
lv_indev_t * indev = lv_event_get_indev(e);
lv_point_t point;
if (indev == NULL) indev = lv_indev_get_act();
if (indev == NULL) return;
if (!is_dragging) {
start_drag(e);
}
lv_indev_get_point(indev, &point);
int dx = point.x - pressed_x;
float delta = dx / (float)DRAG_STEP_X;
scroll_offset = pressed_offset - delta;
if (scroll_offset < 0) scroll_offset = 0;
if (scroll_offset > NUM_ICONS - 1) scroll_offset = NUM_ICONS - 1;
recalc_layout();
}
static void snap_to_center(int target_idx)
{
if (target_idx < 0) target_idx = 0;
if (target_idx >= NUM_ICONS) target_idx = NUM_ICONS - 1;
lv_anim_t anim;
lv_anim_init(&anim);
lv_anim_set_var(&anim, &scroll_offset);
lv_anim_set_values(&anim, (int)(scroll_offset * 100), target_idx * 100);
lv_anim_set_time(&anim, 300);
lv_anim_set_exec_cb(&anim, scroll_anim_cb);
lv_anim_set_path_cb(&anim, lv_anim_path_ease_out);
lv_anim_start(&anim);
}
static void event_handler(lv_event_t * e)
{
lv_event_code_t code = lv_event_get_code(e);
if (code == LV_EVENT_PRESSED) {
start_drag(e);
} else if (code == LV_EVENT_PRESSING) {
update_drag(e);
} else if (code == LV_EVENT_RELEASED || code == LV_EVENT_PRESS_LOST) {
if (!is_dragging) return;
is_dragging = false;
snap_to_center((int)(scroll_offset + 0.5f));
}
}
void demo_carousel(void)
{
static lv_style_prop_t press_transition_props[] = {
LV_STYLE_TRANSLATE_Y,
LV_STYLE_SHADOW_WIDTH,
LV_STYLE_SHADOW_OFS_Y,
LV_STYLE_SHADOW_OPA,
0
};
static lv_style_transition_dsc_t transition_dsc_def;
static lv_style_transition_dsc_t transition_dsc_pr;
static lv_color_filter_dsc_t color_filter;
lv_style_init(&style_icon);
lv_style_set_bg_color(&style_icon, lv_color_hex(ICON_BG_COLOR_HEX));
lv_style_set_bg_opa(&style_icon, 255);
lv_style_set_radius(&style_icon, LV_RADIUS_CIRCLE);
lv_style_set_border_width(&style_icon, 0);
lv_style_set_shadow_width(&style_icon, 4);
lv_style_set_shadow_color(&style_icon, lv_color_black());
lv_style_set_shadow_opa(&style_icon, 30);
lv_style_set_shadow_ofs_y(&style_icon, 2);
lv_style_set_pad_all(&style_icon, 10);
lv_style_transition_dsc_init(&transition_dsc_def, press_transition_props, lv_anim_path_ease_out, 140, 40, NULL);
lv_style_transition_dsc_init(&transition_dsc_pr, press_transition_props, lv_anim_path_ease_in_out, 90, 0, NULL);
lv_color_filter_dsc_init(&color_filter, darken_icon_filter);
lv_style_init(&style_icon_release_transition);
lv_style_set_transition(&style_icon_release_transition, &transition_dsc_def);
lv_style_init(&style_icon_pressed);
lv_style_set_translate_y(&style_icon_pressed, 4);
lv_style_set_shadow_width(&style_icon_pressed, 1);
lv_style_set_shadow_ofs_y(&style_icon_pressed, 0);
lv_style_set_shadow_opa(&style_icon_pressed, 12);
lv_style_set_color_filter_dsc(&style_icon_pressed, &color_filter);
lv_style_set_color_filter_opa(&style_icon_pressed, LV_OPA_20);
lv_style_set_transition(&style_icon_pressed, &transition_dsc_pr);
lv_obj_set_style_bg_color(lv_scr_act(), lv_color_hex(HOME_BG_COLOR_HEX), 0);
lv_obj_set_style_bg_opa(lv_scr_act(), LV_OPA_COVER, 0);
container = lv_obj_create(lv_scr_act());
lv_obj_set_size(container, lv_obj_get_width(lv_scr_act()), lv_obj_get_height(lv_scr_act()));
lv_obj_center(container);
lv_obj_clear_flag(container, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_add_flag(container, LV_OBJ_FLAG_OVERFLOW_VISIBLE);
lv_obj_set_style_bg_color(container, lv_color_hex(HOME_BG_COLOR_HEX), 0);
lv_obj_set_style_bg_opa(container, LV_OPA_COVER, 0);
lv_obj_set_style_border_width(container, 0, 0);
lv_obj_set_style_radius(container, 0, 0);
lv_obj_set_style_pad_all(container, 0, 0);
lv_obj_add_event_cb(container, event_handler, LV_EVENT_ALL, NULL);
icon_info_label = lv_label_create(container);
lv_obj_set_style_text_color(icon_info_label, lv_color_hex(INFO_TEXT_COLOR_HEX), 0);
lv_obj_set_style_text_font(icon_info_label, &lv_font_montserrat_34, 0);
lv_label_set_text(icon_info_label, "");
lv_obj_align(icon_info_label, LV_ALIGN_TOP_RIGHT, -24, 20);
for (int i = 0; i < NUM_ICONS; i++) {
icons[i] = lv_obj_create(container);
lv_obj_set_size(icons[i], ICON_WIDTH, ICON_HEIGHT);
lv_obj_add_style(icons[i], &style_icon, 0);
lv_obj_add_style(icons[i], &style_icon_release_transition, 0);
lv_obj_add_style(icons[i], &style_icon_pressed, LV_STATE_PRESSED);
lv_obj_clear_flag(icons[i], LV_OBJ_FLAG_SCROLLABLE);
lv_obj_add_flag(icons[i], LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_EVENT_BUBBLE | LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_OVERFLOW_VISIBLE);
icon_symbols_obj[i] = lv_label_create(icons[i]);
lv_label_set_text(icon_symbols_obj[i], icon_symbols[i]);
lv_obj_set_style_text_color(icon_symbols_obj[i], icon_colors[i], 0);
lv_obj_set_style_text_font(icon_symbols_obj[i], &lv_font_montserrat_48, 0);
lv_obj_update_layout(icon_symbols_obj[i]);
lv_obj_center(icon_symbols_obj[i]);
}
scroll_offset = 2.0f;
recalc_layout();
}