LVGL 入门第二课:文字是怎么显示出来的?一文搞懂 lv_label

前言:界面有了,文字怎么显示?

上一篇我们讲了 lv_obj

也就是 LVGL 里最基础的对象。

理解了 lv_obj 之后,就知道 LVGL 里面很多控件并不是完全孤立的东西,它们本质上都建立在对象系统之上。

但是只有一个空对象,界面还是很单调。

接下来最常用的控件,就是文字控件。

比如:

  • 页面标题
  • 按钮文字
  • 状态提示
  • 温度数值
  • 设备名称
  • 错误信息

这些内容在 LVGL 里基本都离不开一个控件:

lv_label

它的作用很直接:

显示文字。

不过,新手第一次使用 lv_label 时,通常会遇到一些问题:

  • 英文能显示,中文显示不了。
  • 设置了变量,但数值没有更新。
  • 文字太长,超出了屏幕。
  • 想让文字换行,不知道该怎么写。
  • 想改文字颜色,却设置成了背景颜色。
  • 设置了居中对齐,但看起来没有效果。

所以这一篇,我们就专门讲 lv_label

先把文字显示这件事讲清楚。


一、lv_label 是什么?

lv_label 是 LVGL 中用于显示文本的控件。

它可以用来显示:

  • 普通字符串
  • 数字变量
  • 多行文本
  • 不同字体的文字
  • 不同颜色的文字
  • 超长文本的换行、滚动或省略

但是在学习 lv_label 之前,要先记住一点:

label 也是对象。

也就是说,lv_label 虽然是标签控件,但它仍然可以使用很多 lv_obj 的通用函数。

比如:

lv_obj_center(label);
lv_obj_align(label, LV_ALIGN_TOP_MID, 0, 20);
lv_obj_set_width(label, 200);
lv_obj_set_style_text_color(label, lv_color_hex(0xFF0000), LV_PART_MAIN);

这些函数并不是 lv_label 独有的。

它们来自 LVGL 的对象系统。

这也是上一篇先讲 lv_obj 的原因。

后面学按钮、开关、滑条、图表时也一样:

先理解对象,再理解具体控件。

二、创建第一个标签

创建标签使用这个函数:

lv_obj_t * lv_label_create(lv_obj_t * parent);

参数说明:

参数 说明
parent 父对象,表示标签要创建到哪个对象里面

返回值说明:

返回值 说明
lv_obj_t * 新创建的标签对象

最简单的例子:

void init_page(void)
{
    lv_obj_t * label = lv_label_create(lv_screen_active());
    lv_label_set_text(label, "Hello LVGL");
    lv_obj_center(label);
}

这段代码一共做了三件事:

  1. lv_label_create() 创建标签。
  2. lv_label_set_text() 设置文字。
  3. lv_obj_center() 让标签居中。

如果你看的是旧版本 LVGL 教程,可能会看到这种写法:

lv_scr_act()

本文基于 LVGL 9.2 来写,所以后面的示例优先使用:

lv_screen_active()

三、修改标签内容

设置标签文本最常用的是:

lv_label_set_text(label, "Hello");

它的作用就是把标签显示的内容改成指定字符串。

例如:

lv_obj_t * label = lv_label_create(lv_screen_active());
lv_obj_center(label);

lv_label_set_text(label, "System Ready");

后续如果要修改文字,可以继续调用:

lv_label_set_text(label, "System Running");

所以 lv_label_set_text() 不只是初始化时能用。

运行过程中也可以用它更新标签内容。

比如状态从“准备中”变成“运行中”,或者从“连接中”变成“已连接”,都可以通过更新 label 文本来实现。


四、显示变量:lv_label_set_text_fmt()

实际开发中,标签经常不是显示固定文字,而是显示变量。

比如温度:

int temp = 32;

如果想显示:

Temp: 32 C

可以使用:

lv_label_set_text_fmt(label, "Temp: %d C", temp);

完整例子:

void init_page(void)
{
    int temp = 32;

    lv_obj_t * label = lv_label_create(lv_screen_active());
    lv_obj_center(label);

    lv_label_set_text_fmt(label, "Temp: %d C", temp);
}

lv_label_set_text_fmt() 的用法和 printf() 很像。

常见格式如下:

格式 说明
%d 显示整数
%u 显示无符号整数
%s 显示字符串
%.1f 显示一位小数,是否可用取决于工程里的 sprintf 配置

如果是 LVGL 中常见的 int32_t 类型,也可以使用 LVGL 提供的格式宏。

例如:

int32_t value = 70;
lv_label_set_text_fmt(label, "%" LV_PRId32 "%%", value);

这里最后的:

"%%"

表示显示一个百分号 %

所以最终显示效果类似:

70%

比如做滑条百分比显示时,经常会写成这样:

lv_label_set_text_fmt(value_label, "%" LV_PRId32 "%%", lv_slider_get_value(slider));

需要注意的是:

更新数值时,不要反复创建新的 label。

更常见的写法是:

先创建一个 value_label

后面数值变化时,只调用 lv_label_set_text_fmt() 更新它的内容。


五、换行显示

如果想让文字换行,可以直接在字符串里加 \n

例如:

lv_label_set_text(label, "Hello\nLVGL\nLabel");

完整例子:

void init_page(void)
{
    lv_obj_t * label = lv_label_create(lv_screen_active());
    lv_label_set_text(label, "Line 1\nLine 2\nLine 3");
    lv_obj_center(label);
}

显示效果就是三行文字:

Line 1
Line 2
Line 3

这里要注意:

\n 是换行符,不是两个普通字符。

如果写成:

"Hello\\nLVGL"

那显示出来的可能就是 \n 本身,而不是换行效果。


六、设置文字颜色

很多新手想修改标签文字颜色时,会先写成这样:

lv_obj_set_style_bg_color(label, lv_color_hex(0xFF0000), LV_PART_MAIN);

这句代码改的是背景颜色。

不是文字颜色。

如果要修改文字颜色,应该使用:

lv_obj_set_style_text_color(label, lv_color_hex(0xFF0000), LV_PART_MAIN);

例如显示红色文字:

lv_obj_t * label = lv_label_create(lv_screen_active());
lv_label_set_text(label, "Error");
lv_obj_center(label);

lv_obj_set_style_text_color(label, lv_color_hex(0xFF0000), LV_PART_MAIN);

如果要设置字体,也同样属于 text 样式:

lv_obj_set_style_text_font(label, &lv_font_montserrat_32, LV_PART_MAIN);

所以可以简单记成:

背景颜色:bg_color
文字颜色:text_color
文字字体:text_font

不要把背景样式和文字样式混在一起。


七、文字对齐:为什么设置了没效果?

文字对齐使用这个函数:

lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN);

但这里有一个很容易踩坑的地方:

如果 label 没有固定宽度,文字对齐效果可能不明显。

原因也很简单。

如果 label 的宽度刚好等于文字本身的宽度,那么左对齐、居中、右对齐看起来几乎没有区别。

所以在设置文字对齐前,通常要先给 label 设置一个宽度。

例如:

lv_obj_t * label = lv_label_create(lv_screen_active());
lv_obj_set_width(label, 240);
lv_label_set_text(label, "Hello LVGL");

lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN);
lv_obj_center(label);

常见对齐方式如下:

对齐方式 说明
LV_TEXT_ALIGN_LEFT 左对齐
LV_TEXT_ALIGN_CENTER 居中对齐
LV_TEXT_ALIGN_RIGHT 右对齐

如果想做一个页面标题,一般可以这样写:

lv_obj_t * title = lv_label_create(lv_screen_active());
lv_obj_set_width(title, LV_PCT(100));
lv_label_set_text(title, "Device Panel");
lv_obj_set_style_text_align(title, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN);
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 20);

这段代码里,title 的宽度是父对象宽度的 100%。

所以文字居中对齐时,效果就会很明显。


八、长文本怎么办?

文字太长时,lv_label 提供了几种处理方式。

设置长文本模式使用:

lv_label_set_long_mode(label, LV_LABEL_LONG_WRAP);

常见模式如下:

模式 说明
LV_LABEL_LONG_WRAP 自动换行,常用
LV_LABEL_LONG_DOT 超出部分显示省略号
LV_LABEL_LONG_SCROLL 来回滚动
LV_LABEL_LONG_SCROLL_CIRCULAR 循环滚动
LV_LABEL_LONG_CLIP 直接裁剪

比如做一段固定宽度的说明文本:

lv_obj_t * label = lv_label_create(lv_screen_active());
lv_obj_set_width(label, 220);
lv_label_set_long_mode(label, LV_LABEL_LONG_WRAP);
lv_label_set_text(label, "This is a very long text, it will wrap automatically.");
lv_obj_center(label);

如果做状态栏里的设备名称,空间比较小,可以用省略号模式:

lv_obj_set_width(label, 120);
lv_label_set_long_mode(label, LV_LABEL_LONG_DOT);
lv_label_set_text(label, "A very very long device name");

如果做横向滚动公告,可以使用循环滚动:

lv_label_set_long_mode(label, LV_LABEL_LONG_SCROLL_CIRCULAR);

不同模式解决的问题不一样。

可以简单理解成:

说明文字:用自动换行。
标题太长:用省略号。
公告提示:用滚动。

九、中文为什么显示不出来?

这是 lv_label 里最常见的问题。

英文能显示,中文显示不了,通常不是 lv_label 的问题。

真正的原因大多是:

当前字体不包含对应中文字符。

LVGL 显示文字时,需要字体里包含对应字符。

比如你要显示:

lv_label_set_text(label, "初始化");

那么当前 label 使用的字体里,必须包含这几个字:

初 始 化

如果字体里没有这些字,就可能出现:

  • 显示为空白
  • 显示成方块
  • 部分文字缺失
  • 整段文字看起来不正常

所以中文显示问题,优先检查字体。

不要一上来就怀疑 lv_label


十、使用 LVGL 自带 CJK 字体

如果在 lv_conf.h 中开启了:

#define LV_FONT_SIMSUN_16_CJK 1
#define LV_FONT_DEFAULT &lv_font_simsun_16_cjk

就可以使用 LVGL 自带的 SimSun CJK 字体。

例如:

lv_obj_t * label = lv_label_create(lv_screen_active());
lv_obj_center(label);

lv_obj_set_style_text_font(label, &lv_font_simsun_16_cjk, LV_PART_MAIN);
lv_label_set_text(label, "Hello 坏柠");

不过要注意:

LVGL 自带 CJK 字体并不等于包含所有中文。

它更适合用来做简单测试。

如果界面里有大量中文,或者需要统一字体风格,还是建议生成自己的中文字体。


十一、使用自定义字体

在示例里,我们用一个生成好的中文字体作为例子:

qingniao_18.c

在代码中使用前,需要先声明:

LV_FONT_DECLARE(qingniao_18);

然后设置给 label:

LV_FONT_DECLARE(qingniao_18);

void init_page(void)
{
    lv_obj_t * label = lv_label_create(lv_screen_active());
    lv_obj_center(label);

    lv_obj_set_style_text_font(label, &qingniao_18, LV_PART_MAIN);
    lv_label_set_text(label, "设备控制面板");
}

也可以封装成一个小函数,后面创建标签会更方便。

例如:

LV_FONT_DECLARE(qingniao_18);

static lv_obj_t * create_label(lv_obj_t * parent, const char * text, uint32_t color)
{
    lv_obj_t * label = lv_label_create(parent);
    lv_label_set_text(label, text);
    lv_obj_set_style_text_font(label, &qingniao_18, LV_PART_MAIN);
    lv_obj_set_style_text_color(label, lv_color_hex(color), LV_PART_MAIN);
    return label;
}

实际写界面时,我更常用下面这种写法:

static lv_obj_t * create_label(lv_obj_t * parent,
                               const char * text,
                               const lv_font_t * font,
                               uint32_t color)
{
    lv_obj_t * label = lv_label_create(parent);
    lv_label_set_text(label, text);
    lv_obj_set_style_text_font(label, font, LV_PART_MAIN);
    lv_obj_set_style_text_color(label, lv_color_hex(color), LV_PART_MAIN);
    return label;
}

这样做的好处是:

后面创建文字时,不用每次重复写字体和颜色。

比如创建标题、数值、单位时,都可以复用这个函数。


十二、自己生成字体时要注意什么?

LVGL 的静态字体一般不是在运行时直接读取 .ttf.otf

在这种方式下,我们先用字体转换工具把字体文件转换成一个 .c 文件,然后把这个 .c 文件编进工程。

本项目里的自定义字体就是:

app/src/fonts/qingniao_18.c

这个文件不是手写的,而是由 lv_font_conv 生成的。

大致流程是:

  1. 准备一个 .ttf.otf 字体文件。
  2. 选择字号,比如 18、24、32。
  3. 填入需要用到的字符。
  4. 生成 .c 字体文件。
  5. CMake 把这个 .c 文件编进工程。
  6. LV_FONT_DECLARE(font_name) 声明字体。
  7. lv_obj_set_style_text_font() 设置到 label。

make font 是怎么来的?

make font 不是 LVGL 自带的命令。

它是我们在项目根目录的 Makefile 里自己定义的一个目标:

.PHONY: help prepare font clean

font:
	@sh scripts/generate_font.sh

所以当你在项目根目录执行:

make font

实际发生的是:

sh scripts/generate_font.sh

或者:

make -C .. font

字体生成脚本做了什么?

脚本位置是:

scripts/generate_font.sh

里面先定义了一组默认参数:

font_file="${FONT_FILE:-$project_root/QingNiaoHuaGuangJianMeiHei-2.ttf}"
symbols_file="${SYMBOLS_FILE:-$project_root/app/src/fonts/qingniao_18.symbols.txt}"
output_file="${OUTPUT_FILE:-$project_root/app/src/fonts/qingniao_18.c}"
font_name="${FONT_NAME:-qingniao_18}"
font_size="${FONT_SIZE:-18}"
font_bpp="${FONT_BPP:-4}"
font_range="${FONT_RANGE:-0x20-0x7F,0xB0}"

这些参数的意思是:

参数 含义
font_file 输入字体文件,也就是 .ttf
symbols_file 需要生成进字体里的中文字符表
output_file 输出的 LVGL 字体 .c 文件
font_name 生成出来的 C 变量名
font_size 字号
font_bpp 每个像素用多少 bit 表示灰度,值越大越细腻,文件也越大
font_range 额外加入的字符范围

这里的:

FONT_FILE
SYMBOLS_FILE
OUTPUT_FILE
FONT_NAME
FONT_SIZE
FONT_BPP
FONT_RANGE

都是环境变量。

也就是说,你可以临时覆盖默认参数,例如:

FONT_SIZE=24 FONT_NAME=qingniao_24 OUTPUT_FILE=app/src/fonts/qingniao_24.c make font

脚本接着会检查三个东西:

lv_font_conv 命令是否存在
字体文件是否存在
字符表文件是否存在

如果没有安装 lv_font_conv,脚本会提示:

sudo npm install -g lv_font_conv

然后脚本读取字符表:

symbols=$(sed '/^[[:space:]]*#/d' "$symbols_file" | tr -d '\r\n')

这句的作用是:

  1. 删除以 # 开头的整行注释。
  2. 删除换行符。
  3. 把多行中文字符合并成一个字符串。

所以这个字符表:

# comment
温度湿度当前
设备控制面板

最后会变成:

温度湿度当前设备控制面板

真正的字体转换命令

脚本最后执行的是 lv_font_conv

lv_font_conv \
    --font "$font_file" \
    --size "$font_size" \
    --bpp "$font_bpp" \
    --format lvgl \
    --lv-font-name "$font_name" \
    --symbols "$symbols" \
    --range "$font_range" \
    -o "$output_file"

使用的命令其实就是:

 lv_font_conv \
      --font QingNiaoHuaGuangJianMeiHei-2.ttf \
      --size 18 \
      --bpp 4 \
      --format lvgl \
      --lv-font-name qingniao_18 \
      --symbols "$(sed '/^[[:space:]]*#/d' app/src/fonts/
  qingniao_18.symbols.txt | tr -d '\r\n')" \
      --range 0x20-0x7F,0xB0 \
      -o app/src/fonts/qingniao_18.c

其中 --symbols “$(sed …)” 这段是把 app/src/fonts/
qingniao_18.symbols.txt 里的中文字符读出来,去掉注释和换行后传给
lv_font_conv。

按当前项目默认值展开,大概就是:

lv_font_conv \
    --font QingNiaoHuaGuangJianMeiHei-2.ttf \
    --size 18 \
    --bpp 4 \
    --format lvgl \
    --lv-font-name qingniao_18 \
    --symbols "温度湿度当前CPU使用率报警设置网络设备传感器趋势状态总开关屏幕亮风扇转速系统音量控制面板快捷功能灯光首页数据关于最近小时()" \
    --range 0x20-0x7F,0xB0 \
    -o app/src/fonts/qingniao_18.c

这里几个关键参数要理解:

参数 作用
--font 从哪个 .ttf 里取字形
--size 按多大的字号生成
--bpp 字形 bitmap 的灰度精度
--format lvgl 输出 LVGL 能识别的 C 代码
--lv-font-name 输出文件里的字体变量名
--symbols 指定要加入的中文字符
--range 指定额外字符范围,这里包含 ASCII 和 °
-o 输出 .c 文件路径

生成出来的 .c 里面是什么?

lv_font_conv 会从 .ttf 里取出指定字符的字形,然后按指定字号转换成 LVGL 的静态字体数据。

生成的 qingniao_18.c 里主要有这些内容:

glyph_bitmap
glyph_dsc
cmaps
font_dsc
const lv_font_t qingniao_18

可以这样理解:

内容 作用
glyph_bitmap 每个字真正的点阵图数据
glyph_dsc 每个字的宽、高、偏移等描述信息
cmaps Unicode 字符到 glyph 的映射表
font_dsc LVGL 字体内部描述对象
qingniao_18 最终给代码使用的 lv_font_t 字体对象

所以我们在代码里写:

LV_FONT_DECLARE(qingniao_18);

本质上是在声明:

extern const lv_font_t qingniao_18;

然后再把它设置给 label:

lv_obj_set_style_text_font(label, &qingniao_18, LV_PART_MAIN);

这样 LVGL 绘制文字时,就能通过 qingniao_18 找到每个字符的 bitmap 和尺寸信息。

CMake 为什么能编译这个字体文件?

本项目不是在 CMake 里写了一个 font 目标。

当前字体生成靠的是顶层 Makefile

Makefile -> scripts/generate_font.sh -> lv_font_conv -> qingniao_18.c

CMake 只负责把生成出来的 .c 文件编进 app。

app/CMakeLists.txt 里有:

file(GLOB_RECURSE APP_SOURCES CONFIGURE_DEPENDS
    "${CMAKE_CURRENT_SOURCE_DIR}/src/*.c"
)

add_library(lvgl_app ${APP_SOURCES})

这表示:

app/src/ 下面所有 .c 文件都会被收集进 lvgl_app。

所以 app/src/fonts/qingniao_18.c 会自动进入编译。

完整链路是:

make font
  -> 执行 scripts/generate_font.sh
  -> 调用 lv_font_conv
  -> 生成 app/src/fonts/qingniao_18.c
  -> CMake 构建 app 时收集 app/src/**/*.c
  -> qingniao_18.c 被编译进 lvgl_app
  -> 代码里 LV_FONT_DECLARE(qingniao_18)
  -> label 设置 &qingniao_18 后可以显示中文

最容易出错的地方

这里最容易出错的是第 3 步:

你要显示的中文,必须在生成字体时加入字符列表。

比如你生成字体时只加入了:

初始化

那后面如果显示:

设备控制面板

就可能显示不全。

如果工程里维护了一个字体字符表,例如:

app/src/fonts/qingniao_18.symbols.txt

那么后续新增中文文案时,应该把新文字加进去,然后重新生成字体:

make font

这一步很容易忘。

所以中文界面开发时,建议把所有中文文案集中维护。

否则后期新增一个按钮文字,可能就会遇到“文字又变方块”的问题。

还要注意这些点:

  1. --symbols 里没有的中文不会被生成进 .c
  2. --range 0x20-0x7F,0xB0 只额外包含英文、数字、常用标点和 °,不包含中文。
  3. FONT_NAME 必须和代码里的 LV_FONT_DECLARE() 名字一致。
  4. 不建议手动修改生成出来的 qingniao_18.c,应该改 .symbols.txt 或脚本参数后重新生成。
  5. 字符表和源码文件都建议保持 UTF-8 编码。
  6. 字号越大、字符越多、bpp 越高,生成的 .c 文件越大,占用的 Flash 也越多。

十三、FreeType 动态字体是什么?

这里再补充一个进阶方案:

FreeType 动态字体。

简单理解:

前面说的字体生成方式,是提前把需要的文字生成到 .c 字体文件里。

FreeType 则是另一种方式:

运行时直接加载 .ttf 或 .otf 字体文件。

它的优点是:

  • 不需要提前把所有汉字都生成进 C 文件。
  • 可以按不同字号动态加载字体。
  • 更适合 Linux 这类资源相对充足的平台。

它的缺点也很明显:

  • 工程配置更复杂。
  • 需要文件系统路径正确。
  • 内存占用更高。
  • 对小资源嵌入式平台不一定友好。

FreeType 的配置比静态字体复杂一些,所以这篇先不展开工程细节。

新手阶段建议先掌握三件事:

LVGL 自带字体。
自定义生成字体。
给 label 设置字体。

等这些跑通后,再去了解 FreeType 会更顺。


十四、旧教程里的 lv_label_set_recolor() 怎么办?

有些旧教程会这样写:

lv_label_set_recolor(label, true);
lv_label_set_text(label, "#0000ff Hello# #ff00ff LVGL#");

这个写法用于在一段 label 文本中插入颜色标记。

但是在 LVGL 9.2 中,这个 API 已经不是我建议新手优先使用的方式。

所以如果直接照抄旧教程,可能会遇到编译问题。

新手阶段建议先用更稳定、更直观的方式。

如果整段文字使用同一种颜色:

lv_obj_set_style_text_color(label, lv_color_hex(0x0000FF), LV_PART_MAIN);

如果一行里不同文字要使用不同颜色,可以先拆成多个 label。

例如:

lv_obj_t * row = lv_obj_create(lv_screen_active());
lv_obj_set_layout(row, LV_LAYOUT_FLEX);
lv_obj_set_flex_flow(row, LV_FLEX_FLOW_ROW);

lv_obj_set_style_bg_opa(row, LV_OPA_TRANSP, LV_PART_MAIN);
lv_obj_set_style_border_width(row, 0, LV_PART_MAIN);
lv_obj_center(row);

lv_obj_t * label1 = lv_label_create(row);
lv_label_set_text(label1, "Hello ");
lv_obj_set_style_text_color(label1, lv_color_hex(0x0000FF), LV_PART_MAIN);

lv_obj_t * label2 = lv_label_create(row);
lv_label_set_text(label2, "LVGL");
lv_obj_set_style_text_color(label2, lv_color_hex(0xFF00FF), LV_PART_MAIN);

这种写法虽然看起来“笨一点”,但有几个好处:

  • 结构清楚
  • 样式独立
  • 不依赖旧 API
  • 更符合前面讲过的对象组合思路

很多时候,LVGL 里复杂一点的显示效果,本来就是通过多个基础对象组合出来的。


十五、完整示例:显示一个中文状态卡片

下面做一个稍微完整一点的例子。

目标是:

  • 创建一个深色卡片。
  • 在卡片中显示中文标题。
  • 显示一个动态百分比。
  • 使用自定义中文字体。
  • 通过 Flex 布局让文字垂直排列。

代码如下:

#include <lvgl.h>

LV_FONT_DECLARE(qingniao_18);

static lv_obj_t * create_label(lv_obj_t * parent, const char * text, uint32_t color)
{
    lv_obj_t * label = lv_label_create(parent);
    lv_label_set_text(label, text);
    lv_obj_set_style_text_font(label, &qingniao_18, LV_PART_MAIN);
    lv_obj_set_style_text_color(label, lv_color_hex(color), LV_PART_MAIN);
    return label;
}

void init_page(void)
{
    lv_obj_t * card = lv_obj_create(lv_screen_active());
    lv_obj_set_size(card, 220, 120);
    lv_obj_center(card);

    lv_obj_set_style_bg_color(card, lv_color_hex(0x10243A), LV_PART_MAIN);
    lv_obj_set_style_bg_opa(card, LV_OPA_COVER, LV_PART_MAIN);
    lv_obj_set_style_border_width(card, 0, LV_PART_MAIN);
    lv_obj_set_style_pad_all(card, 12, LV_PART_MAIN);
    lv_obj_set_style_radius(card, 8, LV_PART_MAIN);
    lv_obj_remove_flag(card, LV_OBJ_FLAG_SCROLLABLE);

    lv_obj_set_layout(card, LV_LAYOUT_FLEX);
    lv_obj_set_flex_flow(card, LV_FLEX_FLOW_COLUMN);
    lv_obj_set_flex_align(card,
                          LV_FLEX_ALIGN_CENTER,
                          LV_FLEX_ALIGN_CENTER,
                          LV_FLEX_ALIGN_CENTER);

    create_label(card, "屏幕亮度", 0xF4F7FB);

    lv_obj_t * value = create_label(card, "", 0x62E94F);
    lv_label_set_text_fmt(value, "%d%%", 70);
}

这个例子里用到了:

  • lv_label_create() 创建标签。
  • lv_label_set_text() 设置固定文字。
  • lv_label_set_text_fmt() 设置变量文字。
  • lv_obj_set_style_text_font() 设置字体。
  • lv_obj_set_style_text_color() 设置文字颜色。
  • Flex 布局让两个 label 垂直居中排列。

这已经很接近真实界面开发里的写法。

比如设备控制面板里的:

  • 温度数值
  • 湿度数值
  • CPU 使用率
  • 屏幕亮度
  • 风扇转速
  • 状态文字

这些内容,本质上都可以用类似的方式显示出来。


十六、常见问题

1. 英文能显示,中文不显示

优先检查字体。

当前 label 使用的字体必须包含对应中文字符。


2. 中文显示乱码

优先检查源码编码是否为 UTF-8。

然后检查字体字符表里是否包含这些字。


3. 设置文字居中没效果

先检查 label 有没有设置宽度。

lv_obj_set_width(label, 200);
lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN);

4. 想显示百分号

格式化字符串里要写两个 %

lv_label_set_text_fmt(label, "%d%%", 70);

5. 字体生成后还是显示不全

检查新文字有没有加入字符表。

例如:

app/src/fonts/qingniao_18.symbols.txt

然后重新执行字体生成命令:

make font

十七、这一篇先记住什么?

这一篇我们把 lv_label 的基础用法讲了一遍。

先记住下面几句话:

  1. lv_label 用来显示文字,本质上也是 lv_obj
  2. 创建标签用 lv_label_create()
  3. 设置普通文本用 lv_label_set_text()
  4. 显示变量用 lv_label_set_text_fmt()
  5. 换行直接使用 \n
  6. 改文字颜色用 lv_obj_set_style_text_color(),不是 bg_color
  7. 设置文字居中前,先确认 label 是否有明确宽度。
  8. 显示中文的关键是字体,不是 label。
  9. 自定义字体要用 LV_FONT_DECLARE() 声明,再设置到 label。

后面做按钮、开关、滑条时,里面的文字也经常是 label。

所以 lv_label 是一个非常基础、也非常高频的控件。

下一篇继续讲 lv_button

也就是 LVGL 里的按钮控件。

学按钮时,我们会继续看到:

lv_obj、lv_label、事件回调

这三块内容是怎么组合起来的。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐