3、LVGL坐标系统与对齐:坐标系统详解、对齐方式、自动布局、Margin与Padding
各位同学,欢迎来到第三章。这一章我们聊聊LVGL里最基础、也最容易踩坑的东西——坐标系统和对齐。
说实话,我刚开始用LVGL做项目时,觉得坐标系统不就是x、y嘛,有啥好学的?结果第一次做复杂界面,控件全乱飞,对齐对不上,padding一改整个布局崩掉……嗯,后来我老老实实把坐标系统啃了一遍,才明白这里面门道不少。
3.1 坐标系统详解
LVGL的坐标系统,说白了就是每个控件都有一个自己的“小世界”。
核心概念:父对象坐标系
每个控件的坐标,都是相对于它的父对象(parent)来计算的。父对象的左上角是(0,0),向右是x正方向,向下是y正方向。
举个例子:
// 创建一个屏幕对象(父对象)
lv_obj_t *scr = lv_scr_act();
// 在屏幕上创建一个按钮
lv_obj_t *btn = lv_btn_create(scr);
lv_obj_set_pos(btn, 50, 30); // 按钮左上角距离屏幕左上角 (50, 30)
这里btn的(50,30)是相对于scr的。如果scr本身有偏移,btn的位置会跟着变。
重要:LVGL里所有坐标都是相对于父对象的。这一点和很多桌面GUI框架不一样,你想想看,如果你嵌套了三四层容器,坐标计算就容易乱。我建议你在设计复杂界面时,先在纸上画一下层级关系。
绝对坐标与相对坐标
有时候你需要知道控件在整个屏幕上的位置,那就用绝对坐标:
lv_point_t point;
lv_obj_get_pos(btn, &point); // 相对父对象坐标
lv_obj_get_coords(btn, &rect); // 获取控件在屏幕上的绝对区域
我个人习惯在调试时用lv_obj_get_coords,因为它能直接告诉我控件到底显示在屏幕哪个位置,避免被嵌套层级搞晕。
3.2 对齐方式
对齐是LVGL里最实用的功能之一。你不需要手动计算坐标,只需要告诉系统“我要把按钮放在屏幕中间”就行了。
常用对齐常量
| 常量 | 含义 |
|---|---|
| LV_ALIGN_TOP_LEFT | 左上角 |
| LV_ALIGN_TOP_MID | 顶部居中 |
| LV_ALIGN_CENTER | 中心 |
| LV_ALIGN_BOTTOM_RIGHT | 右下角 |
| LV_ALIGN_OUT_LEFT_TOP | 父对象左侧外部,顶部对齐 |
对齐函数
// 将按钮对齐到屏幕中心
lv_obj_align(btn, LV_ALIGN_CENTER, 0, 0);
// 将按钮对齐到屏幕右上角,并偏移 (-10, 10)
lv_obj_align(btn, LV_ALIGN_TOP_RIGHT, -10, 10);
这里第三个和第四个参数是偏移量。正数向右/向下,负数向左/向上。
小技巧:我在做仪表盘界面时,经常用LV_ALIGN_OUT_RIGHT_MID把标签放在控件右侧外部,这样不用手动计算间距,代码也干净。
对齐到其他控件
有时候你需要让一个控件对齐到另一个控件,而不是父对象:
lv_obj_align_to(btn2, btn1, LV_ALIGN_OUT_RIGHT_MID, 10, 0);
// btn2 对齐到 btn1 的右侧外部,垂直居中,间距10像素
这个功能在制作表单时特别有用。我曾经做过一个设置页面,每个标签都对齐到前一个输入框的右侧,代码量减少了一半。
3.3 自动布局
手动对齐虽然灵活,但控件一多就麻烦了。LVGL提供了几种自动布局机制,说白了就是让系统帮你排位置。
Flex布局(弹性布局)
Flex布局适合一行或一列排列:
lv_obj_t *cont = lv_obj_create(scr);
lv_obj_set_size(cont, 300, 200);
lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_ROW); // 水平排列
lv_obj_set_flex_align(cont,
LV_FLEX_ALIGN_SPACE_BETWEEN, // 主轴:两端对齐
LV_FLEX_ALIGN_CENTER, // 交叉轴:居中
LV_FLEX_ALIGN_CENTER); // 换行对齐
// 添加子控件,它们会自动排列
lv_obj_t *btn1 = lv_btn_create(cont);
lv_obj_t *btn2 = lv_btn_create(cont);
lv_obj_t *btn3 = lv_btn_create(cont);
我刚开始用Flex时犯过一个错:忘了设置父容器的尺寸。结果子控件全挤在一起。记住,Flex布局的父容器必须有明确的尺寸,否则子控件不知道往哪排。
Grid布局(网格布局)
Grid布局适合表格状排列:
lv_obj_t *cont = lv_obj_create(scr);
lv_obj_set_size(cont, 400, 300);
// 定义列和行
static lv_coord_t col_dsc[] = {100, 100, 100, LV_GRID_TEMPLATE_LAST};
static lv_coord_t row_dsc[] = {50, 50, LV_GRID_TEMPLATE_LAST};
lv_obj_set_grid_dsc_array(cont, col_dsc, row_dsc);
// 将子控件放入网格
lv_obj_set_grid_cell(btn1, LV_GRID_ALIGN_CENTER, 0, 1,
LV_GRID_ALIGN_CENTER, 0, 1);
// 放在第0列,跨1列;第0行,跨1行
注意:Grid布局的列宽和行高定义数组必须以LV_GRID_TEMPLATE_LAST结尾。我曾经漏掉这个,结果布局完全乱套,排查了半天才发现。
3.4 Margin与Padding
这两个概念容易混淆,我简单解释一下:
- Margin(外边距):控件与外部元素之间的距离
- Padding(内边距):控件内容与控件边框之间的距离
Margin的使用
Margin主要影响控件在父容器中的位置:
lv_obj_set_style_margin_left(btn, 10, 0);
lv_obj_set_style_margin_right(btn, 10, 0);
lv_obj_set_style_margin_top(btn, 5, 0);
lv_obj_set_style_margin_bottom(btn, 5, 0);
在Flex或Grid布局中,Margin会被自动计算进去。比如两个按钮都有10px的margin,它们之间的间距就是20px。
Padding的使用
Padding影响子控件在父容器内的位置:
lv_obj_set_style_pad_left(cont, 15, 0);
lv_obj_set_style_pad_right(cont, 15, 0);
lv_obj_set_style_pad_top(cont, 10, 0);
lv_obj_set_style_pad_bottom(cont, 10, 0);
设置了padding后,子控件会自动缩进,不会紧贴父容器边缘。
避坑指南:我曾经在做一个列表界面时,给父容器设置了padding,但子控件还是跑到了边缘。后来发现是子控件自己设置了绝对定位(lv_obj_set_pos),覆盖了父容器的padding效果。记住,绝对定位会忽略padding和布局约束。
Padding的简写方式
LVGL提供了几个方便的简写:
// 统一设置所有方向
lv_obj_set_style_pad_all(cont, 10, 0);
// 水平方向
lv_obj_set_style_pad_hor(cont, 10, 0);
// 垂直方向
lv_obj_set_style_pad_ver(cont, 10, 0);
// 设置内部间隙(子控件之间的间距)
lv_obj_set_style_pad_gap(cont, 5, 0);
pad_gap这个属性在Flex和Grid布局中特别有用。你想想看,如果不用gap,你得手动给每个子控件设置margin,代码又臭又长。用gap一行搞定。
3.5 综合示例
最后,我给大家一个实际项目中用到的布局代码,把今天讲的知识点串起来:
// 创建主容器
lv_obj_t *main_cont = lv_obj_create(scr);
lv_obj_set_size(main_cont, 320, 240);
lv_obj_align(main_cont, LV_ALIGN_CENTER, 0, 0);
// 设置内边距
lv_obj_set_style_pad_all(main_cont, 10, 0);
// 使用Flex布局,垂直排列
lv_obj_set_flex_flow(main_cont, LV_FLEX_FLOW_COLUMN);
lv_obj_set_flex_align(main_cont,
LV_FLEX_ALIGN_SPACE_EVENLY,
LV_FLEX_ALIGN_CENTER,
LV_FLEX_ALIGN_CENTER);
// 创建三个按钮,自动排列
for(int i = 0; i < 3; i++) {
lv_obj_t *btn = lv_btn_create(main_cont);
lv_obj_set_size(btn, 200, 40);
lv_obj_set_style_margin_bottom(btn, 5, 0);
lv_obj_t *label = lv_label_create(btn);
lv_label_set_text_fmt(label, "按钮 %d", i + 1);
lv_obj_center(label);
}
这段代码创建了一个居中容器,内部垂直排列三个按钮,间距均匀。你运行一下看看,是不是比手动计算坐标省心多了?
好了,这一章的内容就到这里。坐标系统是LVGL的基石,对齐和布局是提高效率的法宝。下一章我们聊聊样式系统,到时候你会发现自己能做出更漂亮的界面。
公众号:蓝海资料掘金营,微信deep3321