3、LVGL坐标系统与对齐:坐标系统基础、绝对定位与相对定位、对齐方式(lv_align_t)、自动布局与Flex布局入门、Margin与Padding

各位同学,今天我们来聊聊LVGL里最基础也最容易踩坑的东西——坐标系统。说实话,我刚开始用LVGL的时候,也被这玩意儿绕晕过。明明看着对齐了,一跑起来就歪了。后来我才明白,不是LVGL有问题,是我没搞懂它的坐标哲学。

3.1 坐标系统基础

LVGL的坐标系统,说白了就是屏幕上的一个网格。左上角是原点(0,0),X轴往右为正,Y轴往下为正。嗯,跟你在学校学的数学坐标系不一样,Y轴是反的。这个要记住,我见过不少新手在这里翻车。

每个控件都有自己的坐标,这个坐标是相对于它的父对象的。举个例子:

// 创建一个按钮,位置在父对象的(50, 30)处
lv_obj_t *btn = lv_btn_create(lv_scr_act());
lv_obj_set_pos(btn, 50, 30);

这里btn的坐标(50,30),是相对于当前活动屏幕的。如果父对象变了,坐标原点也跟着变。我在项目中遇到过有人把子对象坐标设成绝对屏幕坐标,结果父对象一移动,子对象就飞了。

3.2 绝对定位与相对定位

绝对定位,就是直接告诉LVGL:你给我放在(x, y)这个位置。用lv_obj_set_pos()或者lv_obj_set_x()/lv_obj_set_y()就能搞定。

相对定位呢?说白了就是「相对于某个参考点来定位」。LVGL里最常用的相对定位方式,就是通过对齐来实现的。你想想看,如果你要做一个「确定」按钮放在弹窗的右下角,用绝对定位你得算半天,用相对定位一句话就搞定。

核心区别:绝对定位是「我就要在这里」,相对定位是「我在它旁边」。

3.3 对齐方式(lv_align_t)

对齐是LVGL里最实用的功能之一。lv_align_t枚举定义了对齐的参考点。常用的有:

对齐常量 含义 我常用的场景
LV_ALIGN_TOP_LEFT 左上角 标签默认对齐
LV_ALIGN_CENTER 正中心 弹窗、加载动画
LV_ALIGN_BOTTOM_MID 底部中间 底部导航栏按钮
LV_ALIGN_OUT_BOTTOM_LEFT 外部左下角 下拉菜单触发位置

使用方式很简单:

// 让按钮在父对象中居中
lv_obj_t *btn = lv_btn_create(lv_scr_act());
lv_obj_align(btn, LV_ALIGN_CENTER, 0, 0);

// 让标签在按钮的正上方,偏移10像素
lv_obj_align(label, LV_ALIGN_OUT_TOP_MID, 0, -10);

注意最后一个参数是x偏移和y偏移。我曾经犯过一个错误:以为偏移是相对于控件自身的,其实是相对于对齐参考点的。比如LV_ALIGN_OUT_TOP_MID,参考点是父对象的顶部中间,偏移是让控件再往左/往右、往上/往下挪一点。

小技巧:如果你想让控件相对于另一个控件对齐,用lv_obj_align_to(obj, ref_obj, align, x_ofs, y_ofs)。这个我经常用来做「跟随」效果,比如提示气泡跟在按钮旁边。

3.4 自动布局与Flex布局入门

手动对齐多了,你会发现一个问题:当控件数量变化时,位置全乱了。这时候就需要自动布局了。

LVGL的Flex布局,说白了就是「让LVGL帮你自动排位置」。你只需要告诉它「排成一行还是列」、「间距多少」,剩下的它自己搞定。

// 创建一个容器,使用Flex布局
lv_obj_t *cont = lv_obj_create(lv_scr_act());
lv_obj_set_size(cont, 300, 200);
lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_ROW_WRAP);
lv_obj_set_flex_align(cont, 
    LV_FLEX_ALIGN_SPACE_EVENLY,  // 主轴对齐
    LV_FLEX_ALIGN_CENTER,        // 交叉轴对齐
    LV_FLEX_ALIGN_CENTER);       // 交叉轴对齐(多行时)

// 往容器里加按钮,它们会自动排列
for(int i = 0; i < 5; i++) {
    lv_obj_t *btn = lv_btn_create(cont);
    lv_obj_set_size(btn, 60, 40);
}

这里LV_FLEX_FLOW_ROW_WRAP表示「水平排列,放不下就换行」。我个人习惯用LV_FLEX_FLOW_COLUMN做垂直菜单,用LV_FLEX_FLOW_ROW_WRAP做标签页。

注意:Flex布局只对直接子对象生效。孙对象不会自动排列。我曾经把按钮放在一个容器里,容器又放在Flex容器里,结果按钮全堆在一起了。后来才反应过来——Flex只管它的直接孩子。

3.5 Margin与Padding

这两个概念,搞过CSS的同学应该很熟悉。Margin是外边距,Padding是内边距。说白了:

  • Margin:控件与外部元素之间的距离
  • Padding:控件内容与控件边框之间的距离

在LVGL里,设置方式如下:

// 设置容器的内边距(Padding)
lv_obj_set_style_pad_all(cont, 10, 0);
lv_obj_set_style_pad_left(cont, 20, 0);  // 单独设置左边距

// 设置控件的外边距(Margin)——注意,Margin是通过样式设置的
lv_obj_set_style_margin_all(btn, 5, 0);

嗯,这里有个坑:Margin在LVGL里不是所有控件都支持的。它主要对Flex布局和Grid布局里的子对象生效。如果你用绝对定位,设置Margin是没用的。我刚开始用的时候,给一个绝对定位的按钮设了Margin,发现没反应,查了半天文档才明白。

另外,Padding对容器类控件(比如lv_objlv_cont)效果明显。如果你在容器里放了一堆按钮,设置Padding可以让按钮不贴着容器边缘。

我的经验:调试布局时,先把Padding和Margin设成明显的值(比如20),这样一眼就能看出效果。调好了再改回实际值。这招帮我省了不少时间。

避坑指南

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

  • 坐标单位:LVGL里坐标单位是像素,不是dp。不同屏幕分辨率下,同一个坐标值显示效果不同。我建议用lv_pct()做百分比布局,适配性更好。
  • 对齐与布局冲突:如果你对一个控件用了lv_obj_align(),然后又把它放到Flex容器里,Flex布局会覆盖对齐设置。两者不要混用。
  • 父对象大小变化:子对象的对齐是「一次性」的。父对象大小变了,子对象不会自动重新对齐。除非你监听父对象的大小变化事件,或者用Flex布局。

好了,坐标系统这块就讲到这里。说白了,LVGL的坐标就是「相对父对象的网格」,对齐就是「找个参考点」,Flex就是「自动排班」。搞懂这三层,大部分布局问题都能解决。下一节我们讲样式系统,那个更精彩。