LVGL库入门 02 - 布局

1、简单布局

可以使用 lv_obj_set_pos(obj, x, y) 调整一个控件的位置(或者使用类似的函数单独调整一个方向的坐标),将它放在相对父容器左上角的合适位置。不过这种布局方式非常死板,因为绝对坐标一旦设定就不能自动调整;而且当控件数量较多时,也很难确定合适的坐标值。

上一节介绍过,可以使用 lv_obj_align(obj, align, x_ofs, y_ofs) 设置一个控件相对父容器的对齐,并用以下图片展示所有的对齐方式:

image

从图片中可以看到,控件之间不仅可以内对齐,也可以外对齐。如果两个控件间没有包含关系也不要紧,可以使用 lv_obj_align_to(obj, base, align, x_ofs, y_ofs); 设置两个控件的相对对齐方式。

这种对齐的方式对于控件不多的情况下来说是足够了,但是有些时候需要对很多并列的控件布局(例如,一个计算机界面的所有按钮)。这个时候常规的对齐方式就难以满足需求了。

因此,LVGL 提供了两种更复杂的布局方式:

  • flex(弹性盒子)
  • grid(网格)

这两种布局和 CSS3 新增的 flex 布局和 grid 布局比较相似,如果熟悉 CSS 的话对它们应该不会陌生。

2、flex布局

flex 是一个实验性质的布局,首先需要确定已经在 lv_conf.h 大约 588 行的位置启用了 flex 布局:

/*A layout similar to Flexbox in CSS.*/
#define LV_USE_FLEX 1

后续介绍的 grid 布局也是如此。

2.1、创建flex布局

如果不添加任何布局方式,那么所有的控件都会堆放在左上角。flex 布局可以将一些控件按行或列均匀布局,并且可以自动调整它们的间距。

可以给一个容器设置一个 flex-flow 属性,这样容器就可以使用 flex 布局方式:

lv_obj_t* cont = lv_obj_create(lv_scr_act());
lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_ROW);

对于设置了 flex 布局的容器,在其中创建的元素都会在一个坐标轴上均匀排布。例如,以下使用 for 循环创建多个控件:

lv_obj_set_size(cont, 300, 75);
for (uint8_t i = 0; i < 9; i++) {lv_obj_t* btn = lv_btn_create(cont);lv_obj_t* label = lv_label_create(btn);lv_label_set_text_fmt(label, "%d", i + 1);
}

效果为:

image

尽管没有设置按钮的位置,但是每一个按钮都会在水平位置上均匀排布。如果要让排布时不超过父容器的最大宽度,可以使用 LV_FLEX_FLOW_ROW_WRAP 折行。

也可以使用按列的方式排布控件。可以通过 lv_flex_flow_t 枚举类型检查更多的 flex 布局形式。

2.2、flex布局的对齐

以上 flex 布局中,各控件的尺寸和间距都是固定的,并且第一个控件依然会出现在左上角。如果

可以使用

void lv_obj_set_flex_align(lv_obj_t * obj, lv_flex_align_t main_place, lv_flex_align_t cross_place,lv_flex_align_t track_place);

设置 flex 布局的对齐方式。该函数一次性会设置三个方面的对齐:

  • main_place :设置行或列的对齐
  • cross_place :设置控件在一行或一列内的对齐(当控件高度或宽度不一致时就可以看出效果)
  • track_place :flex-flow 方向上的对齐

如果接触过 CSS 的话,可以明白这些对齐方式实际上就是 CSS 里的 justify-content 、align-items 和 align-content 。

例如,以下调用

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);

创建的每个控件之间在水平方向上均匀对齐、行内上下居中对齐,并作为一个整体上下居中对齐,效果为:

image

又如,以下调用:

lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_ROW_WRAP);
lv_obj_set_flex_align(cont, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START);

创建的每个控件之间在水平方向上两端对齐、行内顶端对齐,并作为一个整体顶端对齐,效果为:

image

flex 布局还可以通过

void lv_obj_set_flex_grow(lv_obj_t *obj, uint8_t grow);

动态调整各个控件的相对宽度,实现更灵活的布局规则。例如,以下代码在一个 flex-flow 框架内创建了 4 个按钮,并将第二个按钮的相对宽度设置为其它按钮的两倍:

for (uint8_t i = 0; i < 4; i++) {lv_obj_t* btn = lv_btn_create(cont);lv_obj_t* label = lv_label_create(btn);lv_label_set_text_fmt(label, "%d", i);if (i == 1)lv_obj_set_flex_grow(btn, 2);elselv_obj_set_flex_grow(btn, 1);
}

效果为:

image

以下利用相对宽度创建了一个更复杂的类似数字输入键盘的布局规则:

lv_obj_t* cont = lv_obj_create(lv_scr_act());
lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_ROW_WRAP);
lv_obj_set_size(cont, 160, 180);
lv_obj_set_flex_align(cont, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START);
lv_obj_set_style_base_dir(cont, LV_BASE_DIR_RTL, 0);
for (int8_t i = 9; i >= 0; i--) {lv_obj_t* btn = lv_btn_create(cont);lv_obj_t* label = lv_label_create(btn);lv_label_set_text_fmt(label, "%d", i);
}
lv_obj_t* btn = lv_btn_create(cont);
lv_obj_set_flex_grow(btn, 2);
lv_obj_t* label = lv_label_create(btn);
lv_label_set_text(label, "OK");

效果为:

alt

这里使用 lv_obj_set_style_base_dir() 函数设置从右向左的书写方式,因此滚动条才会出现在左侧。后续介绍样式时还会介绍更多类似函数。

一般情况下 flex-grow 和带 wrap 的 flex-flow 是冲突的,也就是说所有设置了 flex-grow 的控件都会在同一行布局,但它们的宽度可能变得很窄。因此,以上的各个数字按钮相对宽度并不一致。

使用这种布局创建键盘非常别扭,不过好在 LVGL 提供了另一种形式的布局:grid 。

3、grid布局

3.1、创建grid布局

grid 布局是一种网格形式的布局,可以按行或列来对齐控件。

为了创建网格布局,首先要给出格子的长度和宽度。一般来说,可以通过两个数组分别描述网格每一行的宽度和每一列的宽度:

static lv_coord_t col_size[] = { 60, 60, 90, LV_GRID_TEMPLATE_LAST };
static lv_coord_t row_size[] = { 40, 40, 30, LV_GRID_TEMPLATE_LAST };

每一个数组都需要以 LV_GRID_TEMPLATE_LAST 结尾。然后就可以通过

void lv_obj_set_grid_dsc_array(lv_obj_t *obj, const lv_coord_t col_dsc[], const lv_coord_t row_dsc[])

函数为一个容器设置网格划分。

注意,创建的数组一定要声明为 static 或全局变量,因为这部分数据在后续渲染时才会被用上。

划分好了网格以后,接下来就可以使用以下函数:

void lv_obj_set_grid_cell(lv_obj_t * obj, lv_grid_align_t x_align, uint8_t col_pos, uint8_t col_span,lv_grid_align_t y_align, uint8_t row_pos, uint8_t row_span);

将每一个控件摆放在合适的网格位置。align 指定每一个放置在网格上的控件相对格线的对齐;pos 指定控件放置在哪个格子里,最左上角的格子位置为 (0, 0) ;有的控件可能占据不止一个格子的位置,那么就需要使用 span 来跨越多格。

例如,以下代码:

for (uint8_t i = 0; i < 9; i++) {uint8_t col = i % 3;uint8_t row = i / 3;lv_obj_t* btn = lv_btn_create(cont);lv_obj_set_grid_cell(btn, LV_GRID_ALIGN_STRETCH, col, 1,LV_GRID_ALIGN_STRETCH, row, 1);lv_obj_t* label = lv_label_create(btn);lv_label_set_text_fmt(label, "r%d c%d", row, col);lv_obj_center(label);
}

得到的网格为:

image

这里使用 LV_GRID_ALIGN_STRETCH 让网格内的控件尺寸伸展至网格大小,使网格布局的特点更加明显。

3.2、grid布局的对齐

使用网格布局时,每个格子内的控件在创建时都可以在网格内对齐。除此之外,还可以设置网格自身的对齐方式:

void lv_obj_set_grid_align(lv_obj_t * obj, lv_grid_align_t column_align, lv_grid_align_t row_align);

网格在横向和竖向对齐摆放时,对齐方式都类似于 flex ,因此可以认为 grid 是一种二维的 flex 布局。

例如,如果略微修改以上代码,添加如下语句:

lv_obj_set_grid_align(cont, LV_GRID_ALIGN_SPACE_BETWEEN, LV_GRID_ALIGN_END);
for (uint8_t i = 0; i < 9; i++) {/* ... */lv_obj_set_grid_cell(btn, LV_GRID_ALIGN_START, col, 1,LV_GRID_ALIGN_START, row, 1);/* ... */
}

这里去除了控件尺寸的伸展,使网格的对齐特点更明显:

image


网格也可以使用相对大小,具体做法是利用 LV_GRID_FR(x) 宏计算相对宽度。例如,以下定义了一个这样的宽度数组:

static lv_coord_t col_pos[] = { LV_GRID_FR(1), 60, LV_GRID_FR(2), LV_GRID_TEMPLATE_LAST };

那么第二列的宽度是绝对宽度 60 ,剩余的宽度被划分为 3 份:第一列占 1 份,第三列占 2 份。这种形式创建的网格可以适应容器的尺寸大小:

image

4、组合控件

复选框

复选框(ckeckbox)是一种类似开关,但是带有标签的控件。可以使用以下代码创建复选框并设置标签文本:

lv_obj_t* check = lv_checkbox_create(cont);
lv_checkbox_set_text(check, "Use DMA");

image

一般用复选框并列表示一些“是/否”的选项,因此多个并列的复选项很适合使用 flex 布局表现。复选框可以通过状态 LV_STATE_CHECKED 检查是否被勾选。

LVGL 中没有提供单选按钮(radio button)这一控件,不过可以使用复选框表示单选按钮。单选按钮在同一时间内只有且必须有一个选择框被选中。首先创建一个框架并使用列模式的 flex 布局:

lv_obj_t* cont = lv_obj_create(lv_scr_act());
lv_obj_set_size(cont, 140, 200);
lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN);
lv_obj_set_flex_align(cont, LV_FLEX_ALIGN_SPACE_EVENLY, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER);

然后可以在其中创建一些复选框:

#define CHECKBOX_ITEMS 4
char* checkbox_labels[CHECKBOX_ITEMS] = {"Use parity bits", "Use stop bit", "Auto send", "Debug mode" };
for (uint8_t i = 0; i < CHECKBOX_ITEMS; i++) {lv_obj_t* check = lv_checkbox_create(cont);lv_checkbox_set_text(check, checkbox_labels[i]);
}

为了实现单选按钮的效果,需要在点击事件中清除上一个被选中的选择框。这里介绍一个技巧如何获取事件控件的父容器。如果一个控件被设置了冒泡事件标志 LV_OBJ_FLAG_EVENT_BUBBLE ,那么该控件被点击时,事件将会由它的父容器触发(如果父容器也设置了这一标志位,那么事件还会继续向上冒泡)。

可以通过

lv_obj_t* lv_event_get_current_target(lv_event_t* e);

获取最终触发真正送出事件的控件(也就是冒泡后的父控件),而之前介绍的 lv_event_get_target() 函数则获取的是最先触发事件的控件(也就是子控件)。这样通过设置合适的冒泡层数,就可以同时获取控件与它的父容器了。

了解了这一特性后,就可以编写合适的代码了。首先定义一个全局变量 checked_index 记录单选按钮组此刻选中的按钮索引号,并作为用户数据传给回调函数中:

static uint8_t checked_index = 0;
/* ... */
lv_obj_add_event_cb(cont, radio_checked_cb, LV_EVENT_CLICKED, &checked_index);
for (uint8_t i = 0; i < CHECKBOX_ITEMS; i++) {/* ... */lv_obj_add_flag(check, LV_OBJ_FLAG_EVENT_BUBBLE);
}

由于事件最终由父容器触发,因此要给父容器提供回调函数。然后,在回调函数中通过父容器与索引值取消上一个被点击的选择框选择,选择点击的选择框并更新索引值:

static void radio_checked_cb(lv_event_t* e) {uint8_t* post_checked_index = lv_event_get_user_data(e);lv_obj_t* target = lv_event_get_target(e);lv_obj_t* parent = lv_event_get_current_target(e);if (target == parent) return;lv_obj_clear_state(lv_obj_get_child(parent, *post_checked_index), LV_STATE_CHECKED);lv_obj_add_state(target, LV_STATE_CHECKED);*post_checked_index = lv_obj_get_index(target);
}

由于父容器也拥有点击事件,因此首先要判断事件是否是由选择框触发的。这种事件处理方式非常简洁高效,而且无需定义额外的辅助数组。

这样就可以使用复选框代替单选按钮了,并且这样的回调函数是可以复用的,如果有另一组单选按钮也可以使用类似的方式提供响应:

image

5、列表

LVGL 的列表(list)表现形式更像大多数界面提供的标题栏菜单。这里先介绍列表仅仅是因为它比较简单。列表的核心函数只有 3 个:

lv_obj_t *lv_list_create(lv_obj_t *parent);
lv_obj_t *lv_list_add_text(lv_obj_t *list, const char *txt);
lv_obj_t *lv_list_add_btn(lv_obj_t *list, const void *icon, const char *txt);

以下应用这三个函数创建一个列表:

lv_obj_t* list = lv_list_create(lv_scr_act());
lv_list_add_text(list, "group1");
for (uint8_t i = 0; i < 2; i++)lv_list_add_btn(list, NULL, "item");
lv_list_add_text(list, "group2");
for (uint8_t i = 0; i < 3; i++)lv_list_add_btn(list, NULL, "item");

效果为:

image

默认创建的列表尺寸较大,可以手动调整尺寸大小。

列表中的按钮和一般创建的按钮没有区别,可以给返回值提供回调函数。按钮在创建时还可以指定按钮的图标,图标的本质就是 Unicode 中的特殊符号,在 lvgl/src/font/lv_symbol_def.h 中可以查看提供的特殊符号。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/177952.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

fastapi-参数

路径参数 你可以使用与 Python 格式化字符串相同的语法来声明路径"参数"或"变量"&#xff1a; from fastapi import FastAPIapp FastAPI()app.get("/items/{item_id}") async def item_details(item_id: int):return {"item_id": i…

竖版视频怎么做二维码?扫码播放竖版视频的方法

当我们在将视频转二维码图片展示的时候&#xff0c;一般横版视频在手机展示不会有影响&#xff0c;但是竖版视频会默认用横版的方式播放就会导致无法清晰的看到画面的内容&#xff0c;那么如何将竖版视频生成二维码是很多小伙伴头疼的一个问题。那么下面教大家使用二维码生成器…

Android 如何在Android studio中快速创建raw和assets文件夹

一 方案 1. 创建raw文件夹 切成project浏览模式——>找到res文件粘贴要放入raw文件夹下的文件。 当然此时raw文件还没有&#xff0c;直接在右侧输入框中出现的路径~\res后面加上\raw即可。 2. 创建assets文件夹 同理在main文件夹下粘贴要放入assets文件夹的文件&#xff0…

Java精品项目62基于Springboot+Vue实现的大学生在线答疑平台(编号V62)

Java精品项目62基于SpringbootVue实现的大学生在线答疑平台(编号V62) 大家好&#xff0c;小辰今天给大家介绍一个基于SpringbootVue实现的大学生在线答疑平台(编号V62)&#xff0c;演示视频公众号&#xff08;小辰哥的Java&#xff09;对号查询观看即可 文章目录 Java精品项目…

云尘-Node1 js代码

继续做题 拿到就是基本扫一下 nmap -sP 172.25.0.0/24 nmap -sV -sS -p- -v 172.25.0.13 然后顺便fscan扫一下咯 nmap: fscan: 还以为直接getshell了 老演员了 其实只是302跳转 所以我们无视 只有一个站 直接看就行了 扫出来了两个目录 但是没办法 都是要跳转 说明还是需要…

tomcat必要的配置

tomcat要配置两个&#xff0c;不然访问不了localhost:8080 名&#xff1a;CATALINA_HOME 值&#xff1a;D:\software\computer_software\Tomcat\tomcat8.5.66

taro全局配置页面路由和tabBar页面跳转

有能力可以看官方文档&#xff1a;Taro 文档 页面路由配置&#xff0c;配置在app.config.ts里面的pages里&#xff1a; window用于设置小程序的状态栏、导航条、标题、窗口背景色&#xff0c;其配置项如下&#xff1a; tabBar配置&#xff1a;如果小程序是一个多 tab 应用&…

你一般会什么时候使用CHATGPT?

在当今数字时代&#xff0c;人们对于人工智能&#xff08;AI&#xff09;的依赖程度日益增加&#xff0c;而ChatGPT作为一种强大的自然语言处理工具&#xff0c;吸引了人们的广泛关注和应用。那么&#xff0c;人一般在什么时候会想要使用ChatGPT呢&#xff1f;这个问题涵盖了多…

【原创】java+swing+mysql志愿者管理系统设计与实现

摘要&#xff1a; 志愿者管理系统是一个用于管理志愿者以及活动报名的系统&#xff0c;提高志愿者管理的效率&#xff0c;同时为志愿者提供更好的服务和体验。本文主要介绍如何使用javaswingmysql去实现一个志愿者管理系统。 功能分析&#xff1a; 系统主要提供给管理员和志…

什么是块存储、文件存储、对象存储?

我们都知道&#xff0c;存储设备就是为数据提供空间。 U盘、硬盘和固态硬盘都是存储最终的存储设备。而块存储、文件存储和对象存储也可以简单地理解是不同类型的存储设备&#xff0c;它们是根据使用介质存储数据的手段或方法不同来划分的。 首先我们来看下块存储&#xff1a;…

160. 相交链表、Leetcode的Python实现

博客主页&#xff1a;&#x1f3c6;看看是李XX还是李歘歘 &#x1f3c6; &#x1f33a;每天分享一些包括但不限于计算机基础、算法等相关的知识点&#x1f33a; &#x1f497;点关注不迷路&#xff0c;总有一些&#x1f4d6;知识点&#x1f4d6;是你想要的&#x1f497; ⛽️今…

Unity Perception合成数据生成、标注与ML模型训练

在线工具推荐&#xff1a; Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在线转换 - 3D场景编辑器 任何训练过机器学习模型的人都会告诉你&#xff0c;模型是从数据得到的&#xff0c;一般来说&#xff0c;更多的数据和标签会带来更好的性能。 …

垃圾分类箱通过工业4G路由器实现无人值守远程管理

据今年发布的相关数据统计&#xff0c;人们日常生活中每人每天至少能制造1.2kg垃圾&#xff0c;在环保事业中日常垃圾处理已经成为一项紧迫且不可忽视的任务。为了实现城市清洁和环境保护&#xff0c;越来越多的地区开始引入垃圾分类箱。传统的垃圾分类箱管理方式存在着一些不便…

【APP】go-musicfox - 一款网易云音乐命令行客户端, 文件很小Mac版本只有16.5M

go-musicfox 是用 Go 写的又一款网易云音乐命令行客户端&#xff0c;支持各种音质级别、UnblockNeteaseMusic、Last.fm、MPRIS 和 macOS 交互响应&#xff08;睡眠暂停、蓝牙耳机连接断开响应和菜单栏控制等&#xff09;等功能特性。 预览 启动 启动界面 主界面 主界面 通…

网络工程师-入门基础课:华为HCIA认证课程介绍

【微/信/公/众/号&#xff1a;厦门微思网络】 华为HCIA试听课程&#xff1a;超级实用&#xff0c;华为VRP系统文件详解 华为HCIA试听课程&#xff1a;不会传输层协议&#xff0c;HCIA都考不过 华为HCIA试听课程&#xff1a;网络工程师的基本功&#xff1a;网络地址转换NAT 一…

16. 机器学习 - 决策树

Hi&#xff0c;你好。我是茶桁。 在上一节课讲SVM之后&#xff0c;再给大家将一个新的分类模型「决策树」。我们直接开始正题。 决策树 我们从一个例子开始&#xff0c;来看下面这张图&#xff1a; 假设我们的x1 ~ x4是特征&#xff0c;y是最终的决定&#xff0c;打比方说是…

linux下mysql-8.2.0集群部署(python版本要在2.7以上)

目录 一、三台主机准备工作 1、mysql官方下载地址&#xff1a;https://dev.mysql.com/downloads/ 2、修改/etc/hosts 3、关闭防火墙 二、三台主机安装mysql-8.2.0 1、解压 2、下载相应配置 3、初始化mysql&#xff0c;启动myslq&#xff0c;设置开机自启 4、查看初始密…

华为云资源搭建过程

网络搭建 EIP&#xff1a; 弹性EIP&#xff0c;支持IPv4和IPv6。 弹性公网IP&#xff08;Elastic IP&#xff09;提供独立的公网IP资源&#xff0c;包括公网IP地址与公网出口带宽服务。可以与弹性云服务器、裸金属服务器、虚拟IP、弹性负载均衡、NAT网关等资源灵活地绑定及解绑…

学习python必会知识点:if条件判断语句的运用

大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 如果有什么疑惑/资料需要的可以点击文章末尾名片领取源码 if的基本格式 if语句用来做判断&#xff0c;并选择要执行的语句分支。 基本格式如下&#xff1a; if CONDITION1:code_block(1) elif CONDITION2:code_block(2) elif CO…

Spring Task(定时任务)框架

文章目录 一、Spring Task介绍二、cron表达式1.cron表达式介绍2.cron表达式在线生成器 三、fixedDelay四、fixedRate五、initialDelay六、Spring Task的使用1.导入maven坐标spring-context2.启动类添加注解EnableScheduling开启任务调度3.自定义定时任务类 一、Spring Task介绍…