【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、按钮文本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基础画布

  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规范)
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样式代码,减少手动映射的工作量,让设计与开发的链路更高效。

相关推荐
高级测试工程师欧阳2 小时前
HTML 基本结构
前端
Gazer_S2 小时前
【Element Plus 表单组件样式统一 & CSS 文字特效实现指南】
前端·css·vue.js
蔗理苦2 小时前
2025-09-04 HTML1——环境配置与简介
css·vscode·html
一只小阿乐2 小时前
Html重绘和重排
前端·html
_Rookie._2 小时前
vue3 使用css变量
前端·javascript·html
杨超越luckly2 小时前
HTML应用指南:利用GET请求获取全国招商银行网点位置信息
前端·arcgis·信息可视化·html·银行网点
空山新雨(大队长)2 小时前
HTML第六课:表格展示
html
蔗理苦2 小时前
2025-09-04 HTML3——区块布局与表单
前端·css·html
GIS之路3 小时前
GDAL 开发起步
前端