LVGL 音量调节示例

概述

平台炬芯,使用LVGL渲染UI 界面,实现人机交互。本实例基于模拟器运行,可供参考。

比较传统写法,还有优化的空间。

1、Visual Studio 26版本

2、代码

cpp 复制代码
/*
 * Copyright (c) 2020 Actions Technology Co., Ltd
 *
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @file volume_set_view.c
 */

#include <os_common_api.h>
#include <app_ui.h>
#include <view_stack.h>

/* picture idx */
enum {
	IDX_PIC_VS_VOLUME_DOWN_BTN = 0,
	IDX_PIC_VS_VOLUME_UP_BTN,
	IDX_PIC_VS_COUNT,
};

/* picture */
const static uint32_t _pic_ids[] = {
	PIC_VS_VOLUME_DOWN_BTN, PIC_VS_VOLUME_UP_BTN,
};

/* group picture idx */
enum {
	IDX_PIC_VS_VOLUME_SLIDER_BG = 0,
	IDX_PIC_VS_VOLUME_SLIDER_ICON,
	IDX_PIC_VS_GROUP_COUNT,
};

/* group picture */
const static uint32_t _pic_group_ids[] = {
	PIC_VS_VOLUME_SLIDER_BG, PIC_VS_VOLUME_SLIDER_ICON,
};

typedef struct volume_set_view_data {
	lv_obj_t *scr;
	lv_obj_t *main_obj;
	/* lvgl resource */
	lvgl_res_scene_t res_scene;	 //场景
	lvgl_res_group_t res_group;  //资源组

	lv_img_dsc_t img_dsc[IDX_PIC_VS_COUNT];
	lv_img_dsc_t img_group_dsc[IDX_PIC_VS_GROUP_COUNT];

	lv_font_t font;
	
	/* user data */
	lv_obj_t *vol_down_btn;
	lv_obj_t *vol_up_btn;
	lv_obj_t *vol_slider;        // 滑动条主体
	lv_obj_t *vol_speaker_icon;  // 保存音量图标引用
	int current_volume;      // 新增:保存当前音量值,用于联动
} volume_set_view_data_t;

/* 滑动条值变化回调:同步当前音量值(可选:可在此添加系统音量设置逻辑) */
static void vol_slider_change_cb(lv_event_t *e)
{
	volume_set_view_data_t *data = lv_event_get_user_data(e);
	if (!data || !data->vol_slider) return;

	// 获取滑动条当前值,更新到data中
	data->current_volume = lv_slider_get_value(data->vol_slider);
	SYS_LOG_INF("current_volume: %d", data->current_volume);

	// 【可选】如果需要同步到系统音量,添加这里:
	// os_audio_set_volume(data->current_volume);
}

/* 音量- 按键回调:控制滑动条值减少 */
static void vol_down_btn_cb(lv_event_t *e)
{
	volume_set_view_data_t *data = lv_event_get_user_data(e);
	if (!data || !data->vol_slider) return;

	// 从滑动条获取当前值,减少5(步长可调整)
	int vol = lv_slider_get_value(data->vol_slider);
	if (vol > 0) {
		data->current_volume = (vol - 5) < 0 ? 0 : (vol - 5); // 同步到data->current_volume
		lv_slider_set_value(data->vol_slider, data->current_volume, LV_ANIM_ON); // 带动画更新滑动条
		
		SYS_LOG_INF("vol_down_btn: %d", data->current_volume);

		// 同步到系统音量
		// os_audio_set_volume(data->current_volume);
	}
}

/* 音量+ 按键回调:控制滑动条值增加 */
static void vol_up_btn_cb(lv_event_t *e)
{
	volume_set_view_data_t *data = lv_event_get_user_data(e);
	if (!data || !data->vol_slider) return;

	// 从滑动条获取当前值,增加5(步长可调整)
	int vol = lv_slider_get_value(data->vol_slider);
	if (vol < 100) {
		data->current_volume = (vol + 5) > 100 ? 100 : (vol + 5); // 同步到data->current_volume
		lv_slider_set_value(data->vol_slider, data->current_volume, LV_ANIM_ON); // 带动画更新滑动条

		SYS_LOG_INF("vol_up_btn: %d", data->current_volume);

		// 【可选】同步到系统音量
		// os_audio_set_volume(data->current_volume);
	}
}

static int _volume_set_view_preload(view_data_t *view_data)
{
	return lvgl_res_preload_scene_compact(SCENE_VOLUME_SET_VIEW, NULL, 0, lvgl_res_scene_preload_default_cb_for_view, 
			(void *)VOLUME_SET_VIEW,
			SYS_STY_FILE, SYS_RES_FILE, SYS_STR_FILE);
}

static int _volume_set_view_load_resource(view_data_t *view_data)
{
	int ret;

	// alloc data
	volume_set_view_data_t *data = app_mem_malloc(sizeof(*data));
	if (!data) {
		return -ENOMEM;
	}
	view_data->user_data = data;
	memset(data, 0, sizeof(*data));

	// 初始化默认音量(比如50)
	data->current_volume = 50;

	/* scene */
	ret = lvgl_res_load_scene(SCENE_VOLUME_SET_VIEW, &data->res_scene,
			SYS_STY_FILE, SYS_RES_FILE, SYS_STR_FILE);
	SYS_LOG_INF("load scene ret=%d", ret);		
	if (ret < 0) {
		SYS_LOG_ERR("SCENE_VOLUME_SET_VIEW not found");
		return -ENOENT;
	}

	/* load picture from scene */
	ret = lvgl_res_load_pictures_from_scene(&data->res_scene, _pic_ids, data->img_dsc, NULL,IDX_PIC_VS_COUNT);
	SYS_LOG_INF("load picture from scene ret=%d", ret);
	if (ret < 0) {
		SYS_LOG_ERR("load picture from scene failed");
		goto out_exit;
	}

	/* load group from scene */
	ret = lvgl_res_load_group_from_scene(&data->res_scene, RES_VS_VOLUME_SLIDER, &data->res_group);
	SYS_LOG_INF("load group from scene ret=%d", ret);
	if (ret < 0) {
		SYS_LOG_ERR("load group from scene failed");
		goto out_exit;
	}
	
	/* load pictures from group */
	ret = lvgl_res_load_pictures_from_group(&data->res_group, _pic_group_ids, data->img_group_dsc, NULL, IDX_PIC_VS_GROUP_COUNT);
	SYS_LOG_INF("load pictures from group ret=%d", ret);
	if (ret < 0) {
		SYS_LOG_ERR("load pictures from group failed");
		goto out_exit;
	}

	// font
	ret = LVGL_FONT_OPEN_DEFAULT(&data->font, DEF_FONT_SIZE_NORMAL);
	SYS_LOG_INF("open_font ret=%d", ret);
	if (ret < 0) {
		goto out_exit;
	}

	return 0;

out_exit:
	_volume_set_view_unload_resource(view_data);
	SYS_LOG_ERR("load resource failed, exit");
	return ret;
}

static int _volume_set_view_unload_resource(view_data_t *view_data)
{
	volume_set_view_data_t *data = view_data->user_data;

	if (data) {
		lvgl_res_unload_scene(&data->res_scene);
		/* unload group */
		lvgl_res_unload_group(&data->res_group);
		/* unload picture */
		lvgl_res_unload_pictures(data->img_dsc, IDX_PIC_VS_COUNT);
		/* unload group picture */
		lvgl_res_unload_pictures(data->img_group_dsc, IDX_PIC_VS_GROUP_COUNT);
		/* close font */
		LVGL_FONT_CLOSE(&data->font);

		// clean obj
		if (data->main_obj != NULL && lv_obj_is_valid(data->main_obj)) {
			lv_obj_clean(data->main_obj);
			data->main_obj = NULL;
		}
		data->scr = NULL;
		data->vol_down_btn = NULL;
		data->vol_up_btn = NULL;
		data->vol_slider = NULL;
		data->vol_speaker_icon = NULL;

		// free data
		app_mem_free(data);
		view_data->user_data = NULL;

		SYS_LOG_INF("ok\n");
	} else {
		lvgl_res_preload_cancel_scene(SCENE_VOLUME_SET_VIEW);
	}

	lvgl_res_unload_scene_compact(SCENE_VOLUME_SET_VIEW);
	return 0;
}

static int _volume_set_view_layout(view_data_t *view_data)
{
    volume_set_view_data_t *data = (volume_set_view_data_t *)view_data->user_data;
    data->scr = lv_disp_get_scr_act(view_data->display);

    data->main_obj = lv_obj_create(data->scr);
    if (!data->main_obj) {
        SYS_LOG_ERR("create main obj failed");
        goto fail_exit;
    }
    lv_obj_set_size(data->main_obj, DEF_UI_VIEW_WIDTH, DEF_UI_VIEW_HEIGHT);

    // 音量减按钮
    data->vol_down_btn = lv_img_create(data->main_obj);
    lv_img_set_src(data->vol_down_btn, &data->img_dsc[IDX_PIC_VS_VOLUME_DOWN_BTN]);
    lv_obj_set_pos(data->vol_down_btn, 32, 214);
    lv_obj_add_flag(data->vol_down_btn, LV_OBJ_FLAG_CLICKABLE);
    lv_obj_set_user_data(data->vol_down_btn, data);
    lv_obj_add_event_cb(data->vol_down_btn, vol_down_btn_cb, LV_EVENT_CLICKED, data);

    // 音量加按钮
    data->vol_up_btn = lv_img_create(data->main_obj);
    lv_img_set_src(data->vol_up_btn, &data->img_dsc[IDX_PIC_VS_VOLUME_UP_BTN]);
    lv_obj_set_pos(data->vol_up_btn, 298, 214);
    lv_obj_add_flag(data->vol_up_btn, LV_OBJ_FLAG_CLICKABLE);
    lv_obj_set_user_data(data->vol_up_btn, data);
    lv_obj_add_event_cb(data->vol_up_btn, vol_up_btn_cb, LV_EVENT_CLICKED, data);

    // 1. 滑动条背景图片(最底层,保留原图)
    lv_obj_t *vol_slider_bg = lv_img_create(data->main_obj);
    lv_img_set_src(vol_slider_bg, &data->img_group_dsc[IDX_PIC_VS_VOLUME_SLIDER_BG]);
    lv_obj_set_pos(vol_slider_bg, 30, 120);

    // 2. 创建滑动条(覆盖在背景图上,尺寸匹配)
    lv_obj_t *slider = lv_slider_create(data->main_obj);
    lv_obj_set_size(slider, 320, 80);
    lv_obj_set_pos(slider, 30, 120);
    lv_slider_set_range(slider, 0, 100);
    lv_slider_set_value(slider, data->current_volume, LV_ANIM_OFF); // 初始化到默认音量

    // 滑动条样式优化(匹配设计图)
    lv_obj_set_style_bg_opa(slider, LV_OPA_0, LV_PART_MAIN);        // 隐藏默认底框
    lv_obj_set_style_bg_color(slider, lv_color_white(), LV_PART_INDICATOR); // 选中区域白色
    lv_obj_set_style_bg_opa(slider, LV_OPA_100, LV_PART_INDICATOR);  // 选中区域不透明
    lv_obj_set_style_radius(slider, 40, LV_PART_INDICATOR);         // 半圆角(80高→40圆角)
    lv_obj_set_style_pad_all(slider, 0, LV_PART_INDICATOR);         // 无内边距
    lv_obj_set_style_bg_color(slider, lv_color_hex(0x333333), LV_PART_MAIN); // 未选中区域深灰
    lv_obj_set_style_bg_opa(slider, LV_OPA_80, LV_PART_MAIN);       // 未选中区域半透
    lv_obj_set_style_radius(slider, 40, LV_PART_MAIN);              // 整体圆角
    lv_obj_set_style_border_width(slider, 0, LV_PART_MAIN);         // 无边框
    lv_obj_set_style_bg_opa(slider, LV_OPA_0, LV_PART_KNOB);        // 隐藏默认滑块
    lv_obj_set_style_size(slider, 0, LV_PART_KNOB);                 // 滑块尺寸为0

    // 绑定滑动条值变化事件(拖动滑动条同步音量值)
    lv_obj_set_user_data(slider, data);
    lv_obj_add_event_cb(slider, vol_slider_change_cb, LV_EVENT_VALUE_CHANGED, data);

    // 3. 音量图标(创建为main_obj的子对象,确保置顶不遮挡)
    data->vol_speaker_icon = lv_img_create(data->main_obj);
    lv_img_set_src(data->vol_speaker_icon, &data->img_group_dsc[IDX_PIC_VS_VOLUME_SLIDER_ICON]);
    lv_obj_align_to(data->vol_speaker_icon, vol_slider_bg, LV_ALIGN_LEFT_MID, 30, 0); // 固定位置
    lv_obj_move_foreground(data->vol_speaker_icon); // 强制置顶(核心:解决遮挡)

    // 保存滑动条引用
    data->vol_slider = slider;

    return 0;

fail_exit:
    _volume_set_view_unload_resource(view_data);
    return -ENOMEM;
}

static int _volume_set_view_paint(view_data_t *view_data)
{
	return 0;
}

static int _volume_set_view_key(view_data_t *view_data)
{
	ui_key_msg_data_t *key_data = (ui_key_msg_data_t *)view_data;

	if (KEY_VALUE(key_data->event) == KEY_GESTURE_RIGHT) {
		view_stack_pop();
		key_data->done = true;
	}

	return 0;
}

int _volume_set_view_handler(uint16_t view_id, uint8_t msg_id, void *msg_data)
{
	view_data_t *view_data = msg_data;

	SYS_LOG_INF("_volume_set_view_handler: view_id=%d, msg_id=%d", view_id, msg_id);

	switch (msg_id) {
	case MSG_VIEW_PRELOAD:
		return _volume_set_view_preload(view_data);
	case MSG_VIEW_LAYOUT:
		if (_volume_set_view_load_resource(view_data)) return -ENOENT;
		return _volume_set_view_layout(view_data);
	case MSG_VIEW_DELETE:
		return _volume_set_view_unload_resource(view_data);
	case MSG_VIEW_PAINT:
		return _volume_set_view_paint(view_data);
	case MSG_VIEW_KEY:
		return _volume_set_view_key(view_data);	
	default:
		return 0;
	}
}

VIEW_DEFINE(volume_set, _volume_set_view_handler, NULL, NULL,
			VOLUME_SET_VIEW, NORMAL_ORDER, UI_VIEW_LVGL,
			DEF_UI_VIEW_WIDTH, DEF_UI_VIEW_HEIGHT);
			

3、运行结果

相关推荐
不脱发的程序猿9 小时前
嵌入式软件工程师,怎么把 AI 工具用顺手?
人工智能·单片机·嵌入式硬件·嵌入式
青天喵喵17 小时前
Linux Wi-Fi 实战指南:AP / STA 实战用例(实战篇一)
linux·网络·架构·智能路由器·嵌入式·wi-fi
济61720 小时前
ROS开发专栏---激光雷达数据获取仿真实验+ RViz2使用教程--适配Ubuntu 22.04
嵌入式硬件·嵌入式·ros2·机器人方向
优信电子1 天前
基于STM32F103C8T6单片机驱动ACS712模块进行电流检测
stm32·单片机·嵌入式硬件·嵌入式·电流检测·acs712·电流采集
程序员打怪兽2 天前
生产消费模型与多线程架构
嵌入式
程序员打怪兽2 天前
跨平台相机适配方案总结
嵌入式
华清远见IT开放实验室2 天前
硬核根基,智能载体:华清远见嵌入式“硬件+仿真+课程+师资”产教融合与实践教学方案
linux·人工智能·stm32·物联网·嵌入式·虚拟仿真
用户805533698032 天前
嵌入式Linux驱动开发——Pinctrl 子系统架构深度解析
linux·嵌入式
ziyi程序员2 天前
[嵌入式]嵌入式在线仿真平台 —— Wokwi 入门指南
嵌入式