16 KiB
name, description
| name | description |
|---|---|
| packer-ui | 包装机界面(packer_ui)设计规范与开发指南。包括布局尺寸、颜色体系、字体使用和后续开发指导。适用于800x480 LCD上的LVGL界面。 |
Packer UI Design Guide
1. 布局总览
屏幕分辨率为 800x480,整体划分为左右两栏:
+--------------------------------------------------------------+
| +--------------------------+ +--------------------------+ |
| | 状态卡片 (左) | | 启动按钮 (右) | |
| | x=10, y=10 | | x=580, y=10 | |
| | w=560, h=330 | | w=210, h=330 | |
| | | | | |
| | +----------+----------+ | | "启动" | |
| | | 当前重量 | 总包数 | | | | |
| | | | | | | | |
| | | 0.0 | 0 | | | | |
| | | | | | | | |
| | | 称重斗.. | 累计成品 | | | | |
| | +----------+----------+ | | | |
| | 工序 进料 | o 已停止 | | | |
| +--------------------------+ +--------------------------+ |
| +--------------------------+ +--------------------------+ |
| | 目标重量卡片 (左下) | | 复位按钮 (右下) | |
| | x=10, y=350 | | x=580, y=350 | |
| | w=560, h=120 | | w=210, h=120 | |
| | | | | |
| | 设计重量 190.0 克/包 | | 复位 | |
| | o(-) o(+) | | | |
| +--------------------------+ +--------------------------+ |
+--------------------------------------------------------------+
2. 容器与位置参数
2.1 顶层容器
| 控件 | 创建函数 | x | y | w | h | 圆角 | 背景色 |
|------|---------|---|---|---|---|------|--------|
| packer_scr | lv_obj_create | - | - | 800 | 480 | - | COLOR_BG |
| card_status | lv_obj_create | 10 | 10 | 560 | 330 | 20 | COLOR_CARD |
| card_target | lv_obj_create | 10 | 350 | 560 | 120 | 20 | COLOR_CARD_TARGET |
| btn_start | lv_btn_create | 580 | 10 | 210 | 330 | 20 | COLOR_BTN_START |
| btn_reset | lv_btn_create | 580 | 350 | 210 | 120 | 20 | COLOR_BTN_DARK |
| btn_minus | lv_btn_create | 331 | 30 | 60 | 60 | 30 | COLOR_BTN_ADJ |
| btn_plus | lv_btn_create | 463 | 30 | 60 | 60 | 30 | COLOR_BTN_ADJ |
2.2 状态卡片内部分布
card_status (560x330) 被垂直分割线分成左右两半:
左半区 -- 当前重量 (宽度 281px)
| 控件 | 文本 | 父级x偏移 | 父级y偏移 | 宽 | 高 | 字体 | 颜色 |
|------|------|----------|----------|-----|-----|------|------|
| w_title | "当前重量" | 0 | 14 | 281 | 28 | custom_cjk_20 | COLOR_GREEN |
| label_current_weight | "0.0" | 0 | 90 | 281 | 84 | custom_cjk_60 | COLOR_TEXT_MAIN |
| w_sub | "称重斗实时值" | 0 | 200 | 281 | 20 | custom_cjk_16 | 0x6f9181 |
右半区 -- 总包数 (宽度 278px)
| 控件 | 文本 | 父级x偏移 | 父级y偏移 | 宽 | 高 | 字体 | 颜色 |
|------|------|----------|----------|-----|-----|------|------|
| c_title | "总包数" | 282 | 14 | 278 | 30 | custom_cjk_20 | COLOR_GREEN |
| label_pack_count | "0" | 282 | 90 | 278 | 84 | custom_cjk_60 | COLOR_TEXT_MAIN |
| c_sub | "累计成品" | 282 | 200 | 278 | 20 | custom_cjk_16 | 0x6f9181 |
分割线
| 控件 | 位置(x,y) | 尺寸(w,h) | 颜色 |
|------|----------|----------|------|
| horizontal_line | (40, 240) | (480, 1) | COLOR_BORDER |
| vertical_line | (280, 20) | (1, 290) | COLOR_BORDER |
状态指示
| 控件 | 文本 | 父级x偏移 | 父级y偏移 | 宽 | 高 | 圆角 | 颜色 |
|------|------|----------|----------|-----|-----|------|------|
| status_dot | - | 330 | 270 | 34 | 35 | 17(圆形) | COLOR_GREEN |
| label_status | "已停止" | 420 | 280 | - | - | - | COLOR_TEXT_MAIN, font 20px |
工序状态 (左下, 分割线下方)
标题 "工序" 位于 x=50, y=270, 使用 font 20px, COLOR_GREEN。
当前工序名称紧跟在标题右侧, 使用 font 20px, COLOR_TEXT_MAIN:
| 控件 | 文本(初始) | x | y | 字体 | 颜色 |
|-----|-----------|----|----|------|------|
| proc_title | "工序" | 50 | 270 | custom_cjk_20 | COLOR_GREEN |
| label_step | "进料" | 120 | 270 | custom_cjk_20 | COLOR_TEXT_MAIN |
通过 current_step (0~5) 索引 step_name[] 数组动态切换显示的工序:
static const char *step_name[] = {"进料", "称重", "压缩", "打包", "封包", "出料"};
if (current_step < 6)
lv_label_set_text(label_step, step_name[current_step]);
2.3 目标重量卡内部布局
card_target (560x120) 从左到右:
| 控件 | 文本 | 父级x偏移 | 父级y偏移 | 宽/尺寸 | 高 | 字体 | 颜色 |
|------|------|----------|----------|---------|-----|------|------|
| t_title | "设计重量" | 0 | 50 | 90 | 120 | custom_cjk_20 | COLOR_GREEN |
| label_target_val | "190.0" | 100 | 30 | 150 | 120 | custom_cjk_60 | COLOR_TEXT_MAIN |
| t_unit | "克/包" | 240 | 50 | - | - | custom_cjk_20 | 0x6f9181 |
| btn_minus | "-" | 331 | 30 | 60x60 | - | custom_cjk_60 | COLOR_TEXT_MAIN |
| btn_plus | "+" | 463 | 30 | 60x60 | - | custom_cjk_40 | COLOR_TEXT_MAIN |
btn_minus 字符"-"用 font 60px, btn_plus 字符"+"用 font 40px, 视觉上调整到大小接近。
3. 颜色体系
3.1 颜色常量定义
COLOR_BG = 0x161b22 // 深色背景(深蓝黑)
COLOR_CARD = 0x000000 // 卡片背景(纯黑)
COLOR_CARD_TARGET = 0x080a0e // 目标重量卡片(近乎纯黑)
COLOR_BORDER = 0x30363d // 分割线颜色(暗灰)
COLOR_TEXT_MAIN = 0xffffff // 主文字(纯白)
COLOR_TEXT_SEC = 0x9ab3a5 // 次要文字(薄荷灰)
COLOR_BTN_START = 0x06990c // 启动按钮背景(绿色)
COLOR_BTN_DARK = 0x000000 // 复位按钮背景(纯黑)
COLOR_BTN_ADJ = 0x1e3c28 // 加减按钮背景(深绿)
COLOR_GREEN = 0x06990c // 状态灯/绿色指示(绿色)
COLOR_RED = 0x990B06 // 红色(深红)
COLOR_ADJ_ICON = 0xc4c4c4 // 调节图标辅助色(浅灰)
3.2 辅助颜色(直接硬编码)
| 用途 | 颜色值 | 说明 |
|------|--------|------|
| w_sub / c_sub 文字 | 0x6f9181 | 副标题, 茶绿色 |
| t_unit "克/包" | 0x6f9181 | 单位标签, 茶绿色 |
3.3 状态灯逻辑
状态灯(status_dot)是一个 34x35 的圆形, 通过背景色切换运行/停止:
| 状态 | 圆点颜色 | 圆点值 | 文字颜色 |
|------|---------|--------|---------|
| 运行中 | COLOR_GREEN | 0x06990c | COLOR_TEXT_MAIN |
| 已停止 | COLOR_RED | 0x990B06 | COLOR_TEXT_MAIN |
状态灯右侧显示文字 label_status, 内容对应切换: "运行中" / "已停止", 文字始终为白色 (COLOR_TEXT_MAIN)。
4. 字体系统
4.1 字体规格
使用 SimHei(黑体)生成的 LVGL 位图字体, 4bpp 抗锯齿:
| 大小 | 字体变量 | 适用场景 |
|:----:|----------|----------|
| 16px | custom_cjk_16 | 副标题, 辅助说明文字 |
| 20px | custom_cjk_20 | 区域标题, 标签, 单位 |
| 40px | custom_cjk_40 | 按钮文字, 部分符号(+) |
| 60px | custom_cjk_60 | 数值显示, 大字符号(-) |
4.2 字体声明
LV_FONT_DECLARE(custom_cjk_16);
LV_FONT_DECLARE(custom_cjk_20);
LV_FONT_DECLARE(custom_cjk_40);
LV_FONT_DECLARE(custom_cjk_60);
5. 按钮样式规则
所有按钮统一移除阴影, 轮廓, 边框:
// 每个按钮创建后必须执行的样式
lv_obj_set_style_shadow_width(btn, 0, 0);
lv_obj_set_style_shadow_opa(btn, LV_OPA_0, 0);
lv_obj_set_style_outline_width(btn, 0, 0);
lv_obj_set_style_outline_opa(btn, LV_OPA_0, 0);
lv_obj_set_style_border_width(btn, 0, 0);
lv_obj_set_style_border_side(btn, LV_BORDER_SIDE_NONE, 0);
按钮样式差异(仅背景色和圆角不同):
| 按钮 | 圆角 | 背景色 | 文字颜色 | 字体 |
|------|------|--------|---------|------|
| btn_start | 20 | COLOR_BTN_START (绿) | COLOR_TEXT_MAIN (白) | custom_cjk_40 |
| btn_reset | 20 | COLOR_BTN_DARK (黑) | COLOR_TEXT_SEC (灰绿) | custom_cjk_40 |
| btn_minus | 30 | COLOR_BTN_ADJ (深绿) | COLOR_TEXT_MAIN (白) | custom_cjk_60 |
| btn_plus | 30 | COLOR_BTN_ADJ (深绿) | COLOR_TEXT_MAIN (白) | custom_cjk_40 |
6. 卡片通用样式
所有卡片(create_card)默认属性:
lv_obj_set_style_border_width(card, 0, 0); // 无边框
lv_obj_clear_flag(card, LV_OBJ_FLAG_SCROLLABLE); // 不可滚动
// 圆角在 create_card 的参数中传入
// 背景色在 create_card 的参数中传入
状态卡片和目标重量卡片额外设置:
lv_obj_set_style_pad_hor(card, 0, LV_PART_MAIN);
lv_obj_set_style_pad_ver(card, 0, LV_PART_MAIN);
lv_obj_set_style_outline_width(card, 0, LV_PART_MAIN);
lv_obj_set_style_border_width(card, 0, LV_PART_MAIN);
7. 回调函数
| 回调 | 绑定的控件 | 触发事件 | 行为 |
|------|-----------|---------|------|
| start_cb | btn_start | LV_EVENT_CLICKED | 设置 is_running=true, 清零 current_weight, 更新显示 |
| reset_cb | btn_reset | LV_EVENT_CLICKED | 设置 is_running=false, 清零 weight 和 pack_count, 更新显示 |
| minus_cb | btn_minus | LV_EVENT_CLICKED | 运行中忽略, 否则 target_weight -= 10 (下限 10), 更新显示 |
| plus_cb | btn_plus | LV_EVENT_CLICKED | 运行中忽略, 否则 target_weight += 10 (上限 2000), 更新显示 |
8. 核心数据结构
// 控件指针(文件内 static)
static lv_obj_t *label_target_val; // 目标重量值标签
static lv_obj_t *label_current_weight; // 当前重量值标签
static lv_obj_t *label_pack_count; // 总包数值标签
static lv_obj_t *label_status; // 状态文字标签
static lv_obj_t *status_dot; // 状态指示圆点
static lv_obj_t *label_step; // 当前工序标签(进料/称重/压缩/打包/封包/出料)
// 状态变量(volatile, 可能被中断/任务修改)
static volatile bool is_running = false; // 运行状态
static volatile uint8_t current_step = 0; // 当前工序索引(0~5)
static volatile uint32_t target_weight = 190; // 目标重量 (单位: 0.1g)
static volatile uint32_t current_weight = 230; // 当前重量 (单位: 0.1g)
static volatile uint32_t pack_count = 2340; // 总包数
9. update_display() 刷新规则
每次状态变更后调用 update_display():
static void update_display(void)
{
char tmp[32];
// 1. 刷新当前重量(保留一位小数)
snprintf(tmp, sizeof(tmp), "%lu.%lu",
(unsigned long)(current_weight / 10),
(unsigned long)(current_weight % 10));
lv_label_set_text(label_current_weight, tmp);
// 2. 刷新总包数(整数)
snprintf(tmp, sizeof(tmp), "%lu", (unsigned long)pack_count);
lv_label_set_text(label_pack_count, tmp);
// 3. 刷新状态灯颜色
lv_obj_set_style_bg_color(status_dot,
is_running ? COLOR_GREEN : COLOR_RED, 0);
// 4. 刷新状态文字
lv_label_set_text(label_status, is_running ? "运行中" : "已停止");
}
update_display()由外部任务循环调用, 也由 start_cb/reset_cb 等回调触发。
10. 后续开发指南
10.1 新增控件原则
-
所有坐标都以 800x480 为基准, 不要超出范围
-
使用
create_card()辅助函数创建新的卡片容器, 统一无边框无滚动 -
新按钮必须执行 "无边框三部曲": shadow=0, outline=0, border=0
-
新文字标签必须指定字体, 不要依赖默认字体(默认字体不含中文)
10.2 颜色使用
-
标题一律用 COLOR_GREEN, 内容/数值一律用 COLOR_TEXT_MAIN (白色)
-
优先使用已定义的 COLOR_xxx 常量, 不要随意硬编码新颜色
-
如需新增颜色: 在
set_colors()中添加新常量, 不要在代码中直接写lv_color_hex() -
副标题文字绿色调使用
0x6f9181, 与 COLOR_TEXT_SEC (0x9ab3a5) 区分 -
COLOR_TEXT_SEC 目前仅用于复位按钮文字(白色改为后已不再使用, 保留以备后续)
10.3 字体使用
-
标题/说明: custom_cjk_20
-
数值: custom_cjk_60
-
辅助文字: custom_cjk_16
-
按钮: custom_cjk_40
-
如需新增汉字: 使用 gen_font 技能重新生成字体
10.4 添加新页面
如需添加新页面(如设置页, 历史记录页), 建议步骤:
-
新建
xxx_ui.cpp/xxx_ui.h(参考 packer_ui 结构) -
遵循相同的卡片布局风格: 深色背景, 圆角卡片, 绿黑配色
-
在新文件的
xxx_ui_create()中完成创建 -
在
lv_scr_load()切换页面时, 先用lv_obj_del()删除旧页面控件(可选) -
在
main.cpp或任务流程中调用新页面的 create 函数
10.5 动画与过渡
当前界面为静态布局, 如需添加过渡效果:
// 示例: 按钮按下缩放动画
lv_anim_t a;
lv_anim_init(&a);
lv_anim_set_var(&a, btn);
lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)lv_obj_set_scale);
lv_anim_set_values(&a, 256, 280); // LV_IMG_ZOOM_NONE=256
lv_anim_set_time(&a, 100);
lv_anim_start(&a);
10.6 国际化和单位
-
所有 UI 文字为中文硬编码
-
重量显示格式:
"190.0"(一位小数), 重量内部以 0.1g 为单位存储 -
如需支持多语言, 建议将所有字符串抽取到常量表中
10.7 触摸事件
当前按钮使用 LV_EVENT_CLICKED 事件, 如需区分按下/释放:
lv_obj_add_event_cb(btn, my_cb, LV_EVENT_PRESSED, NULL);
lv_obj_add_event_cb(btn, my_cb, LV_EVENT_RELEASED, NULL);
static void my_cb(lv_event_t *e)
{
lv_event_code_t code = lv_event_get_code(e);
if (code == LV_EVENT_PRESSED) {
// 按下时改变按钮样式
} else if (code == LV_EVENT_RELEASED) {
// 释放时恢复
}
}
10.8 键盘/物理按键支持
LVGL 默认支持键盘焦点导航。焦点指示器为按钮的 outline 样式。如需自定义焦点样式:
// 自定义焦点时的边框颜色
lv_obj_set_style_outline_color(btn, lv_color_hex(0x58a6ff), LV_STATE_FOCUSED);
lv_obj_set_style_outline_width(btn, 2, LV_STATE_FOCUSED);
11. 文件位置
| 文件 | 路径 |
|------|------|
| UI 源码 | app/tasks/packer_ui.cpp |
| UI 头文件 | app/tasks/packer_ui.h |
| 调试日志 | app/tasks/debug_log.h |
| 字体生成脚本 | regen_font_new.py |
12. 界面逻辑
-
当前重量数值跟随着称称重变化
-
总包数数值跟包装好的包数变化
-
状态灯颜色跟运行状态变化
-
状态文字跟运行状态变化
-
当前工序标签跟当前工序变化
-
增加按键点击时,设计重量会增加1g,最大值90g
-
减小按键点击时,设计重量会减少1g,最小值1g
-
复位按键点击时,发出机器复位的指令,所有工序停止,并且各种结构归零复位。
-
运行按键点击时,开始运行,并开始进行包装。并且启动按键变成红色,文字变成停止,状态灯变绿,文字变为运行中,再次点击时,按键变为绿色言,文字变为启动,状态灯变红,文字变为已停止。