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 中启用某个字体,结果编译通过但显示全是方框——嗯,排查了整整一下午。

注意: 中文字体体积较大。建议只包含常用汉字(约3500个),或者使用分段加载。LVGL 8.3+ 支持字体压缩,可以节省约50%的空间。

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「活起来」的东西。咱们到时候见。