3、LVGL坐标系统与对齐:对象的坐标(x, y)、大小(width, height)、对齐方式(lv_align_t)、父子对象坐标关系

各位同学,欢迎来到第三章。今天咱们聊聊LVGL里最基础、但也最容易踩坑的东西——坐标系统。

说实话,我刚开始用LVGL做仪表盘时,觉得坐标不就是x、y嘛,有啥好讲的?结果第一次做复杂布局,控件全飞了。嗯,后来老老实实把坐标系统啃了一遍,才明白这里面门道不少。

3.1 坐标原点与方向

LVGL的坐标系统,说白了就是屏幕左上角是原点(0,0)。x轴向右为正,y轴向下为正。这和很多图形库一样,但和数学课上学的坐标系是反的——y轴是朝下的。

为什么会这样?因为屏幕的扫描线是从上往下走的,这样设计更符合硬件逻辑。

核心要点:

  • 原点(0,0)在父对象的左上角
  • x向右增加,y向下增加
  • 所有坐标都是相对于父对象的

3.2 对象的位置:x和y

每个LVGL对象都有两个位置属性:xy。它们表示对象左上角相对于父对象左上角的偏移量。

设置位置有两种方式:

/* 方式一:分别设置 */
lv_obj_set_x(obj, 50);
lv_obj_set_y(obj, 100);

/* 方式二:同时设置 */
lv_obj_set_pos(obj, 50, 100);

我在项目中遇到过一个问题:用lv_obj_set_pos设置位置后,再单独调用lv_obj_set_x,发现位置变了。其实这很正常,因为后者会覆盖前者的x值。你想想看,如果你先设了(50,100),然后又设x为80,那最终位置就是(80,100)。

我的习惯:如果同时设置x和y,我建议用lv_obj_set_pos,代码更简洁。如果只改其中一个,再用单独的setter。

3.3 对象的大小:width和height

大小属性同样重要。每个对象都有宽度和高度,单位是像素。

/* 设置大小 */
lv_obj_set_width(obj, 200);
lv_obj_set_height(obj, 100);
lv_obj_set_size(obj, 200, 100);  /* 同时设置宽高 */

这里有个坑:如果你设置的大小超过了父对象的范围,子对象可能会被裁剪。我曾经做一个进度条,宽度设得比父容器还大,结果右边部分死活显示不出来。查了半天才发现是父对象的lv_obj_set_style_clip_corner默认开启了裁剪。

注意:LVGL中大小默认是像素单位。但如果你设置了lv_obj_set_style_pad_all之类的内边距,实际可用空间会变小。计算布局时一定要把内边距算进去。

3.4 对齐方式:lv_align_t

手动算坐标太累了,对吧?LVGL提供了对齐机制,让你轻松把对象放到指定位置。

对齐方式定义在lv_align_t枚举中,常用的有:

对齐常量 含义 实际效果
LV_ALIGN_TOP_LEFT 左上角对齐 对象左上角在父对象左上角
LV_ALIGN_CENTER 中心对齐 对象中心在父对象中心
LV_ALIGN_BOTTOM_RIGHT 右下角对齐 对象右下角在父对象右下角
LV_ALIGN_LEFT_MID 左边缘垂直居中 对象左边缘在父对象左边缘,垂直居中

使用对齐的代码很简单:

/* 将对象居中于父对象 */
lv_obj_align(obj, LV_ALIGN_CENTER, 0, 0);

/* 将对象放在父对象右下角,并偏移(-10, -10) */
lv_obj_align(obj, LV_ALIGN_BOTTOM_RIGHT, -10, -10);

最后两个参数是x和y的偏移量。正数表示向右/向下偏移,负数表示向左/向上偏移。

我个人习惯用LV_ALIGN_CENTER配合偏移来做精细调整。比如做一个仪表盘上的指针,先居中,再偏移到具体位置,这样代码可读性高,后期改起来也方便。

3.5 父子对象坐标关系

这是本章的重点,也是很多新手搞混的地方。

在LVGL中,子对象的坐标是相对于父对象的。也就是说,如果你有一个面板(父对象)在屏幕的(100, 100)位置,面板里放一个按钮(子对象)在(10, 10),那按钮在屏幕上的实际位置是(110, 110)。

为什么会这样?因为LVGL采用相对坐标系统。每个对象只知道自己在父对象里的位置,不知道自己在屏幕上的绝对位置。

如果你想知道一个对象在屏幕上的绝对坐标,可以用这个函数:

lv_point_t point;
lv_obj_get_coords(obj, &point);
/* point.x 和 point.y 就是对象左上角在屏幕上的绝对坐标 */

我曾经踩过一个坑:做一个多级菜单,子菜单的位置是相对于父菜单的。结果父菜单移动了,子菜单没跟着动,因为我没有重新计算子菜单的位置。后来我改用lv_obj_align并设置对齐到父对象,问题就解决了。

关键理解:

  • 子对象的(0,0)就是父对象的左上角
  • 父对象移动,子对象跟着移动(相对坐标不变)
  • 父对象大小变化,子对象可能需要重新对齐

3.6 实战:做一个简单的仪表盘布局

光说不练假把式。咱们来写一个简单的仪表盘布局,把今天学的都用上。

/* 创建主面板 */
lv_obj_t *panel = lv_obj_create(lv_scr_act());
lv_obj_set_size(panel, 400, 300);
lv_obj_align(panel, LV_ALIGN_CENTER, 0, 0);

/* 创建标题标签,放在面板顶部居中 */
lv_obj_t *title = lv_label_create(panel);
lv_label_set_text(title, "仪表盘");
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 10);

/* 创建数值显示,放在面板中心 */
lv_obj_t *value = lv_label_create(panel);
lv_label_set_text(value, "75%");
lv_obj_set_style_text_font(value, &lv_font_montserrat_48, 0);
lv_obj_align(value, LV_ALIGN_CENTER, 0, 0);

/* 创建单位标签,放在数值下方 */
lv_obj_t *unit = lv_label_create(panel);
lv_label_set_text(unit, "进度");
lv_obj_align(unit, LV_ALIGN_CENTER, 0, 40);

/* 创建按钮,放在面板右下角 */
lv_obj_t *btn = lv_btn_create(panel);
lv_obj_set_size(btn, 80, 30);
lv_obj_align(btn, LV_ALIGN_BOTTOM_RIGHT, -10, -10);

你看,这个布局里所有子对象都是相对于面板的。如果我把面板移动到屏幕左上角,所有子对象都会跟着移动,不需要重新计算坐标。这就是父子坐标关系的好处。

我的建议:做复杂仪表盘时,先用面板把界面分区,然后在每个面板里用对齐方式摆放子对象。这样代码结构清晰,后期维护也方便。我曾经接手过一个项目,所有控件都用绝对坐标,改一个位置要改十几个地方,那叫一个酸爽。

3.7 避坑指南

最后,分享几个我踩过的坑:

  • 对齐后不要手动改x/y:如果你用了lv_obj_align,之后又手动调用lv_obj_set_x,对齐效果会被覆盖。我建议要么全用对齐,要么全用手动坐标,混着用容易出问题。
  • 父对象大小变化后重新对齐:如果父对象的大小在运行时改变了,子对象的位置可能不对。可以用lv_obj_align重新对齐,或者监听父对象的LV_EVENT_SIZE_CHANGED事件。
  • 注意内边距:父对象的pad属性会影响子对象的对齐位置。比如父对象设置了pad_top = 20,那子对象对齐到顶部时,实际位置会下移20像素。

好了,这一章就到这里。坐标系统是LVGL的基石,搞懂了它,后面的布局和动画才能玩得转。下一章咱们聊聊样式系统,到时候你会看到坐标和样式是怎么配合的。

记住:多动手写代码,光看是学不会的。去试试吧!