4、样式属性详解(下):文本属性(字体、颜色、对齐、行高)、阴影属性
好,咱们接着聊。上一节我们把背景和边框扒了个底朝天,这一节轮到文本和阴影了。说实话,文本属性在嵌入式GUI里是最容易被忽视的。很多人觉得「不就是显示几个字嘛」,但真正做产品时,字体的选择、对齐的偏差、行高的挤压,往往会让界面看起来「差那么一口气」。
我个人习惯把文本属性分成两类:「看得见的」和「排得好的」。前者是字体、颜色、阴影这些视觉元素;后者是对齐、行高、间距这些排版规则。咱们一个一个来。
4.1 字体属性:字体的选择与加载
LVGL的字体系统其实挺灵活的。它支持内置字体、外部字体文件,甚至支持字体回退(fallback)。我在项目中遇到过最头疼的问题就是:中文字体太大,Flash装不下。
先看最基础的字体设置:
/* 设置字体为内置的 Montserrat 16 */
lv_obj_set_style_text_font(btn, &lv_font_montserrat_16, 0);
/* 或者用 LV_FONT_DECLARE 声明外部字体 */
LV_FONT_DECLARE(my_custom_font);
lv_obj_set_style_text_font(label, &my_custom_font, 0);
这里有个坑:字体文件必须提前编译进固件。LVGL不像PC端可以动态加载字体,它所有的字体都是在编译时通过 lv_font_conv 工具转换成的C数组。我曾经因为忘记在 lv_conf.h 中启用某个字体,结果编译通过但显示全是方框——嗯,排查了整整一下午。
4.2 文本颜色:不止是 RGB
文本颜色设置很简单:
lv_obj_set_style_text_color(label, lv_color_hex(0x333333), 0);
但我想说的是,颜色选择有讲究。你想想看,深色背景上放纯白色文字,虽然对比度高,但看久了眼睛会累。我一般建议:正文用 #333333 或 #444444,标题用 #1a1a1a,辅助信息用 #999999。这个灰度梯度在大多数屏幕上表现都很舒服。
另外,LVGL支持 文本颜色渐变(从8.0开始):
lv_obj_set_style_text_color_grad(label, lv_color_hex(0xFF0000), 0);
lv_obj_set_style_text_color_grad_dir(label, LV_GRAD_DIR_HOR, 0);
不过说实话,渐变文本在嵌入式设备上用得不多,因为会消耗额外的CPU资源。除非你做的是音乐播放器或者游戏UI,否则我建议慎用。
4.3 文本对齐:左、中、右、两端
对齐属性看似简单,但实际项目中翻车率很高。LVGL提供了四种对齐方式:
| 对齐方式 | 宏定义 | 说明 |
|---|---|---|
| 左对齐 | LV_TEXT_ALIGN_LEFT |
默认值,适合正文 |
| 居中对齐 | LV_TEXT_ALIGN_CENTER |
适合标题、按钮文字 |
| 右对齐 | LV_TEXT_ALIGN_RIGHT |
适合数字、状态信息 |
| 两端对齐 | LV_TEXT_ALIGN_JUSTIFY |
适合多行文本,但嵌入式用得少 |
代码示例:
lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_CENTER, 0);
这里有个容易踩的坑:对齐只对多行文本有效。如果文本只有一行,左中右其实没区别——因为LVGL默认会裁剪掉空白。我曾经做一个菜单界面,明明设置了居中对齐,但文字还是偏左,排查了半天才发现是文本只有一行,而标签宽度刚好等于文字宽度。解决方案是给标签设置一个固定的宽度:
lv_obj_set_width(label, 200); /* 给个固定宽度 */
lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_CENTER, 0);
4.4 行高:别让文字挤在一起
行高这个属性,说实话在LVGL里有点「隐藏」。它不是直接叫 line_height,而是通过 text_line_space 来控制:
lv_obj_set_style_text_line_space(label, 5, 0); /* 行间距5像素 */
这个值表示行与行之间的额外间距。默认是0,但实际渲染时LVGL会根据字体大小自动计算一个基础行高。我个人习惯给多行文本加3-5像素的行间距,这样阅读起来更舒服。
你可能会问:那字符间距呢?LVGL也提供了:
lv_obj_set_style_text_letter_space(label, 1, 0); /* 字间距1像素 */
不过字间距我一般不动,除非是做特殊效果(比如标题加宽间距显得更大气)。
lv_obj_set_style_align(label, LV_ALIGN_CENTER, 0) 使用。但注意,这是对象对齐,不是文本对齐。
4.5 阴影属性:给文字加点「立体感」
阴影在LVGL里属于「文本修饰」的一部分。它不像CSS那样有 text-shadow 属性,而是通过 shadow 相关的样式属性来实现。不过要注意:LVGL的阴影是作用于整个对象的,不是单独作用于文字。
阴影属性包括:
| 属性 | 函数 | 说明 |
|---|---|---|
| 阴影宽度 | lv_obj_set_style_shadow_width |
阴影的模糊半径,0表示无阴影 |
| 阴影颜色 | lv_obj_set_style_shadow_color |
阴影的颜色,建议用半透明 |
| 阴影偏移 | lv_obj_set_style_shadow_ofs_x/y |
阴影在X/Y方向的偏移量 |
| 阴影透明度 | lv_obj_set_style_shadow_opa |
阴影的透明度,0-255 |
一个典型的阴影效果:
lv_obj_set_style_shadow_width(label, 10, 0);
lv_obj_set_style_shadow_color(label, lv_color_hex(0x000000), 0);
lv_obj_set_style_shadow_ofs_x(label, 3, 0);
lv_obj_set_style_shadow_ofs_y(label, 3, 0);
lv_obj_set_style_shadow_opa(label, 80, 0); /* 约30%透明度 */
这里有个性能问题:阴影会触发重绘。如果你在列表或者滚动容器里大量使用阴影,帧率可能会下降。我记得有一次做智能家居面板,每个卡片都加了阴影,结果滑动时明显卡顿。后来我把阴影宽度从8像素减到3像素,并且只在选中态显示阴影,问题就解决了。
4.6 综合实战:一个带阴影的标题卡片
说了这么多,咱们来写一个完整的例子。假设我们要做一个卡片标题,包含:大号字体、居中对齐、深灰色、带轻微阴影。
/* 创建标签 */
lv_obj_t * title = lv_label_create(container);
lv_label_set_text(title, "系统状态");
/* 字体:使用内置的 Montserrat 20 */
lv_obj_set_style_text_font(title, &lv_font_montserrat_20, 0);
/* 颜色:深灰色 */
lv_obj_set_style_text_color(title, lv_color_hex(0x2c3e50), 0);
/* 对齐:居中 */
lv_obj_set_style_text_align(title, LV_TEXT_ALIGN_CENTER, 0);
/* 阴影:轻微下偏 */
lv_obj_set_style_shadow_width(title, 6, 0);
lv_obj_set_style_shadow_color(title, lv_color_hex(0x000000), 0);
lv_obj_set_style_shadow_ofs_x(title, 0, 0);
lv_obj_set_style_shadow_ofs_y(title, 2, 0);
lv_obj_set_style_shadow_opa(title, 60, 0);
/* 让标签居中显示 */
lv_obj_center(title);
这个效果做出来,文字会有一点点「浮起来」的感觉,但又不会太夸张。我个人觉得,这种微妙的层次感,正是好UI和普通UI的分水岭。
4.7 避坑指南
最后,分享几个我踩过的坑:
- 字体回退问题: 如果你用了外部字体,但某个字符在字体文件中不存在,LVGL会显示为方框。解决办法是启用字体回退:在
lv_conf.h中设置LV_FONT_FMT_TXT_LARGE为1,并确保有至少一个包含该字符的字体被编译。 - 阴影与透明背景: 如果标签背景是透明的(默认就是),阴影会直接投射到父容器上。如果你不想这样,可以给标签设置一个圆角背景,阴影会显得更自然。
- 行高与多行文本: 当文本自动换行时,行高设置才生效。如果文本只有一行,设置行高是没用的——别问我怎么知道的。
好了,文本和阴影就聊到这里。下一节我们会进入更「高级」的样式属性——变换和动画。说实话,那才是真正让UI「活起来」的东西。咱们到时候见。