LVGL库入门 02 - 布局
1、简单布局
可以使用 lv_obj_set_pos(obj, x, y)
调整一个控件的位置(或者使用类似的函数单独调整一个方向的坐标),将它放在相对父容器左上角的合适位置。不过这种布局方式非常死板,因为绝对坐标一旦设定就不能自动调整;而且当控件数量较多时,也很难确定合适的坐标值。
上一节介绍过,可以使用 lv_obj_align(obj, align, x_ofs, y_ofs)
设置一个控件相对父容器的对齐,并用以下图片展示所有的对齐方式:
从图片中可以看到,控件之间不仅可以内对齐,也可以外对齐。如果两个控件间没有包含关系也不要紧,可以使用 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); }
效果为:
尽管没有设置按钮的位置,但是每一个按钮都会在水平位置上均匀排布。如果要让排布时不超过父容器的最大宽度,可以使用 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);
创建的每个控件之间在水平方向上均匀对齐、行内上下居中对齐,并作为一个整体上下居中对齐,效果为:
又如,以下调用:
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);
创建的每个控件之间在水平方向上两端对齐、行内顶端对齐,并作为一个整体顶端对齐,效果为:
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); }
效果为:
以下利用相对宽度创建了一个更复杂的类似数字输入键盘的布局规则:
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");
效果为:
这里使用 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); }
得到的网格为:
这里使用 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);/* ... */ }
这里去除了控件尺寸的伸展,使网格的对齐特点更明显:
网格也可以使用相对大小,具体做法是利用 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 份。这种形式创建的网格可以适应容器的尺寸大小:
4、组合控件
复选框
复选框(ckeckbox)是一种类似开关,但是带有标签的控件。可以使用以下代码创建复选框并设置标签文本:
lv_obj_t* check = lv_checkbox_create(cont); lv_checkbox_set_text(check, "Use DMA");
一般用复选框并列表示一些“是/否”的选项,因此多个并列的复选项很适合使用 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); }
由于父容器也拥有点击事件,因此首先要判断事件是否是由选择框触发的。这种事件处理方式非常简洁高效,而且无需定义额外的辅助数组。
这样就可以使用复选框代替单选按钮了,并且这样的回调函数是可以复用的,如果有另一组单选按钮也可以使用类似的方式提供响应:
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");
效果为:
默认创建的列表尺寸较大,可以手动调整尺寸大小。
列表中的按钮和一般创建的按钮没有区别,可以给返回值提供回调函数。按钮在创建时还可以指定按钮的图标,图标的本质就是 Unicode 中的特殊符号,在 lvgl/src/font/lv_symbol_def.h
中可以查看提供的特殊符号。
相关文章:

LVGL库入门 02 - 布局
1、简单布局 可以使用 lv_obj_set_pos(obj, x, y) 调整一个控件的位置(或者使用类似的函数单独调整一个方向的坐标),将它放在相对父容器左上角的合适位置。不过这种布局方式非常死板,因为绝对坐标一旦设定就不能自动调整…...

利用Vue2实现印章徽章组件
需要实现的组件效果: 该组件有设置颜色、大小、旋转度数和文本内容功能。 一、组件实现代码 <template><divclass"first-ring"v-bind"getBindValue":class"getStampBadgeClass":style"{ transform: rotate(${rotate}…...

金麟国际用工-全新蓝领跨境就业服务平台
金麟国际用工-全新蓝领跨境就业服务平台 金麟国际用工平台是一个引领时代的蓝领跨境就业服务平台,专为蓝领求职者和雇主提供一个全面、便捷、高效的就业对接环境。这个平台通过其强大的数字化系统,包括客户管理系统、岗位信息系统和智能营销工具等&…...
性能测试知多少---并发用户
在做性能测试的时候,我们常常听到并发用户、响应时间、吞吐量专业术语,也许大家都理解,这里有一个理解的层次与深度概念。最近有看断念《软件性能详解与案例分析》一书,看了他的讲解,原来我对这些术语的理解还是比较肤…...

自动驾驶算法(三):RRT算法讲解与代码实现(基于采样的路径规划)
目录 1 RRT算法原理 2 RRT算法代码解析 3 RRT完整代码 1 RRT算法原理 RRT算法的全称是快速扩展随机树算法(Rapidly Exploring Random Tree),它的想法就是从根结点长出一棵树当树枝长到终点的时候这样就能找到从终点到根节点的唯一路径。 算法流程: 首先…...

基于SSM的酒店客房预定管理系统
基于SSM的酒店客房预定管理系统的设计与实现~ 开发语言:Java数据库:MySQL技术:SpringSpringMVCMyBatis工具:IDEA/Ecilpse、Navicat、Maven 系统展示 前台主页 客房详情 登录界面 管理员界面 用户界面 摘要 基于SSM(…...

IDEA初步入门
1 安装 现在的系统更迭很快,很多软件都只支持win10 和 11了,但我们过时党还在用win7. 所以就必须找到合适的版本。在windows 7 64位系统下,可以使用IDEA 2020.1.4版本。 在Jetbrain官方下,找到历史版本,找到windows版…...

《Webpack 5 基础配置》- 禁止在出现编译错误或警告时,覆盖浏览器全屏显示
Webpack5 overlay 配置地址默认编译错误或警告为 true,即浏览器全屏显示;overlay 属性可以是 boolean 型,也可是 object 类型;还有其它设置说明,详见上述官网地址; module.exports {devServer: {client: {…...

echart 饼图怎么让图形铺满整个div
1.原效果(未铺满):原配置 2.如果想要铺满,需要设置radius ,radius的意思是 第一个元素为内环半径,第二个参数为外环半径; 如果想要填满的话直接写[0,100%],不过第一个为0后就不是圆环里&#…...

回归预测 | Matlab实现WOA-CNN-SVM鲸鱼算法优化卷积神经网络-支持向量机的多输入单输出回归预测
回归预测 | Matlab实现WOA-CNN-SVM鲸鱼算法优化卷积神经网络-支持向量机的多输入单输出回归预测 目录 回归预测 | Matlab实现WOA-CNN-SVM鲸鱼算法优化卷积神经网络-支持向量机的多输入单输出回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.WOA-CNN-SVM鲸鱼算法…...
arm-none-eabi-gcc下实现printf的两种方式
方式1,移植第三方printf库: 1. 下载地址:https://github.com/mpaland/printf 2. 拷贝其中的printf.c和printf.h到本地; 3. 重新实现 void _putchar(char character) 接口,使用具体串口发送ch数据,如在 u…...
组件库开发
组件库开发 环境搭建 menorepo pnpmpnpm-workspacelerna 7.4.2 (已全局安装lerna) 1、初始化 1.1 新建项目目录root 1.2 在目录root中使用pnpm初始化packages.json文件,新建 pnpm-workspace.yaml文件, packages/文件夹 pnp…...

【python基础】魔法参数*args, **kwargs的使用
文章目录 前言一、*args 和 **kwargs 是什么?二、*args 的用法打包参数:将不定数量的参数传递给一个函数拆分参数:调用一个函数 三、**kwargs 的用法打包参数:将不定数量的参数传递给一个函数拆分参数:调用一个函数 四…...

Android Icon 添加水印 Python脚本
源代码 # -*- coding: utf-8 -*- from PIL import Image 图片合成def mergePictureLXJ():commonIcon Image.open("icon.png")markIcon Image.open("领现金.png")markLayer Image.new(RGBA, commonIcon.size, (0, 0, 0, 0))markLayer.paste(markIcon, (0…...
选择Centos系统需不需要带SElinux?
CentOS 7的SELinux代表"Security-Enhanced Linux",它是一个Linux操作系统的安全增强功能。SELinux是一个强制访问控制(Mandatory Access Control,MAC)系统,它在操作系统级别提供了更加精细的访问控制和安全策…...

项目级asp.net框架的LIMS实验室管理系统源码
LIMS可用于管理完整的实验程序,从样品登记到检验、校核、审核到最终批准报告,建立在过程质量控制的基础上,对检测流程进行有效全面的管理,对影响质量的人、机、料、法、环因素加以控制,同时为质量改进提供数据依据。进…...
pthread 变量静态初始化 避免使用被销毁过的变量
pthread 变量静态初始化 互斥锁:pthread_mutex_t g_mutex PTHREAD_MUTEX_INITIALIZER;读写锁:pthread_rwlock_t g_rwlock PTHREAD_RWLOCK_INITIALIZER;条件变量:pthread_cond_t g_cond PTHREAD_COND_INITIALIZER; 适用场景 以互斥锁为例…...

深度学习之基于ResNet18的神经网络水果分类系统
欢迎大家点赞、收藏、关注、评论啦 ,由于篇幅有限,只展示了部分核心代码。 文章目录 一项目简介二、功能三、神经网络水果分类系统四. 总结 一项目简介 基于ResNet18神经网络的水果分类系统是一个利用深度学习技术进行水果图像分类的系统。下面是该系统…...
并查集易错点
并查集就俩核心点,1是找父节点,2是合并 1: return fa[x] x ? x : fa[x] find(fa[x]); 2. fa[find(a)] find(b) 第二步还挺容易写错的,左边是find(a)的根,而不是fa[a]...

车载网关产品解析(附:车载网关详细应用案例及部署流程)
5G车载网关是一款功能强大的工业级无线通讯设备。它集成了4G/5G双模网络模块、M12接口设计、强大的路由和安全功能等特性,可以为车载和移动应用提供稳定可靠的无线数据连接。 链接直达:https://www.key-iot.com/iotlist/sv900.html ### 产品特性 5G车载网关最大的…...
SAP 在 AI 与数据统一平台上的战略转向
在 2025 年 SAP Sapphire 大会上,SAP 展示了其最新的产品战略和技术整合方向,与以往不同的是,今年的讨论更加务实、聚焦客户实际需求。SAP 强调,ERP 的转型不再是“一刀切”或破坏性的,而是可以根据客户现状࿰…...

固定ip和非固定ip的区别是什么?如何固定ip地址
在互联网中,我们常会接触到固定IP和非固定IP的概念。它们究竟有何不同?如何固定IP地址?让我们一起来探究这个问题。 一、固定IP和非固定IP的区别是什么 固定IP(静态IP)和非固定IP(动态IP)是两种…...
PCB特种工艺应用扩展:厚铜、高频与软硬结合板
新能源汽车与消费电子驱动PCB特种工艺创新,厚铜板降阻30%,软硬结合板渗透率年增15%。 1. 厚铜板:新能源高压平台核心 技术突破:猎板PCB量产10oz厚铜板(传统为3oz),载流能力提升200%,…...
GitHub 趋势日报 (2025年06月05日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 1472 onlook 991 HowToCook 752 ChinaTextbook 649 quarkdown 451 scrapy 324 age…...
vue3单独封装表单校验函数
1.在页面中建一个.ts文件 import { useI18n } from /hooks/web/useI18n import { FormItemRule } from element-plusconst { t } useI18n()interface LengthRange {min: numbermax: numbermessage?: string } //必输项校验 export const useValidator () > {const requi…...
JAVA获取ES连接并查询所有数据
我们的项目要获取es连接,新版本和旧版本有不小的区别,在8.17.0版本使用的是 ElasticsearchClient <dependency><groupId>co.elastic.clients</groupId><artifactId>elasticsearch-java</artifactId><version>8.17…...
AI图片售卖:是暴利新风口还是虚幻泡沫?哪些平台适合售卖AI图片
还记得去年大火的Midjourney吗?今年4月,Midjourney又发布了备受期待的V7版本,带来了更高的图像质量和创新功能。使用Midjourney、Stable Diffusion、DALLE等AI图片生成工具,创作者只需输入关键词即可获得高质量的原创图片。这一变…...

Linux进程(中)
目录 进程等待 为什么有进程等待 什么是进程等待 怎么做到进程等待 wait waitpid 进程等待 为什么有进程等待 僵尸进程无法杀死,需要进程等待来消灭他,进而解决内存泄漏问题--必须解决的 我们要通过进程等待,获得子进程退出情况--知…...

【SSM】SpringBoot笔记2:整合Junit、MyBatis
前言: 文章是系列学习笔记第9篇。基于黑马程序员课程完成,是笔者的学习笔记与心得总结,供自己和他人参考。笔记大部分是对黑马视频的归纳,少部分自己的理解,微量ai解释的内容(ai部分会标出)。 …...

[华为eNSP] OSPF综合实验
目录 配置流程 画出拓扑图、标注重要接口IP 配置客户端IP 配置服务端IP 配置服务器服务 配置路由器基本信息:名称和接口IP 配置路由器ospf协议 测试结果 通过配置OSPF路由协议,实现跨多路由器的网络互通,并验证终端设备的访问能力。 …...