从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 、按钮文本14px |
Figma中匹配相似字体,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基础画布
- 打开Figma,新建"设计文件",创建800×480像素的框架(Frame),命名为"嵌入式压力传感器UI";
- 设置框架背景色为HTML的页面背景色
#0f172a
,关闭"自动调整大小",确保尺寸固定。
2. 还原HTML核心组件(逐个击破)
按HTML的布局结构(标题栏→压力显示区→图表区→状态栏),在Figma中逐一重建组件,重点关注"样式一致性"和"可复用性"。
(1)标题栏组件
- 结构:左侧标题(图标+文字)+ 右侧时间/设置按钮;
- 实操 :
- 左侧:添加"文本"层(内容"双通道压力传感器监控系统",字体Inter 16px,颜色
#ffffff
),左侧搭配"图标"(Figma搜索"dashboard",颜色#0ea5e9
); - 右侧:时间文本(Inter 14px,颜色
#94a3b8
),设置按钮(圆形,尺寸40×40,背景色#1e293b
,图标"cog",颜色#94a3b8
); - 间距:标题栏上下间距16px,左右元素间距24px,确保与HTML一致。
- 左侧:添加"文本"层(内容"双通道压力传感器监控系统",字体Inter 16px,颜色
(2)双通道压力显示区
这是核心数据展示区,需重点还原"渐变边框""大数值""趋势标签":
- 渐变边框 :HTML中用
gradient-border
实现,Figma中需:- 创建外层矩形(尺寸380×140,圆角8px),设置"描边"为线性渐变(
#0ea5e9
→#06b6d4
),描边宽度2px; - 内层创建矩形(尺寸376×136,圆角6px),背景色
#1e293b
,与外层矩形居中对齐,模拟"渐变边框"效果;
- 创建外层矩形(尺寸380×140,圆角8px),设置"描边"为线性渐变(
- 数值与单位 :
- 数值文本(Roboto Mono 64px,颜色
#0ea5e9
,内容"12.8"); - 单位文本(Inter 18px,颜色
#94a3b8
,内容"kg"),与数值右对齐,底部偏移2px;
- 数值文本(Roboto Mono 64px,颜色
- 趋势标签 :矩形背景(
#0ea5e9/10
,圆角12px),内部文字("+0.23 kg",Inter 12px,颜色#94a3b8
)+ 箭头图标(颜色#10b981
)。
(3)曲线图表区
HTML中用Chart.js实现,Figma中无需实现动态数据,只需还原"图表框架"和"静态曲线":
- 创建图表容器(尺寸520×200,背景色
#1e293b
,圆角8px); - 绘制坐标轴:X轴(时间标签"0s""20s"..."600s",Inter 9px,颜色
#64748b
),Y轴(数值标签"8kg""10kg"..."14kg",同X轴样式); - 绘制曲线:用"钢笔工具"绘制两条平滑曲线(通道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开发所需的资源:
- 图标资源:将Figma中的按钮图标、状态图标导出为"PNG格式"(分辨率24×24,单色,透明背景);
- 样式规范文档:用Figma的"样式指南"功能,导出颜色值(RGB/Hex)、字体参数、组件尺寸表,保存为PDF;
- UI截图:导出完整的800×480 UI截图,作为LVGL开发的视觉对比参考。
四、第二步:Figma设计适配LVGL(嵌入式落地)
LVGL是嵌入式领域主流的轻量级GUI库,支持STM32、ESP32等主流MCU,需将Figma的"设计资产"转化为LVGL的"代码对象",核心是"组件映射"与"资源适配"。
1. 搭建LVGL开发环境
以STM32为例,环境搭建步骤:
- 下载LVGL源码(GitHub仓库),版本选择v8.3(稳定性高,文档完善);
- 在STM32CubeIDE中创建工程,将LVGL源码添加到工程目录,配置
lv_conf.h
(开启必要功能,如图表、动画); - 适配屏幕驱动:根据屏幕接口(SPI/RGB),实现LVGL的显示回调函数(
lv_port_disp_init()
),设置分辨率为800×480。
2. Figma组件→LVGL代码映射(核心实操)
LVGL的UI由"对象(lv_obj_t)"和"样式(lv_style_t)"构成,需按Figma规范创建样式,再生成对应组件。以下是关键组件的实现代码:
(1)初始化全局样式(匹配Figma规范)
c
// 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的核心数据区)
c
// 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的趋势图)
c
// 创建压力趋势图表
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); // 通道1
lv_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.5kg
lv_chart_set_next_value(chart, ser2, 8 + (rand() % 20) / 10.0); // 8±1kg
}
}
(4)添加触摸交互(零点校准按钮)
c
// 校准按钮点击事件回调
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样式代码,减少手动映射的工作量,让设计与开发的链路更高效。