当前位置: 首页 > ds >正文

【LVGL】从HTML到LVGL:嵌入式UI的设计迁移与落地实践

从HTML到LVGL:嵌入式UI的设计迁移与落地实践

在嵌入式开发中,我们常遇到这样的场景:用HTML/CSS快速实现了UI原型(比如压力传感器监控界面),但最终需要在单片机屏幕上通过LVGL(轻量级嵌入式GUI库)运行。而Figma作为设计与开发的“桥梁”,能帮我们精准还原HTML设计,并为LVGL开发提供标准化的视觉参考。本文将详细拆解从HTML迁移UI到Figma,再适配LVGL的完整流程,附带实操代码与避坑指南。

一、背景:为什么需要“HTML→Figma→LVGL”的链路?

在嵌入式UI开发中,直接用LVGL写界面效率低,且难以和设计师协作;而HTML/CSS适合快速迭代Web端原型,但无法直接运行在单片机上。三者的定位互补:

  • HTML:快速验证UI逻辑(如数据展示、交互流程),适合前期原型开发;
  • Figma:将HTML原型转化为标准化设计资产(颜色、组件、尺寸规范),打通设计与嵌入式开发;
  • LVGL:基于Figma规范,在嵌入式硬件上实现高性能UI,适配800×480等单片机屏幕。

本文以“双通道压力传感器监控UI”为例(分辨率800×480,深色科技风),展开具体实践。

二、前期准备:工具与设计参数梳理

在开始迁移前,需准备工具并提取HTML中的核心设计参数,避免后续适配偏差。

1. 必备工具清单

工具用途推荐工具核心作用
设计工具Figma(网页版/客户端)还原HTML UI,导出设计规范与资源
嵌入式开发环境VS Code + STM32CubeIDE/ESP-IDF搭建LVGL工程,编写适配代码
HTML样式提取Chrome开发者工具(Elements面板)+ 取色器提取HTML的颜色、字体、间距等参数
资源格式转换在线RGB→RGB565转换器、Figma导出插件将Figma资源转为LVGL支持的格式(如.png)

2. 提取HTML设计核心参数

打开HTML文件,用Chrome开发者工具(F12)提取关键样式,整理成表格(后续Figma和LVGL将严格遵循此规范):

设计维度HTML参数(示例)说明
颜色体系主色#0ea5e9(通道1)、辅助色#8b5cf6(通道2)、背景色#0f172a(页面)、#1e293b(面板)需在Figma中创建对应“颜色样式”
字体体系无衬线字体(Inter)、等宽字体(Roboto Mono);标题16px、数值64px、按钮文本14pxFigma中匹配相似字体,LVGL需预编译对应字体文件
组件尺寸按钮最小高度48px(触摸友好)、面板圆角8px、组件间距16px、图表区尺寸520×200px嵌入式屏幕尺寸固定,需严格匹配800×480布局
交互效果按钮hover背景色#0ea5e9/30、状态灯脉冲动画(1s周期)、图表曲线渐变填充Figma用“原型动画”模拟,LVGL用样式/动画API实现

三、第一步:HTML UI迁移至Figma(设计落地)

Figma的核心作用是将HTML的“代码化UI”转化为“可视化设计资产”,需保证1:1还原布局与样式,为后续LVGL开发提供精准参考。

1. 搭建Figma基础画布

  1. 打开Figma,新建“设计文件”,创建800×480像素的框架(Frame),命名为“嵌入式压力传感器UI”;
  2. 设置框架背景色为HTML的页面背景色#0f172a,关闭“自动调整大小”,确保尺寸固定。

2. 还原HTML核心组件(逐个击破)

按HTML的布局结构(标题栏→压力显示区→图表区→状态栏),在Figma中逐一重建组件,重点关注“样式一致性”和“可复用性”。

(1)标题栏组件
  • 结构:左侧标题(图标+文字)+ 右侧时间/设置按钮;
  • 实操
    1. 左侧:添加“文本”层(内容“双通道压力传感器监控系统”,字体Inter 16px,颜色#ffffff),左侧搭配“图标”(Figma搜索“dashboard”,颜色#0ea5e9);
    2. 右侧:时间文本(Inter 14px,颜色#94a3b8),设置按钮(圆形,尺寸40×40,背景色#1e293b,图标“cog”,颜色#94a3b8);
    3. 间距:标题栏上下间距16px,左右元素间距24px,确保与HTML一致。
(2)双通道压力显示区

这是核心数据展示区,需重点还原“渐变边框”“大数值”“趋势标签”:

  • 渐变边框:HTML中用gradient-border实现,Figma中需:
    1. 创建外层矩形(尺寸380×140,圆角8px),设置“描边”为线性渐变(#0ea5e9#06b6d4),描边宽度2px;
    2. 内层创建矩形(尺寸376×136,圆角6px),背景色#1e293b,与外层矩形居中对齐,模拟“渐变边框”效果;
  • 数值与单位
    1. 数值文本(Roboto Mono 64px,颜色#0ea5e9,内容“12.8”);
    2. 单位文本(Inter 18px,颜色#94a3b8,内容“kg”),与数值右对齐,底部偏移2px;
  • 趋势标签:矩形背景(#0ea5e9/10,圆角12px),内部文字(“+0.23 kg”,Inter 12px,颜色#94a3b8)+ 箭头图标(颜色#10b981)。
(3)曲线图表区

HTML中用Chart.js实现,Figma中无需实现动态数据,只需还原“图表框架”和“静态曲线”:

  1. 创建图表容器(尺寸520×200,背景色#1e293b,圆角8px);
  2. 绘制坐标轴:X轴(时间标签“0s”“20s”…“600s”,Inter 9px,颜色#64748b),Y轴(数值标签“8kg”“10kg”…“14kg”,同X轴样式);
  3. 绘制曲线:用“钢笔工具”绘制两条平滑曲线(通道1:#0ea5e9,通道2:#8b5cf6),曲线下方添加“渐变填充”(透明度30%→0%)。
(4)按钮与状态组件
  • 零点校准按钮:矩形(高度48px,背景色#0ea5e9/20,圆角8px),文字(“零点校准”,Inter 14px,颜色#0ea5e9)+ 图标(“balance-scale”);
  • 状态指示灯:圆形(尺寸8px,颜色#10b981),添加“脉冲动画”(Figma右侧“原型”面板,设置“不透明度”从100%→50%→100%,周期2s,循环播放)。

3. 导出Figma设计资产

完成UI还原后,导出LVGL开发所需的资源:

  1. 图标资源:将Figma中的按钮图标、状态图标导出为“PNG格式”(分辨率24×24,单色,透明背景);
  2. 样式规范文档:用Figma的“样式指南”功能,导出颜色值(RGB/Hex)、字体参数、组件尺寸表,保存为PDF;
  3. UI截图:导出完整的800×480 UI截图,作为LVGL开发的视觉对比参考。

四、第二步:Figma设计适配LVGL(嵌入式落地)

LVGL是嵌入式领域主流的轻量级GUI库,支持STM32、ESP32等主流MCU,需将Figma的“设计资产”转化为LVGL的“代码对象”,核心是“组件映射”与“资源适配”。

1. 搭建LVGL开发环境

以STM32为例,环境搭建步骤:

  1. 下载LVGL源码(GitHub仓库),版本选择v8.3(稳定性高,文档完善);
  2. 在STM32CubeIDE中创建工程,将LVGL源码添加到工程目录,配置lv_conf.h(开启必要功能,如图表、动画);
  3. 适配屏幕驱动:根据屏幕接口(SPI/RGB),实现LVGL的显示回调函数(lv_port_disp_init()),设置分辨率为800×480。

2. Figma组件→LVGL代码映射(核心实操)

LVGL的UI由“对象(lv_obj_t)”和“样式(lv_style_t)”构成,需按Figma规范创建样式,再生成对应组件。以下是关键组件的实现代码:

(1)初始化全局样式(匹配Figma规范)
// lv_style.h
#include "lvgl.h"// 定义全局样式(对应Figma的颜色、字体、尺寸)
static lv_style_t style_page_bg;    // 页面背景样式
static lv_style_t style_panel;      // 面板样式(Figma的#1e293b)
static lv_style_t style_text_title; // 标题文本样式
static lv_style_t style_text_value; // 数值文本样式(Figma的64px等宽字体)
static lv_style_t style_btn_calib;  // 校准按钮样式// 初始化样式
void lv_style_init_global(void) {// 1. 页面背景样式(Figma的#0f172a)lv_style_init(&style_page_bg);lv_style_set_bg_color(&style_page_bg, lv_color_hex(0x0f172a));lv_style_set_pad_all(&style_page_bg, 16); // 页面内边距16px(Figma)// 2. 面板样式(Figma的#1e293b,圆角8px)lv_style_init(&style_panel);lv_style_set_bg_color(&style_panel, lv_color_hex(0x1e293b));lv_style_set_radius(&style_panel, 8);lv_style_set_pad_all(&style_panel, 16);// 3. 标题文本样式(Figma的Inter 16px,#ffffff)lv_style_init(&style_text_title);lv_style_set_text_color(&style_text_title, lv_color_hex(0xffffff));lv_style_set_text_font(&style_text_title, &lv_font_inter_16); // 预编译的Inter字体// 4. 数值文本样式(Figma的Roboto Mono 64px,#0ea5e9)lv_style_init(&style_text_value);lv_style_set_text_color(&style_text_value, lv_color_hex(0x0ea5e9));lv_style_set_text_font(&style_text_value, &lv_font_roboto_mono_64);// 5. 校准按钮样式(Figma的#0ea5e9/20背景,48px高度)lv_style_init(&style_btn_calib);lv_style_set_bg_color(&style_btn_calib, lv_color_hex(0x0ea5e9)); // 基础色lv_style_set_bg_opa(&style_btn_calib, 20); // 透明度20%(对应#0ea5e9/20)lv_style_set_radius(&style_btn_calib, 8);lv_style_set_min_height(&style_btn_calib, 48); // 最小高度48px(触摸友好)lv_style_set_text_color(&style_btn_calib, lv_color_hex(0x0ea5e9));lv_style_set_text_font(&style_btn_calib, &lv_font_inter_14);
}
(2)创建双通道压力显示面板(对应Figma的核心数据区)
// lv_ui.c
#include "lvgl.h"
#include "lv_style.h"// 创建单个通道的压力显示面板
static lv_obj_t *create_pressure_channel(lv_obj_t *parent, const char *chan_name, lv_color_t chan_color) {// 1. 外层容器(模拟Figma的渐变边框,用嵌套实现)lv_obj_t *chan_container = lv_obj_create(parent);lv_obj_set_size(chan_container, 380, 140); // Figma尺寸lv_obj_set_style_bg_color(chan_container, chan_color, 0);lv_obj_set_style_bg_opa(chan_container, 30, 0); // 边框透明度30%lv_obj_set_style_radius(chan_container, 8, 0);// 2. 内层面板(Figma的#1e293b背景)lv_obj_t *chan_panel = lv_obj_create(chan_container);lv_obj_add_style(chan_panel, &style_panel, 0);lv_obj_set_size(chan_panel, 376, 136); // 比外层小4px,模拟边框lv_obj_center(chan_panel);// 3. 通道名称标签(Figma的“通道1”标签)lv_obj_t *chan_label = lv_label_create(chan_panel);lv_label_set_text_fmt(chan_label, "%s", chan_name);lv_obj_set_style_bg_color(chan_label, chan_color, 0);lv_obj_set_style_bg_opa(chan_label, 20, 0);lv_obj_set_style_radius(chan_label, 12, 0);lv_obj_set_style_pad_hor(chan_label, 8, 0);lv_obj_set_style_pad_ver(chan_label, 2, 0);lv_obj_align(chan_label, LV_ALIGN_TOP_LEFT, 0, 0);// 4. 数值标签(Figma的64px大字体)lv_obj_t *val_label = lv_label_create(chan_panel);lv_label_set_text(val_label, "12.8");lv_obj_add_style(val_label, &style_text_value, 0);lv_obj_set_style_text_color(val_label, chan_color, 0); // 通道专属颜色lv_obj_align(val_label, LV_ALIGN_CENTER, 0, 0);// 5. 单位标签(Figma的“kg”)lv_obj_t *unit_label = lv_label_create(chan_panel);lv_label_set_text(unit_label, "kg");lv_obj_set_style_text_color(unit_label, lv_color_hex(0x94a3b8), 0);lv_obj_set_style_text_font(unit_label, &lv_font_inter_18, 0);lv_obj_align_to(unit_label, val_label, LV_ALIGN_RIGHT_MID, 8, 8); // 与数值右对齐return chan_container;
}// 创建双通道面板(左右布局)
void lv_ui_create_pressure_panel(lv_obj_t *parent) {// 通道1(Figma的#0ea5e9色)lv_obj_t *chan1 = create_pressure_channel(parent, "通道 1", lv_color_hex(0x0ea5e9));lv_obj_align(chan1, LV_ALIGN_LEFT_MID, 0, 0);// 通道2(Figma的#8b5cf6色)lv_obj_t *chan2 = create_pressure_channel(parent, "通道 2", lv_color_hex(0x8b5cf6));lv_obj_align(chan2, LV_ALIGN_RIGHT_MID, 0, 0);
}
(3)创建曲线图表(对应Figma的趋势图)
// 创建压力趋势图表
void lv_ui_create_chart(lv_obj_t *parent) {// 1. 图表容器(Figma的520×200尺寸)lv_obj_t *chart_container = lv_obj_create(parent);lv_obj_add_style(chart_container, &style_panel, 0);lv_obj_set_size(chart_container, 520, 200);lv_obj_align(chart_container, LV_ALIGN_LEFT_MID, 0, 0);// 2. 图表对象(LVGL的lv_chart组件)lv_obj_t *chart = lv_chart_create(chart_container);lv_obj_set_size(chart, 500, 180);lv_obj_center(chart);lv_chart_set_type(chart, LV_CHART_TYPE_LINE); // 线型图表(Figma)lv_chart_set_axis_tick(chart, LV_CHART_AXIS_PRIMARY_Y, 10, 5, 5, 5, true, 40); // Y轴刻度lv_chart_set_axis_tick(chart, LV_CHART_AXIS_PRIMARY_X, 20, 5, 5, 5, true, 40); // X轴刻度// 3. 添加两条曲线(对应双通道)lv_chart_series_t *ser1 = lv_chart_add_series(chart, lv_color_hex(0x0ea5e9), LV_CHART_AXIS_PRIMARY_Y); // 通道1lv_chart_series_t *ser2 = lv_chart_add_series(chart, lv_color_hex(0x8b5cf6), LV_CHART_AXIS_PRIMARY_Y); // 通道2// 4. 初始化模拟数据(后续可替换为传感器真实数据)for (int i = 0; i < 30; i++) {lv_chart_set_next_value(chart, ser1, 12 + (rand() % 30) / 10.0); // 12±1.5kglv_chart_set_next_value(chart, ser2, 8 + (rand() % 20) / 10.0);  // 8±1kg}
}
(4)添加触摸交互(零点校准按钮)
// 校准按钮点击事件回调
static void btn_calib_event_cb(lv_event_t *e) {lv_obj_t *btn = lv_event_get_target(e);lv_obj_t *parent = lv_obj_get_parent(btn);// 模拟校准逻辑:弹出确认框lv_obj_t *msgbox = lv_msgbox_create(parent, "零点校准确认", "请确保传感器无负载,校准将重置零点", NULL, true);lv_obj_set_size(msgbox, 300, 150);lv_obj_center(msgbox);// 确认按钮回调(校准完成)lv_obj_t *btn_confirm = lv_msgbox_get_btns(msgbox)[0];lv_obj_add_event_cb(btn_confirm, (lv_event_cb_t)lv_msgbox_close, LV_EVENT_CLICKED, msgbox);
}// 创建校准按钮
void lv_ui_create_calib_btn(lv_obj_t *parent) {lv_obj_t *btn_calib = lv_btn_create(parent);lv_obj_add_style(btn_calib, &style_btn_calib, 0);lv_obj_set_size(btn_calib, 180, 48);lv_obj_align(btn_calib, LV_ALIGN_RIGHT_MID, 0, 0);// 按钮文本lv_obj_t *btn_text = lv_label_create(btn_calib);lv_label_set_text(btn_text, "零点校准");lv_obj_center(btn_text);// 绑定点击事件lv_obj_add_event_cb(btn_calib, btn_calib_event_cb, LV_EVENT_CLICKED, NULL);
}

3. 嵌入式适配关键优化(避坑指南)

(1)颜色适配:RGB→RGB565

嵌入式屏幕多支持RGB565格式(16位色),而Figma用RGB888(24位色),需转换:

  • 转换工具:在线RGB→RGB565转换器;
  • 示例:Figma的#0ea5e9(RGB:14,165,233)→ RGB565:0x4D7B
  • 代码中替换:lv_color_hex(0x0ea5e9)lv_color_hex(0x4D7B)
(2)字体精简:只编译必要字符

LVGL字体需预编译,直接使用完整字体库会占用大量Flash:

  • 用LVGL的lv_font_conv工具,只编译“0-9”“kg”“通道”“校准”等必要字符;
  • 字体大小:数值用64px,按钮文本用14px,避免过大字体占用内存。
(3)动画优化:降低MCU算力消耗

Figma中的脉冲动画、渐变效果在LVGL中需简化:

  • 脉冲动画:用lv_anim_t实现,周期从2s改为3s,减少CPU占用;
  • 渐变填充:LVGL的图表曲线不支持渐变填充,可改为纯色填充(透明度20%)。

五、常见问题与解决方案

问题场景原因分析解决方案
Figma渐变边框在LVGL中无法实现LVGL不支持直接渐变边框用“嵌套容器”:外层容器设渐变背景,内层容器小4px,模拟边框
LVGL图表数据更新卡顿每次更新重新绘制整个图表,占用算力lv_chart_set_next_value()增量更新,关闭“重绘整个图表”
屏幕显示错位Figma与LVGL的尺寸单位不一致(Figma用px,LVGL用像素)严格按800×480布局,组件尺寸、间距与Figma完全一致
触摸按钮无响应按钮最小高度小于48px,触摸识别不灵敏按Figma规范,按钮最小高度设为48px,增大触摸区域

六、总结

从HTML到Figma再到LVGL的UI迁移,本质是“设计标准化→开发代码化”的过程:

  • Figma 是关键桥梁,将HTML的灵活原型转化为嵌入式开发可复用的设计资产;
  • LVGL适配 需聚焦“资源轻量化”,在保证设计一致性的同时,兼顾嵌入式硬件的性能限制。

这套流程不仅适用于压力传感器UI,还可推广到工业控制、智能家居等嵌入式场景,帮助开发者快速实现“Web原型→嵌入式产品”的落地,提升开发效率与视觉一致性。

后续可进一步探索自动化工具,比如用Figma插件直接生成LVGL样式代码,减少手动映射的工作量,让设计与开发的链路更高效。

http://www.xdnf.cn/news/20185.html

相关文章:

  • C# FileInfo 类深度解析文件时间属性
  • NIPT 的时点选择与胎儿的异常判定
  • leetcode162.寻找峰值
  • STM32 读写备份寄存器
  • VR红色教育基地+数字党建展厅+智慧校史馆
  • 网络安全防护——主动防护和被动防护
  • java程序员的爬虫技术
  • 研发文档更新滞后的常见原因与解决方法
  • 【大模型实战笔记 1】Prompt-Tuning方法
  • 【IO进程 共享内存、信号量集】
  • Redis AOF 持久化:银行的 “交易流水单” 管理逻辑
  • 从质疑到真香:小白使用「飞牛NAS」+「节点小宝」的花式操作
  • .NET 开发者的“Fiddler”:Titanium.Web.Proxy 库的强大魅力
  • 虚拟化安全:从逃逸漏洞到实战分析
  • Python快速入门专业版(三):print 格式化输出:% 占位符、format 方法与 f-string(谁更高效?)
  • vue+elementUI 进行表格行内新增及校验,同行其他输入框数据影响当前输入框校验结果
  • 【ComfyUI】涂鸦 ControlNet 涂鸦参考引导生成
  • django全国小米su7的行情查询系统(代码+数据库+LW)
  • 论文介绍:Fast3R,更快的多视图 3D 重建的新范式
  • 计算机原理(一)
  • 4.5.8版本来了~山海鲸最新内容抢鲜看
  • 2025 全国大学生数学建模竞赛题目-B 题 碳化硅外延层厚度的确定 问题二完整思路
  • Coze插件AI复刻之:网页截图
  • 数据结构准备:包装类+泛型
  • 大语言模型推理的幕后英雄:深入解析Prompt Processing工作机制
  • 时序数据库IoTDB的六大实用场景盘点
  • 基于机器学习的缓存准入策略研究
  • 服务器异常磁盘写排查手册 · 已删除文件句柄篇
  • 安装与配置Jenkins(小白的”升级打怪“成长之路)
  • AI-Agent智能体提示词工程使用分析