☰
基于LVGL的嵌入式圆形屏幕音频播放器UI开发实战
2026/10/27 1:41:07 网站建设 项目流程

这次我们来看一个基于 LVGL 的嵌入式 UI 项目,它专门针对圆形屏幕设计了音频播放器的用户界面。对于从事智能手表、智能家居面板、便携式音乐播放器等圆形屏幕设备开发的工程师来说,如何在小内存、低功耗的 MCU 上实现流畅且美观的交互,一直是个挑战。这个项目提供了一个现成的解决方案,将 LVGL 的轻量级图形库与音频播放功能结合,直接在圆形画布上构建了一套完整的音频播放 UI。

这个项目的核心价值在于,它不是一个简单的演示,而是一个可以直接集成到产品中的模块。它解决了圆形屏幕 UI 开发中的几个痛点:控件适配、动画流畅度、内存占用以及音频解码与 UI 的协同。如果你正在为 ESP32、STM32 等微控制器寻找一个既能播放音频(如 MP3、WAV)又具备精美圆形 UI 的参考实现,这个项目值得深入研究。

本文会带你从零开始,理解这个项目的架构,完成在 PC 模拟器上的环境搭建与运行,并最终将其移植到实际的硬件开发板(如 ESP32)上。我们会重点关注其资源占用、UI 响应速度、音频播放的稳定性,以及如何根据你的硬件进行定制化修改。无论你是想快速评估,还是计划将其用于产品原型,都能在文中找到对应的路径。

1. 核心能力速览

能力项说明
项目类型嵌入式图形界面 (GUI) 与音频播放集成项目
核心库LVGL (Light and Versatile Graphics Library)
目标平台支持 LVGL 的嵌入式平台,如 ESP32、STM32 等
主要功能在圆形屏幕上实现音频播放器 UI:播放/暂停、上一曲/下一曲、进度条、音量控制、歌曲列表等
UI 特性专为圆屏设计,控件布局、动画效果均适配圆形区域
音频支持依赖于平台音频驱动(如 I2S),通常支持 PCM、MP3、WAV 等格式(需具体解码库)
开发环境可在 LVGL 官方 PC 模拟器上快速开发与调试
资源占用取决于 LVGL 配置和分辨率,通常需要几十 KB RAM 和几百 KB Flash
启动方式通过标准 LVGL 项目结构编译运行,或使用 PlatformIO、ESP-IDF 等集成开发环境
接口能力提供 LVGL 对象(按钮、滑块、标签等)的事件回调接口,可自定义业务逻辑
适合场景圆形屏幕的智能穿戴设备、家用电器面板、工业仪表盘等需要音频反馈的嵌入式产品

2. 适用场景与使用边界

这个项目非常适合以下几类开发者和场景:

  1. 智能穿戴设备开发者:正在开发圆形表盘的智能手表或手环,需要内置音乐播放或语音提示功能。
  2. 智能家居产品经理/工程师:设计带圆形屏幕的智能音箱、温控面板或中控屏,希望集成本地音频播放和交互式 UI。
  3. 嵌入式 UI 学习者:希望学习如何将 LVGL 应用于非矩形屏幕,并处理 UI 与底层硬件(如音频编解码器)的交互。
  4. 快速原型构建者:需要在短时间内为硬件验证一个具备音频播放能力的圆形 UI 演示。

使用边界与注意事项:

  • 硬件依赖:项目运行需要支持 LVGL 的 MCU 以及一个圆形显示屏(如 GC9A01、ST7789v 等驱动芯片)。音频播放功能需要额外的硬件支持,如 I2S 接口的 DAC 或音频编解码芯片(如 MAX98357A)。
  • 性能门槛:UI 流畅度和音频解码同时进行对 MCU 主频和内存有一定要求。对于低端 MCU,可能需要权衡动画复杂度和音频解码质量。
  • 功能完整性:项目主要提供 UI 框架和与音频播放器的对接示例。完整的音频解码器(如 MP3 解码库)、文件系统读取、网络流媒体等功能,可能需要开发者自行集成或选用更强大的硬件平台(如 ESP32-A1S 模组)。
  • 版权与合规:若播放的音频内容涉及版权,务必确保拥有合法授权。在涉及语音提示或录音功能时,需注意用户隐私保护,符合相关法律法规。

3. 环境准备与前置条件

在开始动手之前,请确保你的开发环境满足以下基本要求。

3.1 硬件准备(用于最终移植)

  1. 微控制器开发板:如 ESP32-DevKitC、STM32F4 Discovery 等。
  2. 圆形显示屏模块:常见的有 GC9A01(240x240)、ST7789v(240x240)等 SPI 或并行接口的屏幕。
  3. 音频输出设备:可以是 I2S 接口的 DAC 模块(如 MAX98357A 模块),或者开发板自带的音频输出接口。
  4. 连接线:杜邦线,用于连接屏幕、音频模块与开发板。
  5. 存储设备:SD 卡或 SPI Flash,用于存放音频文件(如果播放本地文件)。

3.2 软件准备(用于模拟器开发与调试)

在将项目移植到硬件前,强烈建议先在 PC 上的 LVGL 模拟器中运行和修改 UI,这能极大提高开发效率。

  1. 操作系统:Windows 10/11, Linux 或 macOS。
  2. 代码编辑器:Visual Studio Code (VSCode) 是首选,拥有丰富的 LVGL 插件。
  3. 编译器与构建工具:
    • PC 模拟器:需要安装 GCC/MinGW 或 MSVC (Windows)、GCC (Linux/macOS)。
    • ESP32:需要安装 ESP-IDF 或 PlatformIO 扩展。
    • STM32:需要安装 ARM GCC 工具链和 STM32CubeIDE 或 PlatformIO。
  4. LVGL PC 模拟器项目:这是快速启动的关键。可以从 LVGL 官方 GitHub 仓库获取。
  5. 必要的软件库:
    • SDL2 (用于模拟器显示和输入)。
    • (可选)FFmpeg 库或其它音频库,用于在模拟器中模拟音频播放回调。

4. 安装部署与启动方式

我们将分两步走:首先在 PC 模拟器上运行和体验项目,然后再移植到硬件。

4.1 在 LVGL PC 模拟器上运行

这是最快捷的评估方式,无需硬件。

  1. 获取 LVGL 模拟器项目:

    git clone --recursive https://github.com/lvgl/lv_port_pc_vscode.git cd lv_port_pc_vscode
  2. 集成圆屏音频播放器 UI 代码:

    • 将你要评估的 “LVGL圆屏播放音频创意UI” 项目中的 UI 相关源文件(通常是.c和.h文件)复制到模拟器项目的src目录下。
    • 主要需要复制的是创建屏幕(lv_scr_act())和所有控件(按钮、滑块、标签、图像等)的代码。
    • 同时,需要将项目中的图片、字体等资源文件复制到assets目录。
  3. 模拟音频播放逻辑:

    • 由于 PC 上没有真实的 I2S 硬件,你需要用一个软件回调来模拟音频播放。例如,在main.c中,可以将播放/暂停按钮的回调函数链接到一个模拟函数,该函数打印日志或调用系统的音频播放 API(如 SDL_mixer)。
    • 关键是在 UI 的回调中,更新进度条、时间标签等状态,模拟播放过程。
  4. 编译与运行:

    • 用 VSCode 打开项目文件夹。
    • 安装推荐的扩展(如 C/C++)。
    • 根据你的平台,在终端运行构建命令。通常项目会提供build.sh(Linux/macOS) 或使用 CMake。
    # Linux/macOS 示例 mkdir build && cd build cmake .. make -j4 ./lvgl_example
    • 运行后,会出现一个模拟的圆形窗口,展示完整的音频播放器 UI,你可以用鼠标点击按钮进行交互。

4.2 移植到硬件平台(以 ESP32 为例)

当模拟器 UI 调试满意后,即可移植到真实硬件。

  1. 创建 ESP-IDF 项目:

    mkdir -p ~/esp/audio_player_ui cd ~/esp/audio_player_ui cp -r $IDF_PATH/examples/peripherals/lvgl . # 复制 LVGL 示例作为基础 # 或者使用 PlatformIO 创建新项目,选择 ESP32 和 LVGL 库。
  2. 集成 UI 与驱动:

    • 将模拟器中调试好的 UI 源文件和资源文件复制到 ESP32 项目的main目录。
    • 配置显示屏驱动:修改lv_conf.h和lvgl_helpers.h/c,设置正确的屏幕分辨率(240x240)、驱动芯片类型(GC9A01)和 SPI 引脚。
    • 集成音频驱动:根据你使用的音频模块(如 MAX98357A),配置 ESP32 的 I2S 引脚和参数。可以使用esp-adf(乐鑫音频开发框架)中的相关示例代码。
  3. 连接音频播放逻辑:

    • 在 UI 的播放按钮回调函数中,不再调用模拟函数,而是调用i2s_stream_start()等实际的音频播放 API。
    • 创建一个任务(FreeRTOS Task)或定时器,用于更新播放进度,并调用lv_bar_set_value()和lv_label_set_text()来刷新 UI。
  4. 编译与烧录:

    idf.py set-target esp32 idf.py menuconfig # 配置 LVGL 内存大小、堆栈大小等 idf.py build idf.py -p /dev/ttyUSB0 flash monitor

5. 功能测试与效果验证

在模拟器和硬件上,都需要对核心功能进行验证。

5.1 UI 渲染与交互测试

  • 测试目的:验证圆形 UI 是否正常显示,所有控件是否响应输入。
  • 操作步骤:
    1. 启动程序(模拟器或硬件上电)。
    2. 观察屏幕是否点亮,并完整绘制出圆形播放器界面(专辑封面、进度条、控制按钮等)。
    3. 使用鼠标(模拟器)或触摸屏/按键(硬件)点击“播放/暂停”按钮。
    4. 点击“上一曲”、“下一曲”按钮。
    5. 拖动“进度条”滑块。
    6. 拖动“音量”滑块。
  • 预期结果:
    • 按钮应有按下态视觉效果(颜色或形状变化)。
    • 点击播放/暂停,按钮图标应在“播放”和“暂停”状态间切换。
    • 点击上一曲/下一曲,专辑封面或歌曲名标签应发生变化(模拟或实际)。
    • 拖动进度条,进度指示应跟随移动,时间标签应更新。
    • 拖动音量条,音量图标或数值应变化。
  • 判断成功:所有视觉反馈与交互逻辑符合预期。
  • 常见失败原因:
    • 屏幕驱动初始化失败:检查 SPI 引脚配置、屏幕初始化序列。
    • 触摸屏或按键无响应:检查输入设备驱动和 LVGL 输入设备注册。
    • 控件事件未绑定:检查lv_obj_add_event_cb()函数调用。

5.2 音频播放功能测试

  • 测试目的:验证 UI 能控制音频硬件正常播放、暂停、切换。
  • 操作步骤(硬件):
    1. 将测试音频文件(如test.mp3)放入 SD 卡或 SPIFFS 文件系统。
    2. 在代码中硬编码或通过列表加载该文件路径。
    3. 点击 UI 上的“播放”按钮。
    4. 聆听扬声器或耳机是否有声音输出。
    5. 点击“暂停”按钮,声音应停止。
    6. 点击“下一曲”(如果有多个文件),应播放下一首。
    7. 拖动进度条,尝试实现快进/快退(此功能需要音频解码器支持)。
  • 预期结果:
    • 音频播放流畅,无杂音或卡顿。
    • 播放/暂停控制即时生效。
    • 进度条应随时间自动前进。
  • 判断成功:音频输出与控制逻辑正常。
  • 常见失败原因:
    • I2S 配置错误:检查时钟、数据格式、引脚分配。
    • 音频文件格式不支持:确认解码库支持该格式(MP3, WAV, AAC)。
    • 内存不足:音频解码缓冲区可能过大,尝试减小缓冲区大小或优化内存配置。
    • 文件读取失败:检查文件系统挂载和文件路径。

5.3 性能与压力测试

  • 测试目的:评估系统在 UI 动画与音频解码同时运行时的稳定性。
  • 操作步骤:
    1. 让系统持续播放音频。
    2. 快速、连续地操作 UI 按钮,模拟用户频繁交互。
    3. 观察是否有界面卡顿、音频断音、系统重启等现象。
  • 预期结果:系统运行稳定,UI 响应及时,音频播放不间断。
  • 判断成功:长时间运行(如 30 分钟)无异常。
  • 常见失败原因:
    • 堆栈溢出:UI 任务或音频任务的堆栈设置过小。在menuconfig中增加LVGL_TASK_STACK_SIZE和音频任务的堆栈。
    • 内存泄漏:在切换歌曲或界面时,未正确释放前一次分配的内存(如图片缓存)。使用 ESP-IDF 的内存调试工具检查。
    • 中断冲突:SPI(屏幕)、I2S(音频)、SDMMC(读卡)可能共用中断或 DMA 通道,造成冲突。调整外设使用的 DMA 通道或优先级。

6. 接口 API 与事件回调设计

本项目本质上是 LVGL 应用程序,其“接口”主要体现在 LVGL 对象的事件回调函数上。理解如何扩展这些回调是定制功能的关键。

6.1 核心事件回调示例

假设我们有一个播放/暂停按钮play_btn和一个进度条progress_bar。

// 在创建UI的函数中 lv_obj_t *play_btn = lv_btn_create(lv_scr_act()); lv_obj_t *progress_bar = lv_bar_create(lv_scr_act()); // 为播放按钮添加点击事件回调 lv_obj_add_event_cb(play_btn, play_btn_event_handler, LV_EVENT_CLICKED, NULL); // 为进度条添加拖动事件回调 lv_obj_add_event_cb(progress_bar, progress_bar_event_handler, LV_EVENT_VALUE_CHANGED, NULL);

6.2 回调函数实现示例

// 播放/暂停按钮事件处理函数 static void play_btn_event_handler(lv_event_t *e) { lv_event_code_t code = lv_event_get_code(e); lv_obj_t *btn = lv_event_get_target(e); if(code == LV_EVENT_CLICKED) { // 获取当前按钮状态(可通过标签或用户数据存储) static bool is_playing = false; is_playing = !is_playing; if(is_playing) { // 1. 更新按钮图标为“暂停” lv_label_set_text(lv_obj_get_child(btn, 0), LV_SYMBOL_PAUSE); // 2. 调用底层音频播放器开始播放 audio_player_start(); // 3. 启动一个定时器,用于更新进度条 lv_timer_resume(progress_update_timer); } else { // 1. 更新按钮图标为“播放” lv_label_set_text(lv_obj_get_child(btn, 0), LV_SYMBOL_PLAY); // 2. 调用底层音频播放器暂停 audio_player_pause(); // 3. 暂停更新进度条的定时器 lv_timer_pause(progress_update_timer); } } } // 进度条更新定时器回调 static void progress_update_timer_cb(lv_timer_t *timer) { // 1. 从音频播放器获取当前播放位置(单位:秒) uint32_t current_time = audio_player_get_current_time(); uint32_t total_time = audio_player_get_total_time(); // 2. 计算进度百分比 lv_coord_t progress = (total_time > 0) ? (current_time * 100 / total_time) : 0; // 3. 更新进度条值 lv_bar_set_value(progress_bar, progress, LV_ANIM_ON); // 4. 更新时间标签 (例如: "01:23 / 04:56") char time_str[20]; snprintf(time_str, sizeof(time_str), "%02d:%02d / %02d:%02d", current_time / 60, current_time % 60, total_time / 60, total_time % 60); lv_label_set_text(time_label, time_str); }

6.3 与外部系统集成

你可以通过这些事件回调,轻松地将 UI 与更复杂的系统连接:

  • 蓝牙控制:在回调函数中发送蓝牙 AVRCP 命令。
  • 网络请求:切换歌曲时,从网络 API 获取下一首歌曲的元数据(专辑图、名称)。
  • 传感器交互:抬起手腕(加速度计)自动点亮屏幕,或旋转表冠(编码器)调节音量。

7. 资源占用与性能优化

在资源受限的嵌入式设备上,优化至关重要。

7.1 内存占用分析

  1. LVGL 本身:通过lv_conf.h配置。关键配置项:
    #define LV_MEM_SIZE (48 * 1024U) // 建议从32KB开始测试,根据控件数量增加 #define LV_DISP_DEF_REFR_PERIOD 30 // 刷新周期(ms),值越大CPU占用越低,但流畅度下降 #define LV_DPI_DEF 130 // 根据屏幕实际DPI设置,影响字体和图像渲染
  2. 帧缓冲区:双缓冲区能提升流畅度,但会占用 2 * 屏幕分辨率 * 颜色深度 的内存。对于 240x240 RGB565 屏幕,单缓冲区需 2402402 = 115200 字节 (约112.5KB)。如果内存紧张,可使用单缓冲区或部分缓冲区。
  3. 图片与字体:是 Flash 占用的大头。
    • 优化图片:使用 LVGL 的在线图片转换工具,将 PNG/JPG 转换为 C 数组,并选择适当的颜色格式(如 RGB565)和压缩。
    • 优化字体:仅包含 UI 中实际用到的字符集(LV_FONT_SIMPLIFIED_CHINESE包含很多字),可以使用 LVGL 的字体转换工具生成自定义字体文件。

7.2 CPU 占用与渲染性能

  1. 监控工具:使用lv_timer_get_idle()函数可以获取系统空闲时间百分比,间接反映 CPU 负载。
  2. 降低刷新率:非必要不刷新。对于静态元素多的界面,可以设置lv_obj_add_flag(obj, LV_OBJ_FLAG_HIDDEN)或lv_obj_add_state(obj, LV_STATE_DEFAULT)来减少重绘区域。
  3. 简化动画:减少同时进行的复杂动画数量。使用LV_ANIM_OFF或更短的动画时间。
  4. 音频解码优化:如果音频解码占用大量 CPU,考虑:
    • 使用硬件解码(如果 MCU 支持)。
    • 降低音频采样率(如从 44.1kHz 降到 22.05kHz)。
    • 使用计算量更小的解码算法(如 ADPCM 代替 MP3)。

7.3 存储优化

  1. 文件系统:如果音频文件较多,使用 SPIFFS 可能效率较低。考虑使用 FATFS 配合 SD 卡。
  2. 资源管理:不要一次性将所有歌曲的封面图片加载到内存。使用lv_img_set_src()时,可以设置LV_IMG_SRC_FILE从文件系统动态加载,并配合缓存策略。

8. 常见问题与排查方法

问题现象可能原因排查方式解决方案
屏幕白屏或花屏1. 屏幕驱动初始化序列错误。
2. SPI 时钟速率过高。
3. 电源或背光未开启。
1. 用逻辑分析仪抓取 SPI 时序,与屏幕数据手册对比。
2. 降低 SPI 时钟频率。
3. 检查屏幕的 VCC、GND、背光引脚电压。
1. 仔细核对并修正初始化命令序列。
2. 在lv_conf.h或驱动代码中降低 SPI 频率。
3. 确保硬件连接正确,背光被点亮。
触摸无反应1. 触摸屏驱动未正确注册到 LVGL。
2. 触摸屏 I2C/SPI 通信失败。
3. 触摸屏校准数据错误。
1. 检查lv_port_indev_init()中是否添加了触摸屏设备。
2. 检查 I2C/SPI 引脚和地址。
3. 运行 LVGL 的触摸校准示例。
1. 确保lv_indev_drv_register()被调用。
2. 修复通信问题。
3. 重新校准并保存触摸参数。
UI 严重卡顿1. LVGL 任务堆栈溢出。
2. 帧缓冲区设置过小或内存不足。
3. 过于复杂的动画或频繁全局重绘。
1. 查看系统日志是否有堆栈溢出警告。
2. 监控lv_timer_get_idle()值,如果很低说明 CPU 忙。
3. 使用 LVGL 的性能分析器(如果启用)。
1. 增加LVGL_TASK_STACK_SIZE。
2. 增加LV_MEM_SIZE或优化内存使用。
3. 简化 UI,减少动画,使用局部刷新lv_obj_invalidate_area()。
播放音频时系统重启1. 音频任务堆栈溢出。
2. I2S DMA 缓冲区配置错误导致内存访问越界。
3. 电源带载能力不足。
1. 检查音频任务的堆栈大小。
2. 检查 I2S 配置,特别是 DMA 缓冲区数量和大小。
3. 测量播放音频时电源电压是否被拉低。
1. 增加音频任务堆栈。
2. 调整 I2S DMA 缓冲区大小,确保其对齐且未越界。
3. 为音频功放模块提供独立电源或增加电源滤波电容。
进度条不更新或跳跃1. 更新进度条的定时器周期设置不当。
2. 从音频解码器获取的时间信息不准确或不同步。
3. 回调函数内执行了耗时操作,阻塞了 LVGL 主任务。
1. 检查定时器回调函数是否被正常调用。
2. 打印获取到的当前时间和总时间,看是否连续。
3. 在回调函数中只做最简单的赋值操作,将复杂逻辑移到其他任务。
1. 调整定时器周期(如 200ms)。
2. 确保音频解码器提供的时间基是稳定的,或使用音频数据已播放的字节数来计算进度。
3. 使用队列(Queue)或信号量(Semaphore)进行任务间通信,避免阻塞。
切换歌曲时界面卡死1. 在事件回调中进行了文件系统操作(如读取下一首歌),该操作阻塞时间过长。
2. 切换时未正确释放上一首歌的资源(如解码器实例、文件句柄)。
1. 在回调中只发送切换请求,在独立的任务中执行实际的加载和解码操作。
2. 检查资源释放代码,确保没有内存泄漏。
1. 将耗时的 IO 和解析操作放入低优先级任务。
2. 实现严格的资源管理生命周期,使用malloc/free或 RAII 模式(C++)。

9. 最佳实践与使用建议

  1. 开发流程建议:

    • 模拟器先行:80% 的 UI 逻辑和布局调整应在 PC 模拟器上完成,效率远高于反复烧录硬件。
    • 版本控制:对 UI 资源(图片、字体)和配置文件(lv_conf.h)进行版本管理,便于回溯和对比不同硬件配置下的效果。
    • 分模块测试:先确保屏幕驱动和触摸驱动单独工作正常,再集成 LVGL;先确保音频播放单独工作正常,再与 UI 联动。
  2. 代码组织建议:

    • UI 与逻辑分离:将创建 UI 控件的代码放在ui.c中,将与硬件交互的音频控制代码放在audio_player.c中。两者通过清晰定义的接口(如函数指针、事件队列)通信。
    • 使用 LVGL 样式:统一管理控件的颜色、字体、边距等属性,而不是硬编码在每个控件创建函数里。这便于后期整体换肤。
    • 合理使用用户数据:lv_obj_set_user_data(obj, &my_data)可以将业务数据(如歌曲索引、播放状态)与 LVGL 对象关联,方便在事件回调中获取。
  3. 性能与资源最佳实践:

    • 懒加载资源:对于多级菜单或复杂页面,不要一次性创建所有控件。使用lv_obj_del()和lv_obj_create()动态管理。
    • 图片缓存策略:对于频繁使用的图标,可以加载到内存;对于不常用的专辑大图,从文件系统读取并设置较小的缓存。
    • 功耗考虑:在播放音频时,可以适当提高 CPU 频率;在待机界面,降低屏幕刷新率甚至关闭屏幕背光,以节省电量。
  4. 产品化建议:

    • 异常处理:增加看门狗(Watchdog),防止程序跑飞。对文件读取失败、内存分配失败等情况要有降级处理(如显示默认图片、播放错误提示音)。
    • 用户数据持久化:将音量设置、播放模式(单曲循环、列表循环)等用户偏好保存到非易失性存储(NVS)。
    • 工厂测试模式:预留一个硬件按键组合,进入测试模式,自动循环测试屏幕显示、触摸、音频输出、按键等功能,方便生产环节质检。

10. 总结与下一步

这个“LVGL圆屏播放音频创意UI”项目为嵌入式圆形屏幕设备开发提供了一个高起点。它最值得尝试的点在于,将成熟的 LVGL 生态与具体的音频播放场景深度结合,给出了一个可直接运行、易于修改的完整范例。你最先应该验证的是在模拟器上跑通整个 UI 交互流程,这能帮你快速理解其代码结构。

最容易踩的坑集中在硬件适配层:屏幕驱动初始化、触摸校准、I2S 音频配置。建议按照“先显示,再触摸,最后音频”的顺序逐个击破。当硬件基础打牢后,丰富的 LVGL 控件和事件机制能让你的创意快速实现。

下一步,你可以基于此项目进行多方向扩展:

  • 功能增强:添加蓝牙耳机连接管理、均衡器设置、歌词同步显示(LRC)功能。
  • 交互升级:结合旋转编码器或手势传感器,实现更自然的交互(旋转表冠调音量,上下滑切歌)。
  • 云端集成:让设备通过 Wi-Fi 连接音乐流媒体服务,播放在线歌曲。
  • 多语言支持:利用 LVGL 的字体引擎,轻松添加多语言歌曲信息显示。

建议将本文作为你的开发路线图,收藏备用。从模拟器到硬件,从基础播放到功能扩展,每一步都稳扎稳打,你就能高效地将这个精美的圆形音频播放 UI 应用到自己的产品中。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询