【学习目标】
- 理解 aspectRatio 宽高比约束的计算规则,实现组件等比缩放;
- 掌握"占比适配"的两种核心方式(百分比/layoutWeight)及优先级规则;
- 掌握 displayPriority 实现子组件按优先级显隐,适配容器尺寸变化;
- 掌握 Blank 组件实现主轴剩余空间自适应拉伸,适配多设备布局。
一、工程结构
1.1 新增演示文件
基于上一节 ColumnRowApplication 工程基础,在 entry/src/main/ets/pages 目录下新增4个页面:
pages
├── AspectRatioPage.ets # 宽高比约束演示
├── LayoutWeightScalePage.ets # 占比适配演示
├── DisplayPriorityPage.ets # 显隐控制演示
├── BlankStretchPage.ets # Blank自适应拉伸演示
1.2 更新主入口(Index.ets)
javascript
// 引入路由模块(确保能正常跳转页面)
import router from '@ohos.router';
// 定义按钮数据的接口(规范数据结构)
interface ButtonItem {
// 按钮显示文本
title: string;
// 跳转路由地址
routerPath: string;
}
@Entry
@Component
struct Index {
@State message: string = 'Hello World';
// 定义按钮数据源(统一管理所有按钮的文本和路由)
private buttonList: ButtonItem[] = [
// 基础布局
{ title: "横向布局主轴", routerPath: "pages/RowFlexAlignPage" },
{ title: "横向布局交叉轴", routerPath: "pages/RowItemAlignPage" },
{ title: "纵向布局主轴", routerPath: "pages/ColumnFlexAlignPage" },
{ title: "纵向布局交叉轴", routerPath: "pages/ColumnItemAlignPage" },
{ title: "子组件差异化对齐(alignSelf)", routerPath: "pages/AlignSelfPage" },
{ title: "Margin不生效问题演示以及解决方案", routerPath: "pages/MarginPage" },
// 第十节进阶内容
{ title: "宽高比约束(aspectRatio)", routerPath: "pages/AspectRatioPage" },
{ title: "占比适配(百分比/layoutWeight)", routerPath: "pages/LayoutWeightScalePage" },
{ title: "显隐控制(displayPriority)", routerPath: "pages/DisplayPriorityPage" },
{ title: "自适应拉伸(Blank)", routerPath: "pages/BlankStretchPage" }
];
build() {
Column({ space: 20 }) {
// 页面标题
Text("线性布局演示")
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor($r('app.color.text_primary'))
.margin({ bottom: 30 })
// 动态生成按钮列表 ForEach 循环遍历生成按钮
ForEach(
// 1. 数据源:按钮数组
this.buttonList,
// 2. 渲染函数:遍历每个元素生成Button
(item: ButtonItem) => {
Button(item.title)
.width('80%') // 增加按钮宽度,适配长文本
.fontSize(16) // 调整字体大小,避免文本溢出
.onClick(() => {
router.pushUrl({ url: item.routerPath });
});
},
// 3. 唯一标识:保证列表更新时的性能(用路由地址作为唯一key)
(item: ButtonItem) => item.routerPath
)
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.backgroundColor($r('app.color.bg_page'));
}
}
1.3 资源说明
本章节使用的图片资源均存放于项目 src/main/resources/base/media 目录下,配套代码已包含所有所需资源,无需额外配置。
二、宽高比约束:aspectRatio
aspectRatio 用于指定组件宽高比(计算公式:aspectRatio = 宽度/高度),可实现组件在不同尺寸容器下的等比缩放,是适配图片、视频封面、头像等场景的核心属性。
2.1 核心规则
- 优先级:
aspectRatio> 手动设置的单维度尺寸(width/height); - 计算逻辑(父容器空间足够时):
- 仅设置宽度 + aspectRatio → 高度 = 宽度 / aspectRatio;
- 仅设置高度 + aspectRatio → 宽度 = 高度 × aspectRatio;
- 同时设置宽高 + aspectRatio → height 失效,高度强制按"宽度/aspectRatio"计算;
- 边界限制:组件最终尺寸受父容器内容区大小约束,
constraintSize优先级高于aspectRatio; - 优先级层级:constraintSize(容器约束) > aspectRatio > 手动设置的单维度尺寸(width/height)。
2.2 宽高比示例代码(aspectRatio)
javascript
@Entry
@Component
struct AspectRatioPage {
build() {
Column({ space: 20 }) {
// 页面标题
Text("宽高比约束演示(aspectRatio)")
.fontSize(22)
.fontWeight(FontWeight.Bold)
.fontColor($r('app.color.text_primary'))
.width('100%')
.textAlign(TextAlign.Center);
// 场景1:16:9 宽高比(视频封面)
Text("场景1:16:9 宽高比(宽度固定,高度自动计算)")
.fontSize(14)
.fontColor($r('app.color.text_secondary'))
.width('90%')
.margin({ bottom: 5 });
Text("视频封面")
.width('90%')
.aspectRatio(16/9)
.backgroundColor($r('app.color.bg_blue_light'))
.fontColor($r('app.color.text_white'))
.textAlign(TextAlign.Center)
.borderRadius(8)
.padding(10);
// 场景2:1:1 宽高比(圆形头像)
Text("场景2:1:1 宽高比(高度固定,宽度自动=高度)")
.fontSize(14)
.fontColor($r('app.color.text_secondary'))
.width('90%')
.margin({ top: 20, bottom: 5 });
Text("头像")
.height(100)
.aspectRatio(1)
.backgroundColor($r('app.color.bg_red_light'))
.fontColor($r('app.color.text_white'))
.textAlign(TextAlign.Center)
.borderRadius(50)
}
.width('100%')
.height('100%')
.padding(15)
.backgroundColor($r('app.color.bg_page'));
}
}
2.3 运行效果说明
- 16:9 视频封面:宽度占屏幕90%,高度自动计算为
宽度 ÷ (16/9),始终保持16:9比例; - 1:1 圆形头像:高度固定100vp,宽度自动等于高度(100vp),结合
borderRadius(50)实现完美圆形。
三、占比适配:百分比 / layoutWeight
线性布局中实现"按比例分配空间"有两种核心方式,适用于不同场景:
3.1 核心规则对比
| 适配方式 | 参考基准 | 生效方向 | 核心特点 | 适用场景 |
|---|---|---|---|---|
| 百分比 | 父/祖先容器的实际宽/高 | 任意方向 | 直接按比例适配,计算简单 | 固定比例的静态布局(如分栏、按钮宽度) |
| layoutWeight | 父容器主轴剩余空间 | 仅在主轴方向分配剩余空间 | 按权重分配空间,适配性更强 | 动态布局(如内容区+操作区、列表项分配) |
layoutWeight 计算公式:
组件最终尺寸 = 父容器主轴剩余空间 × (自身 layoutWeight 值 / 所有子组件 layoutWeight 之和)
示例:父容器高度200vp,子组件 layoutWeight 为2和1 → 剩余空间200vp,内容区=200×(2/3)≈133vp,操作区=200×(1/3)≈67vp。
注意:使用 layoutWeight 时,子组件不要手动设置主轴方向的固定尺寸(如Row中不设width、Column中不设height),否则权重分配规则会失效。
3.2 代码演示:LayoutWeightScalePage.ets
javascript
@Entry
@Component
struct LayoutWeightScalePage {
build() {
Column({ space: 20 }) {
// 页面标题
Text("占比适配演示(百分比/layoutWeight)")
.fontSize(22)
.fontWeight(FontWeight.Bold)
.fontColor($r('app.color.text_primary'))
.width('100%')
.textAlign(TextAlign.Center);
// 场景1:百分比占比
Text("场景1:百分比占比(宽度50%)")
.fontSize(14)
.fontColor($r('app.color.text_secondary'))
.width('90%')
.margin({ bottom: 5 });
Row() {
Text("宽度50%")
.width('50%')
.height(60)
.backgroundColor($r('app.color.bg_red_light'))
.fontColor($r('app.color.text_white'))
.textAlign(TextAlign.Center)
Text("剩余50%")
.width('50%')
.height(60)
.backgroundColor($r('app.color.bg_blue_light'))
.fontColor($r('app.color.text_white'))
.textAlign(TextAlign.Center)
}
.width('90%')
.backgroundColor($r('app.color.bg_white'))
.borderRadius(8);
// 场景2:layoutWeight 权重占比
Text("场景2:layoutWeight 2:1 分配(主轴剩余空间)")
.fontSize(14)
.fontColor($r('app.color.text_secondary'))
.width('90%')
.margin({ top: 20, bottom: 5 });
Column({ space: 0 }) {
Text("内容区(2/3)")
.layoutWeight(2)
.width('100%')
.backgroundColor($r('app.color.bg_purple_light'))
.fontColor($r('app.color.text_white'))
.textAlign(TextAlign.Center)
Text("操作区(1/3)")
.layoutWeight(1)
.width('100%')
.backgroundColor($r('app.color.bg_orange_light'))
.fontColor($r('app.color.text_white'))
.textAlign(TextAlign.Center)
}
.width('90%')
.height(200)
.borderRadius(8);
}
.width('100%')
.height('100%')
.padding(15)
.backgroundColor($r('app.color.bg_page'));
}
}
3.3 运行效果说明
- 百分比占比:两个文本组件各占Row宽度的50%,比例固定,无论Row整体宽度如何变化,均保持1:1;
- layoutWeight权重占比:Column总高度200vp,内容区和操作区按2:1分配高度(内容区≈133vp,操作区≈67vp),无需手动计算具体数值,适配更灵活。
四、显隐控制:displayPriority
displayPriority 是线性布局中适配容器尺寸变化的核心属性,用于控制"空间不足时子组件的隐藏顺序",解决多设备适配时"内容挤爆容器"的问题。
4.1 核心规则
- 优先级逻辑:值越小 → 重要性越低 → 空间不足时越先被移除;值越大 → 重要性越高 → 空间不足时越晚被移除;
- 移除行为:组件被隐藏时直接从组件节点移除,无文本压缩、省略号等过渡效果;
- 触发条件:Row/Column 默认不换行,容器宽度/高度不足时自动触发。
4.2 代码演示:DisplayPriorityPage.ets
javascript
@Entry
@Component
struct DisplayPriorityPage {
@State containerWidth: number = 375; // 手机基准宽度(375vp)
@State isPlaying: boolean = false;
build() {
Column({ space: 20 }) {
// 页面标题
Text("displayPriority 音乐控制栏演示")
.fontSize(22)
.fontWeight(FontWeight.Bold)
.fontColor($r('app.color.text_primary'))
.width('100%')
.textAlign(TextAlign.Center);
// 资源说明:以下图片资源均存放于项目的 src/main/resources/base/media 目录下,配套代码已包含完整资源文件
// 核心演示:音乐控制栏(值越小重要性越低,宽度不足时先被移除)
Row({space:20}) {
// 重要性1(最低):随机播放 → 最先被移除(非核心功能)
Image($r('app.media.ic_public_random'))
.objectFit(ImageFit.Contain) // 保证图片等比显示,不变形
.width(25)
.height(25)
.displayPriority(1);
// 重要性2:单曲循环 → 第二步被移除(次要功能)
Image($r('app.media.ic_public_single_cycle'))
.objectFit(ImageFit.Contain)
.width(25)
.height(25)
.displayPriority(2);
// 重要性3:上一曲 → 第三步被移除(常用功能)
Image($r('app.media.ic_previous'))
.objectFit(ImageFit.Contain)
.width(25)
.height(25)
.displayPriority(3);
// 重要性4(最高):播放/暂停 → 最后被移除(核心功能)
Image(this.isPlaying ? $r('app.media.ic_pause') : $r('app.media.ic_play'))
.objectFit(ImageFit.Contain)
.width(36)
.height(36)
.displayPriority(4)
.onClick(() => this.isPlaying = !this.isPlaying);
// 重要性3:下一曲 → 第三步被移除(常用功能)
Image($r('app.media.ic_next'))
.objectFit(ImageFit.Contain) // 补充缺失的图片适配属性
.width(25)
.height(25)
.displayPriority(3);
// 重要性2:列表循环 → 第二步被移除(次要功能)
Image($r('app.media.ic_public_list_cycle'))
.objectFit(ImageFit.Contain)
.width(25)
.height(25)
.displayPriority(2);
// 重要性1(最低):收藏 → 最先被移除(非核心功能)
Image($r('app.media.ic_public_favor_filled'))
.objectFit(ImageFit.Contain)
.width(25)
.height(25)
.displayPriority(1);
}
.padding(15)
.width(Math.round(this.containerWidth))
.backgroundColor($r('app.color.bg_white'))
.justifyContent(FlexAlign.Center)
// 滑动条调节区域(模拟不同设备宽度)
Column({ space: 10 }) {
Slider({
value: this.containerWidth,
min: 150, // 手表宽度
max: 375, // 手机宽度
step: 1,
style: SliderStyle.OutSet
})
.width('90%')
.onChange((value: number) => {
this.containerWidth = value;
});
Text(`当前容器宽度:${Math.round(this.containerWidth)}vp`)
.fontSize(16)
.fontColor($r('app.color.text_secondary'))
.width('90%')
.textAlign(TextAlign.Center);
}
// 核心提示(精准说明优先级规则)
Text("核心逻辑:displayPriority值越小重要性越低,宽度不足时小数值按钮先从组件节点移除")
.fontSize(12)
.fontColor($r('app.color.text_secondary'))
.width('90%')
.textAlign(TextAlign.Center);
}
.justifyContent(FlexAlign.SpaceEvenly)
.width('100%')
.height('100%')
.padding(15)
.backgroundColor($r('app.color.bg_page'));
}
}
4.3 运行效果
4.4 displayPriority显隐规则
displayPriority仅在 Row、Column或单行 Flex容器组件中生效:
- 所有子组件的 displayPriority值均 ≤1 时,优先级无区别(所有组件均显示);
- 当子组件的值 >1 时,数值越大优先级越高;
- 未显式设置时,系统自动采用默认值 1;
- 嵌套容器中,仅最外层容器的 displayPriority 会被系统识别(决定整个嵌套容器的移除时机);
- 外层容器被移除时,内部所有子组件(无论自身优先级)都会被一并从组件节点移除;
- 内层容器的 displayPriority 完全不生效,即便设置也不会改变移除逻辑;
示例代码:
javascript
Row() {
// 外层Row的displayPriority决定整个嵌套容器的移除时机,默认值1
Row(){
Text("元素1").width(60).displayPriority(1); // 内部优先级无影响
Text("元素2").width(60).displayPriority(3);
}
Text("元素3")
.width(60)
.displayPriority(3);
Row(){
Text("元素4").width(60).displayPriority(4);
Text("元素5").width(60).displayPriority(5);
}
.displayPriority(2);
}.width(Math.round(this.containerWidth))
运行效果
父组件Row宽度由大变小过程,元素1``元素2组件被外层Row包裹,受外层Row的displayPriority等级影响(未显式设置则默认值为1),他们一起先消失,displayPriority(2)其次,最后displayPriority(3)。
五、自适应拉伸:Blank 组件
Blank 是线性布局的"空白填充神器",用于在主轴方向自动填充剩余空间,实现"固定元素+自适应空白"的布局效果。
5.1 核心特性
- 生效范围:仅在 Row/Column/Flex 布局中生效;
- 核心作用:自动填充父容器主轴方向的剩余空间;
- 扩展用法:可通过
min参数设置最小填充尺寸(如Blank(20)); - 单位支持:数字默认vp,字符串可指定单位(如
Blank('20vp'),数字默认使用vp单位)。
5.2 代码演示:BlankStretchPage.ets
javascript
@Entry
@Component
struct BlankStretchPage {
build() {
Column({ space: 15 }) {
// 页面标题
Text("Blank 自适应拉伸演示")
.fontSize(22)
.fontWeight(FontWeight.Bold)
.fontColor($r('app.color.text_primary'))
.width('100%')
.textAlign(TextAlign.Center);
// 场景1:Row 左/右固定 + 中间填充(常用导航栏)
Text("场景1:左侧固定 + 右侧固定 + 中间填充")
.fontSize(14)
.fontColor($r('app.color.text_secondary'))
.width('90%')
.margin({ bottom: 5 });
Row({ space: 10 }) {
Text("返回")
.fontSize(16)
.fontColor($r('app.color.text_primary'))
.padding(8);
Blank(); // 填充Row主轴剩余空间,实现"返回左对齐、保存右对齐"
Button("保存")
.fontSize(14)
.padding({ left: 15, right: 15 })
.backgroundColor($r('app.color.primary_blue'))
.fontColor($r('app.color.text_white'))
.borderRadius(6);
}
.width('90%')
.padding(10)
.backgroundColor($r('app.color.bg_white'))
.borderRadius(8)
.shadow({ radius: 2, color: '#EEEEEE', offsetX: 0, offsetY: 2 });
// 场景2:Column 上下固定 + 中间填充(常用弹窗)
Text("场景2:顶部固定 + 底部固定 + 中间填充")
.fontSize(14)
.fontColor($r('app.color.text_secondary'))
.width('90%')
.margin({ top: 15, bottom: 5 });
Column({ space: 10 }) {
Text("标题栏")
.fontSize(18)
.fontWeight(FontWeight.Bold)
.padding(8);
Blank(); // 填充Column主轴剩余空间,撑满弹窗高度
Row({ space: 10 }) {
Button("取消")
.fontSize(14)
.padding({ left: 15, right: 15 })
.backgroundColor($r('app.color.bg_white'))
.fontColor($r('app.color.text_primary'))
.borderRadius(6);
Button("确认")
.fontSize(14)
.padding({ left: 15, right: 15 })
.backgroundColor($r('app.color.primary_blue'))
.fontColor($r('app.color.text_white'))
.borderRadius(6);
}.justifyContent(FlexAlign.SpaceAround)
.width('100%')
}
.width('90%')
.height(200)
.padding(10)
.backgroundColor($r('app.color.bg_blue_light'))
.borderRadius(8);
// 场景3:多个Blank均分剩余空间
Text("场景3:多个Blank均分剩余空间")
.fontSize(14)
.fontColor($r('app.color.text_secondary'))
.width('90%')
.margin({ top: 15, bottom: 5 });
Row({ space: 5 }) {
Text("A").padding(8).backgroundColor($r('app.color.bg_red_light'));
Blank(); // 第一个Blank
Text("B").padding(8).backgroundColor($r('app.color.bg_blue_light'));
Blank(); // 第二个Blank
Text("C").padding(8).backgroundColor($r('app.color.bg_purple_light'));
}
.width('90%')
.padding(10)
.backgroundColor($r('app.color.bg_white'))
.borderRadius(8);
}
.width('100%')
.height('100%')
.padding(15)
.backgroundColor($r('app.color.bg_page'));
}
}
5.3 运行效果说明
- Row场景(导航栏):"返回"固定左对齐、"保存"固定右对齐,中间空白由Blank自动填充,适配不同宽度的屏幕;
- Column场景(弹窗):顶部标题、底部操作区固定位置,中间Blank填充剩余高度,确保操作区始终在弹窗底部;
- 多Blank场景:两个 Blank 会均分 Row 剩余宽度,实现 A、B、C 三个元素均匀分布。
六、核心注意事项
aspectRatio:同时设置宽高会导致height失效,建议仅设置单维度+aspectRatio;layoutWeight:仅作用于主轴方向,交叉轴需用百分比/固定尺寸;displayPriority:嵌套容器仅外层优先级生效,被移除时直接从组件节点删除整个容器及内部组件;Blank仅在线性布局中生效,多个Blank共存时,会均分剩余空间。
七、内容总结
- aspectRatio 按宽高比自动计算组件尺寸,优先级高于手动单维度尺寸、低于constraintSize,实现等比缩放;
- 百分比适配基于父容器尺寸(任意方向),layoutWeight 按权重分配主轴剩余空间(仅主轴方向),可通过公式精准计算尺寸;
- displayPriority 数值越小重要性越低,空间不足时小数值组件先从组件节点移除,核心功能建议设大值;
- Blank 组件仅在线性布局中生效,用于填充主轴剩余空间,多个Blank共存时会均分剩余空间,实现元素对齐和自适应布局;
- displayPriority 触发移除时组件会直接从节点删除,无过渡效果。
八、代码仓库
- 工程名称:ColumnRowApplication
- 仓库地址:https://gitee.com/HarmonyOS-UI-Basics/harmony-os-ui-basics.git
九、下节预告
下一节我们将进入弹性布局 Flex 的核心学习,Flex 作为线性布局(Row/Column)的"进阶增强版",是鸿蒙应用多设备适配的核心布局方案。我们将重点掌握:
- Flex 与 Row/Column 的本质区别,理解主轴/交叉轴的灵活配置逻辑;
- flexGrow、flexShrink、flexBasis 三大核心属性的计算规则------对比本节
layoutWeight,掌握 Flex 更精细化的空间分配方式; - 结合
wrap换行规则,解决线性布局"空间不足只能隐藏/压缩"的局限性; - 实战 Flex 对齐体系(justifyContent/alignItems/alignSelf),实现比线性布局更灵活的元素对齐;
通过本节学习,你将能从"固定维度适配"升级为"动态弹性适配",彻底解决不同屏幕尺寸下的布局适配难题。