LVGL容器布局完全指南:Flex与Grid让ESP32界面开发不再靠坐标

LVGL容器布局完全指南:Flex与Grid让ESP32界面开发不再靠坐标
很多嵌入式开发者在最初接触 LVGL 时都是从“创建一个按钮”“显示一个标签”开始的。这个阶段很轻松因为只需要设置坐标和尺寸屏幕上就能出现想要的内容。但一旦界面元素变多比如要做一个设备状态面板、一个带多组参数的数据展示页面或者一个带侧边栏的菜单界面问题立刻来了按钮该放在哪、卡片怎么对齐、不同分辨率下控件是不是会重叠、代码怎么维护。这些问题本质上不是“屏幕不够大”而是“布局思路不对”。在 ESP32 这类资源受限的嵌入式平台上LVGL 已经是事实上的 GUI 标准。而容器与布局恰恰是 LVGL 从“能画控件”走向“能做好界面”的关键分水岭。很多人觉得容器只是“把控件包起来”实际上它真正的价值在于用一套声明式的布局规则替代手写坐标让界面在不同屏幕尺寸下都能自动排列。这是 LVGL 进阶路上绕不开的核心能力。这篇文章会围绕 ESP32 LVGL 的容器与布局把 Flex 布局、Grid 布局、容器嵌套、样式控制、常见坑位和实践建议完整拆解一遍。看完之后你应该能自己搭出一个结构清晰、可维护的 LVGL 界面而不是继续在坐标堆砌里挣扎。1. 这篇文章真正要解决的问题先说一个真实的开发场景。假设你给 ESP32 接了一块 2.4 寸 SPI 屏要做一个温湿度监控界面。界面上需要显示标题、三个数据卡片、底部两个控制按钮。如果按照很多入门教程的写法流程是这样的lv_obj_t *label lv_label_create(lv_scr_act()); lv_obj_set_pos(label, 10, 5); lv_label_set_text(label, 设备状态); lv_obj_t *temp_card lv_obj_create(lv_scr_act()); lv_obj_set_pos(temp_card, 10, 60); lv_obj_set_size(temp_card, 70, 80); lv_obj_t *hum_card lv_obj_create(lv_scr_act()); lv_obj_set_pos(hum_card, 90, 60); lv_obj_set_size(hum_card, 70, 80);这段代码看起来没什么问题因为屏幕宽度是 240三个卡片横排每张宽 70加上间距和边距刚好放得下。但真实项目的界面不会只有三个卡片。当你需要增加一个数据项或者屏幕从 2.4 寸换成 3.5 寸所有坐标都要重新计算一遍。更麻烦的是如果某个控件的字体变大、文字变长原本写死的坐标就会互相重叠。这种“坐标硬编码”的开发方式在界面复杂度上来之后维护成本会指数级上升。这篇文章要解决的问题就是用 LVGL 的容器与布局能力替代手工坐标计算让界面自动适应内容变化和屏幕尺寸变化。具体来说读完你会掌握LVGL 中容器的本质是什么它和普通控件有什么关系Flex 布局如何实现横向、纵向自动排列Grid 布局如何搭建行列分明的数据面板容器如何嵌套形成清晰合理的界面层级常见布局问题的排查思路。2. 核心概念与原理容器不是“一个控件”而是一套布局思路在进入代码之前必须先建立一个准确的技术判断否则后续所有 API 的使用都会浮在表面。LVGL 的容器机制背后是一个从“绝对坐标”到“相对布局”的思维转变。只有理解了这个转变你才能真正用好 Flex 和 Grid。2.1 从“坐标写死”到“自动布局”在传统的嵌入式 GUI 开发中给控件定位的方式非常原始。比如在 ST7735、ILI9341 这类屏幕上如果想放一个按钮通常会直接写lv_obj_t *btn lv_btn_create(screen); lv_obj_set_pos(btn, 20, 30); lv_obj_set_size(btn, 100, 40);这种写法在屏幕尺寸固定、控件数量少的时候完全没问题。可一旦界面复杂起来问题就暴露了换一套屏幕分辨率所有坐标都要重新算新增一个控件可能挤掉旁边的控件要手动调整整个布局不同字体、不同文字长度下控件实际宽度不一样写死的坐标很容易重叠团队合作时每个人只管自己那块坐标最后拼起来往往是乱的。这种开发方式其实就是“手写坐标”模式成本集中在每次界面调整后的“重新计算”上。LVGL 的容器和布局机制本质上是要解决这个问题让控件的位置和尺寸由容器和布局规则决定而不是由人肉坐标决定。2.2 LVGL 中的容器是什么LVGL 里没有一个叫“lv_container”的控件但有一个能够承担容器职责的基础对象叫做lv_obj。你可以把它理解成一个“透明盒子”它本身不显示内容但可以作为其他控件的父对象设置大小、位置、背景、边框、圆角、阴影设置布局方式让子控件自动排列设置滚动、裁剪、点击事件等行为。所以在 LVGL 中“容器”实际上就是用lv_obj创建一个普通对象然后把它当作其他控件的父级。官方也对lv_obj做了很多扩展比如lv_cont在早期版本中存在后来逐步被吸收进lv_obj的 flex/grid 能力中。现代的 LVGL 8/9 版本直接使用lv_obj就可以做容器和布局。核心概念只有两个容器负责承载子控件、划定布局区域布局决定容器内子控件的排列方式包括方向、间距、对齐、换行等。2.3 LVGL 容器与 HTML/CSS 容器的类比如果把 LVGL 的容器机制和 Web 前端做对比会更容易理解LVGL 概念Web 前端对应概念说明lv_obj_create(parent)div创建一个空的容器对象lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_ROW)display: flex; flex-direction: row;设置 flex 布局方向LV_FLEX_FLOW_COLUMNflex-direction: column;纵向排列lv_obj_set_style_pad_row(obj, 10, 0)gap: 10px;行间距lv_obj_set_grid_dsc_array(obj, col_dsc, row_dsc)grid-template-columns: ...; grid-template-rows: ...;网格布局LV_GRID_ALIGN_STRETCHalign-items: stretch;拉伸对齐当然LVGL 的布局能力在功能丰富度上远远不如 CSS它面对的是资源受限的嵌入式设备。但基本的设计思想是相通的用规则描述布局而不是用坐标堆砌界面。2.4 为什么 LVGL 选择 Flex 和 Grid 两种布局HTML/CSS 里最常用的布局方案就是 Flex 和 GridLVGL 在 8.x 版本中跟着引入了两者说明这套方案在“声明式描述布局”上已经非常成熟。Flex 布局适合一维排列要么是横排、要么是竖排比如工具栏、列表项、状态栏、按钮组。Grid 布局适合二维排列适合仪表盘、键盘、网格面板、图表区域等需要行列对齐的场景。两者可以嵌套使用外层用 Grid 整体分块内层用 Flex 排列具体元素。这也是实际项目中最常见的组合方式。在进入实操之前有一个判断非常明确如果你要在 ESP32 上做稍微复杂一点的 LVGL 界面不掌握容器与布局后面所有工作都是在给自己挖坑。手动坐标也许能撑过前两三个页面但一旦数据联动、控件数量增加你会立刻感受到维护成本的爆炸式上升。这也是本文标题把“容器与布局”当作核心主题的原因。3. 环境准备与前置条件在开始写容器和布局代码之前需要把开发环境梳理清楚。LVGL 的移植方式会直接影响后续代码的调试效率这里给出一个比较稳妥的搭建方案。3.1 硬件与开发板选择从很多开发者关心的问题来看“esp32 教程”“esp32 芯片”“esp32 烧录”都是高频内容。结合本篇文章的需求建议使用以下配置硬件/软件推荐配置说明主控芯片ESP32-WROOM-32 / ESP32-S3推荐 ESP32-S3RAM 更大刷屏更快屏幕ILI9341 2.4寸 / ST7789 1.3寸SPI 接口屏幕最常用触摸XPT2046 电阻触摸屏可选布局演示不强制需要触摸开发框架ESP-IDF v5.x / Arduino ESP32两者都可以本文使用 ESP-IDF 思路演示LVGL 版本LVGL 8.3.x目前最稳定的 8.x 系列构建系统CMake idf.pyESP-IDF 自带需要注意LVGL 9.x 已经发布但 8.3.x 仍然是很多项目的主力版本社区资料和第三方控件也更集中在 8.x。本文以 8.3.x 为主但只要理解 API 变化规律迁移到 9.x 也不难。3.2 LVGL 获取与添加在 ESP-IDF 工程中LVGL 通常以 component 形式管理。推荐使用官方仓库的 release/v8.3 分支。cd components git clone -b release/v8.3 https://github.com/lvgl/lvgl.git如果使用的是 Arduino IDE可以在库管理器中搜索 “lvgl” 并安装或者在项目里放置lv_conf.h配置文件。3.3 关键配置项LVGL 的配置文件是lv_conf.h移植到 ESP32 时需要关注以下配置#define LV_COLOR_DEPTH 16 #define LV_MEM_CUSTOM 0 #define LV_MEM_SIZE (32U * 1024U) #define LV_FONT_MONTSERRAT_14 1 #define LV_TICK_CUSTOM 1 #define LV_USE_FLEX 1 #define LV_USE_GRID 1LV_USE_FLEX和LV_USE_GRID必须开启否则布局 API 不生效。这两个开关对于新手来说非常容易忽略很多人写完布局代码发现没有任何变化检查了半天最后才发现是配置文件里没开。需要注意LV_MEM_SIZE不是越大越好ESP32 的 RAM 是有限的。布局和容器本身会占用一些动态内存建议先用 32KB如果后续控件多、动画多再逐步调大。3.4 显示驱动初始化在写布局代码之前确保屏幕能正常显示一个基础画面。这一步很关键因为如果屏幕初始化、缓冲区设置、刷屏回调有问题后面的所有布局演示都会是花屏或者黑屏。// 文件路径main/lvgl_display.c void lvgl_display_init(void) { lv_init(); static lv_disp_draw_buf_t draw_buf; static lv_color_t buf1[240 * 10]; lv_disp_draw_buf_init(draw_buf, buf1, NULL, 240 * 10); static lv_disp_drv_t disp_drv; lv_disp_drv_init(disp_drv); disp_drv.hor_res 240; disp_drv.ver_res 320; disp_drv.flush_cb screen_flush_cb; disp_drv.draw_buf draw_buf; lv_disp_drv_register(disp_drv); }其中screen_flush_cb是实际的 SPI 刷屏回调函数具体实现和屏幕驱动有关这里不展开。只要屏幕能够绘制一个简单控件就说明环境已经准备好。4. 核心流程拆解用容器和布局搭建界面在动手写代码之前需要先明确界面结构和布局思路理清“容器层级”与“布局策略”这两个维度。很多人第一次接触 LVGL 布局时会犯一个错误想到什么控件就直接创建一个完全不规划父子关系结果页面层级混乱布局代码写到后面自己都看不懂。4.1 设计界面层级以本文要演示的“智能设备状态面板”为例界面可以分为三个区域顶部标题栏显示标题文本中部数据区显示一组参数卡片例如温度、湿度、电量底部操作区并排显示两个按钮。对应的 LVGL 容器层级如下screen └── main_container全屏容器Grid 布局行分三段 ├── header_container顶部容器Flex 布局横向 │ └── title_label ├── body_container中部容器Grid 布局三列 │ ├── card_temp │ ├── card_humidity │ └── card_battery └── footer_container底部容器Flex 布局横向 ├── btn_start └── btn_stop这种层级设计的好处是每个容器职责单一改一个区域不影响其他区域内部控件使用相对布局不依赖绝对坐标屏幕分辨率变化时只需要调整容器的留白、间距和行列比例整体结构不变。4.2 创建主容器首先在屏幕上创建一个全屏容器设置 Grid 布局lv_obj_t *main_container lv_obj_create(lv_scr_act()); lv_obj_set_size(main_container, lv_pct(100), lv_pct(100)); lv_obj_center(main_container); static lv_coord_t main_col_dsc[] { LV_GRID_FR(1), LV_GRID_TEMPLATE_LAST }; static lv_coord_t main_row_dsc[] { LV_GRID_CONTENT(60), LV_GRID_FR(1), LV_GRID_CONTENT(50), LV_GRID_TEMPLATE_LAST }; lv_obj_set_grid_dsc_array(main_container, main_col_dsc, main_row_dsc); lv_obj_set_style_pad_row(main_container, 0, 0); lv_obj_set_style_pad_column(main_container, 0, 0);这里有三行顶部固定 60 像素中间自适应底部固定 50 像素。LV_GRID_FR(1)表示弹性分配剩余空间LV_GRID_CONTENT(value)表示固定尺寸。4.3 创建顶部区域顶部区域是一个横向 Flex 容器放置标题标签lv_obj_t *header_container lv_obj_create(main_container); lv_obj_set_grid_cell(header_container, LV_GRID_ALIGN_STRETCH, 0, 1, LV_GRID_ALIGN_STRETCH, 0, 1); lv_obj_set_flex_flow(header_container, LV_FLEX_FLOW_ROW); lv_obj_set_style_pad_all(header_container, 10, 0); lv_obj_set_style_border_width(header_container, 0, 0);lv_obj_set_grid_cell将 header 容器放置到主容器 Grid 的第一列、第一行并拉伸填充。标题标签lv_obj_t *title_label lv_label_create(header_container); lv_label_set_text(title_label, 设备状态面板); lv_obj_set_style_text_font(title_label, lv_font_montserrat_14, 0);4.4 创建中部数据区中部数据区是 Grid 布局三列等宽。这里会演示如何在 Grid 单元中使用 Flex 布局把卡片内容和数值垂直排布。lv_obj_t *body_container lv_obj_create(main_container); lv_obj_set_grid_cell(body_container, LV_GRID_ALIGN_STRETCH, 0, 1, LV_GRID_ALIGN_STRETCH, 1, 1); static lv_coord_t body_col_dsc[] { LV_GRID_FR(1), LV_GRID_FR(1), LV_GRID_FR(1), LV_GRID_TEMPLATE_LAST }; static lv_coord_t body_row_dsc[] { LV_GRID_FR(1), LV_GRID_TEMPLATE_LAST }; lv_obj_set_grid_dsc_array(body_container, body_col_dsc, body_row_dsc);以温度卡片为例创建卡片容器并让它在 body 容器中占据第一行第一列lv_obj_t *temp_card lv_obj_create(body_container); lv_obj_set_grid_cell(temp_card, LV_GRID_ALIGN_STRETCH, 0, 1, LV_GRID_ALIGN_STRETCH, 0, 1); lv_obj_set_flex_flow(temp_card, LV_FLEX_FLOW_COLUMN); lv_obj_set_style_pad_all(temp_card, 8, 0); lv_obj_set_style_border_width(temp_card, 1, 0); lv_obj_set_style_border_color(temp_card, lv_color_hex(0xCCCCCC), 0); lv_obj_t *temp_name lv_label_create(temp_card); lv_label_set_text(temp_name, 温度); lv_obj_set_style_text_color(temp_name, lv_color_hex(0x888888), 0); lv_obj_t *temp_value lv_label_create(temp_card); lv_label_set_text(temp_value, 26.5°C); lv_obj_set_style_text_font(temp_value, lv_font_montserrat_14, 0);湿度卡片和电量卡片的结构完全一样只需修改列索引和文本内容。这里不再重复粘贴代码。4.5 创建底部操作区底部操作区使用 Flex 横向布局两个按钮并排放置并设置均匀间距lv_obj_t *footer_container lv_obj_create(main_container); lv_obj_set_grid_cell(footer_container, LV_GRID_ALIGN_STRETCH, 0, 1, LV_GRID_ALIGN_STRETCH, 2, 1); lv_obj_set_flex_flow(footer_container, LV_FLEX_FLOW_ROW); lv_obj_set_style_pad_all(footer_container, 8, 0); lv_obj_set_style_pad_column(footer_container, 16, 0); lv_obj_t *btn_start lv_btn_create(footer_container); lv_obj_set_size(btn_start, lv_pct(40), lv_pct(100)); lv_obj_t *btn_start_label lv_label_create(btn_start); lv_label_set_text(btn_start_label, 启动); lv_obj_center(btn_start_label); lv_obj_t *btn_stop lv_btn_create(footer_container); lv_obj_set_size(btn_stop, lv_pct(40), lv_pct(100)); lv_obj_t *btn_stop_label lv_label_create(btn_stop); lv_label_set_text(btn_stop_label, 停止); lv_obj_center(btn_stop_label);这里的关键点是两个按钮的宽度使用lv_pct(40)即相对父容器宽度百分比而不是固定像素。这样在不同尺寸屏幕上都能保持相同的视觉比例。4.6 本轮小结完成这一步之后整个界面的结构已经出来了顶部标题栏固定高度中部数据区自适应高度三张卡片等宽排列底部按钮区固定高度两个按钮横向均匀分布。整个过程中没有使用任何硬编码坐标所有控件的位置都由容器和布局规则决定。这就是容器布局的核心价值。5. Flex 布局详解与示例Flex 布局是 LVGL 中使用频率最高的布局方式。它的特点是“一维排列”只需要关注主轴方向、换行方式、对齐方式和间距。这一节会通过完整的代码示例把 Flex 布局的核心 API 讲透。5.1 Flex 布局的核心 APIAPI作用常用取值lv_obj_set_flex_flow(obj, flow)设置 Flex 主轴方向与换行LV_FLEX_FLOW_ROW、LV_FLEX_FLOW_COLUMN、LV_FLEX_FLOW_ROW_WRAPlv_obj_set_flex_align(obj, main, cross, track)设置主轴、交叉轴、行轴对齐方式LV_FLEX_ALIGN_START、LV_FLEX_ALIGN_CENTER、LV_FLEX_ALIGN_END、LV_FLEX_ALIGN_SPACE_BETWEEN、LV_FLEX_ALIGN_SPACE_AROUNDlv_obj_set_style_pad_row(obj, value, 0)控制行间距纵向排列时像素值lv_obj_set_style_pad_column(obj, value, 0)控制列间距横向排列时像素值理解这组 API 的时候可以把它想成 CSS Flexbox 的简化版。flow对应flex-direction flex-wrapalign对应justify-content align-itemspad_row、pad_column对应gap。5.2 Flex 横向排列示例下面的代码创建一个横向 Flex 容器里面放 3 个不同颜色的色块用来观察布局效果void flex_row_demo(lv_obj_t *parent) { lv_obj_t *container lv_obj_create(parent); lv_obj_set_size(container, 300, 80); lv_obj_center(container); lv_obj_set_flex_flow(container, LV_FLEX_FLOW_ROW); lv_obj_set_flex_align(container, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); lv_obj_set_style_pad_column(container, 10, 0); lv_obj_set_style_pad_all(container, 10, 0); for (int i 0; i 3; i) { lv_obj_t *box lv_obj_create(container); lv_obj_set_size(box, 40, 40); lv_obj_set_style_bg_color(box, lv_palette_main(LV_PALETTE_BLUE i), 0); lv_obj_set_style_border_width(box, 0, 0); } }运行效果3 个 40x40 的色块从容器左上角开始横向排列间隔 10 像素。如果把LV_FLEX_ALIGN_START改为LV_FLEX_ALIGN_SPACE_BETWEEN则第一个色块靠左、最后一个色块靠右剩余空间均匀分配在中间。5.3 Flex 纵向排列示例纵向排列适合列表、仪表数据行等场景void flex_column_demo(lv_obj_t *parent) { lv_obj_t *container lv_obj_create(parent); lv_obj_set_size(container, 120, 160); lv_obj_center(container); lv_obj_set_flex_flow(container, LV_FLEX_FLOW_COLUMN); lv_obj_set_flex_align(container, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER); lv_obj_set_style_pad_row(container, 8, 0); lv_obj_set_style_pad_all(container, 10, 0); for (int i 0; i 4; i) { lv_obj_t *label lv_label_create(container); lv_label_set_text_fmt(label, 数据 %d, i 1); } }这个示例里4 个标签纵向排列在主轴方向上居中在交叉轴方向上靠左。pad_row控制行间距相当于 CSS 的row-gap。5.4 常见的 Flex 对齐效果对齐取值效果LV_FLEX_ALIGN_START从主轴起点开始排列LV_FLEX_ALIGN_CENTER主轴方向居中排列LV_FLEX_ALIGN_END从主轴终点开始排列LV_FLEX_ALIGN_SPACE_BETWEEN首尾贴边元素之间间距相等LV_FLEX_ALIGN_SPACE_AROUND每个元素两侧间距相等在实际项目中SPACE_BETWEEN用于按钮组、底部操作栏非常常见因为它天然能把元素撑开。5.5 Flex 布局的适用场景从实践来看Flex 布局最适合以下场景状态栏左侧标题 右侧电池图标按钮组横排多个按钮等宽或自适应列表行图标 文本 右侧箭头进度面板纵向排列多个指标项。如果你的页面是一维排列为主优先选择 Flex。6. Grid 布局详解与示例Grid 布局解决的是“二维排列”问题。它的核心是定义行和列的模板然后把子控件放入对应的单元格中。LVGL 的 Grid 布局从 CSS Grid 借鉴了很多思想包括FR单位、content固定尺寸、拉伸对齐等。6.1 Grid 布局核心概念Grid 布局有三个必须掌握的概念列模板定义每一列的宽度策略行模板定义每一行的高度策略单元格放置将子控件放到指定行列。列/行模板使用数组描述以LV_GRID_TEMPLATE_LAST结尾static lv_coord_t col_dsc[] { 80, LV_GRID_FR(1), LV_GRID_FR(2), LV_GRID_TEMPLATE_LAST }; static lv_coord_t row_dsc[] { LV_GRID_CONTENT(40), LV_GRID_FR(1), LV_GRID_TEMPLATE_LAST };这里第一列固定 80 像素第二列占 1 份弹性空间第三列占 2 份弹性空间所以第三列比第二列宽一倍。行模板中第一行固定 40 像素第二行占据剩余空间。6.2 网格单元放置 APIlv_obj_set_grid_cell(obj, LV_GRID_ALIGN_STRETCH, col_start, col_span, LV_GRID_ALIGN_STRETCH, row_start, row_span);参数含义col_start起始列索引从 0 开始col_span横向跨列数row_start起始行索引row_span纵向跨行数LV_GRID_ALIGN_STRETCH自动拉伸填充单元格。如果你不想让子控件填满整个单元格可以改用LV_GRID_ALIGN_CENTER或其他对齐方式。6.3 完整 Grid 示例三列仪表盘下面实现一个三列等宽的仪表盘网格每个单元格内放一个名称和一个数值void grid_dashboard_demo(lv_obj_t *parent) { lv_obj_t *grid lv_obj_create(parent); lv_obj_set_size(grid, lv_pct(90), lv_pct(80)); lv_obj_center(grid); static lv_coord_t col_dsc[] { LV_GRID_FR(1), LV_GRID_FR(1), LV_GRID_FR(1), LV_GRID_TEMPLATE_LAST }; static lv_coord_t row_dsc[] { LV_GRID_FR(1), LV_GRID_FR(1), LV_GRID_TEMPLATE_LAST }; lv_obj_set_grid_dsc_array(grid, col_dsc, row_dsc); lv_obj_set_style_pad_all(grid, 10, 0); lv_obj_set_style_pad_row(grid, 10, 0); lv_obj_set_style_pad_column(grid, 10, 0); const char *names[] { 温度, 湿度, 电量, 信号, 内存, 运行时长 }; for (int i 0; i 6; i) { lv_obj_t *card lv_obj_create(grid); lv_obj_set_grid_cell(card, LV_GRID_ALIGN_STRETCH, i % 3, 1, LV_GRID_ALIGN_STRETCH, i / 3, 1); 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); lv_obj_set_style_pad_all(card, 8, 0); lv_obj_set_style_border_width(card, 1, 0); lv_obj_set_style_border_color(card, lv_color_hex(0xCCCCCC), 0); lv_obj_t *name_label lv_label_create(card); lv_label_set_text(name_label, names[i]); lv_obj_set_style_text_color(name_label, lv_color_hex(0x888888), 0); lv_obj_t *value_label lv_label_create(card); lv_label_set_text_fmt(value_label, %.1f, i * 1.5 20); lv_obj_set_style_text_font(value_label, lv_font_montserrat_14, 0); } }运行效果是一个 3x2 的卡片网格每个卡片内部用 Flex 列布局显示指标名称和数值。这个结构非常适合用作数据面板的基础模板。6.4 Grid 与 Flex 的嵌套实践实际项目中很少会纯粹只用一个布局。更常见的做法是 “Grid 分块Flex 排内”。下面是一个嵌套示例外层 Grid 划分左右区域左侧区域内部使用 Flex 纵向排列void nested_layout_demo(lv_obj_t *parent) { lv_obj_t *outer lv_obj_create

最新新闻

日新闻

周新闻

月新闻