鸿蒙应用开发:一次开发多端适配与自适应布局实战
·
文章目录
这是一个使用鸿蒙技术开发的本地原生记账应用,非常适合大家用来练手。相关源码已上传至 Github,点击此处查看项目。欢迎大家交流、指正,也欢迎提交 PR。
一、引言
在鸿蒙生态中,应用需要运行在手机、平板、折叠屏、智能手表、车机等多种设备上。这些设备的屏幕尺寸、分辨率、形态差异巨大。为了实现“一次开发,多端部署”的目标,HarmonyOS 提供了强大的自适应布局能力。本文将深入浅出地介绍什么是多端适配,以及如何使用鸿蒙应用开发中的七种自适应布局能力,构建出在不同屏幕上都表现良好的应用界面。
二、什么是“一次开发,多端适配”?
“一次开发,多端适配”并非指编写一套完全不变的代码,而是指开发者通过一套代码工程,在不同的设备上呈现出符合该设备屏幕特性和交互习惯的用户界面。其核心思想是:
- 逻辑归一:使用统一的开发语言(ArkTS)和声明式 UI 框架(ArkUI),编写核心业务逻辑和界面布局。
- 布局自适应:界面元素能够根据容器(通常是屏幕或窗口)的尺寸变化,自动调整自身的大小、位置、排列方式,甚至显示或隐藏。
- 资源差异化:针对不同屏幕密度和尺寸,提供差异化的图片、字体等资源文件,系统会自动加载最合适的资源。
简单来说,开发者只需关注一套代码逻辑,而界面如何在不同屏幕上“优雅地”展示,则由自适应布局机制和资源管理策略共同完成。
三、自适应布局的七种核心能力
HarmonyOS 提供了七种自适应布局能力,它们是实现多端适配的基石。这些能力可以独立使用,也可以灵活组合,以应对复杂的布局需求。
| 能力 | 使用场景 | 实现方式 |
|---|---|---|
| 拉伸能力 | 容器尺寸变化时,将增减的空间全部分配给指定区域。 | Flex 布局的 flexGrow 和 flexShrink 属性 |
| 均分能力 | 容器尺寸变化时,将增减的空间均匀分配给所有空白区域。 | Row/Column/Flex 的 justifyContent 设为 FlexAlign.SpaceEvenly |
| 占比能力 | 子组件宽高按预设比例随容器变化。 | 百分比宽高 或 layoutWeight 属性 |
| 缩放能力 | 子组件宽高按比例变化且保持宽高比不变。 | aspectRatio 属性 |
| 延伸能力 | 子组件按顺序随容器尺寸变化显示或隐藏。 | List 组件 或 Scroll 配合 Row/Column |
| 隐藏能力 | 子组件按预设优先级随容器尺寸变化显示或隐藏。 | displayPriority 属性 |
| 折行能力 | 子组件按顺序随容器尺寸变化折行排列。 | Flex 的 wrap 设为 FlexWrap.Wrap |
四、实战:七种常用自适应布局能力演示
下面,我们通过一个完整的示例,来演示拉伸、均分、占比、缩放、延伸、隐藏和折行这七种最常用的自适应布局能力的具体用法。
4.1 示例代码
interface NavItem {
icon: Resource
label: string
priority: number
}
interface TabItem {
label: string
}
@Entry
@Component
struct AdaptiveLayoutFullDemo {
// 导航项列表,用于演示隐藏能力
@State navItems: NavItem[] = [
{ icon: $r('sys.media.ohos_ic_public_phone'), label: '首页', priority: 5 },
{ icon: $r('sys.media.ohos_ic_public_phone'), label: '分类', priority: 4 },
{ icon: $r('sys.media.ohos_ic_public_phone'), label: '购物车', priority: 3 },
{ icon: $r('sys.media.ohos_ic_public_phone'), label: '消息', priority: 2 },
{ icon: $r('sys.media.ohos_ic_public_phone'), label: '收藏', priority: 1 }
]
// 卡片颜色列表,用于演示折行能力
@State cardColors: string[] = [
'#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A',
'#98D8C8', '#F7DC6F', '#BB8FCE', '#85C1E9'
]
// 标签页列表,用于演示延伸能力
@State tabItems: TabItem[] = [
{ label: '推荐' },
{ label: '关注' },
{ label: '视频' },
{ label: '直播' },
{ label: '购物' },
{ label: '附近' },
{ label: '游戏' }
]
@State selectedIndex: number = 0
build() {
Scroll() {
Column({ space: 16 }) {
// ========== 1. 拉伸能力 ==========
this.StretchSection()
// ========== 2. 均分能力 ==========
this.EvenlySection()
// ========== 3. 占比能力 ==========
this.ProportionSection()
// ========== 4. 缩放能力 ==========
this.ScaleSection()
// ========== 5. 延伸能力 ==========
this.ExtensionSection()
// ========== 6. 隐藏能力 ==========
this.HideSection()
// ========== 7. 折行能力 ==========
this.WrapSection()
}
.padding(16)
.backgroundColor('#F5F5F5')
}
.width('100%')
.height('100%')
}
// ----- 1. 拉伸能力 -----
@Builder
StretchSection() {
Column({ space: 8 }) {
Text('1. 拉伸能力(flexGrow / flexShrink)')
.fontSize(16)
.fontWeight(FontWeight.Medium)
Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center }) {
TextInput({ placeholder: '请输入搜索内容' })
.flexGrow(1)
.flexShrink(1)
.height(40)
.backgroundColor('#FFFFFF')
.borderRadius(20)
.padding({ left: 16, right: 16 })
Button('搜索')
.flexGrow(0)
.flexShrink(0)
.width(72)
.height(40)
.margin({ left: 8 })
.borderRadius(20)
}
.width('100%')
}
}
// ----- 2. 均分能力 -----
@Builder
EvenlySection() {
Column({ space: 8 }) {
Text('2. 均分能力(FlexAlign.SpaceEvenly)')
.fontSize(16)
.fontWeight(FontWeight.Medium)
Row() {
Button('功能A')
.width(80)
.height(36)
.borderRadius(18)
Button('功能B')
.width(80)
.height(36)
.borderRadius(18)
Button('功能C')
.width(80)
.height(36)
.borderRadius(18)
}
.width('100%')
.justifyContent(FlexAlign.SpaceEvenly)
}
}
// ----- 3. 占比能力 -----
@Builder
ProportionSection() {
Column({ space: 8 }) {
Text('3. 占比能力(layoutWeight)')
.fontSize(16)
.fontWeight(FontWeight.Medium)
Row() {
Column() {
Text('主内容区')
.fontSize(14)
.textAlign(TextAlign.Center)
}
.layoutWeight(2)
.height(40)
.backgroundColor('#E3F2FD')
.borderRadius(8)
.justifyContent(FlexAlign.Center)
Column() {
Text('边栏')
.fontSize(14)
.textAlign(TextAlign.Center)
}
.layoutWeight(1)
.height(40)
.backgroundColor('#FFF3E0')
.borderRadius(8)
.justifyContent(FlexAlign.Center)
.margin({ left: 8 })
}
.width('100%')
}
}
// ----- 4. 缩放能力 -----
@Builder
ScaleSection() {
Column({ space: 8 }) {
Text('4. 缩放能力(aspectRatio)')
.fontSize(16)
.fontWeight(FontWeight.Medium)
Column() {
Text('16:9 等比缩放区域 \n @程序员黑豆')
.fontSize(14)
.fontColor('#FFFFFF')
.textAlign(TextAlign.Center)
}
.width('100%')
.aspectRatio(16 / 9)
.backgroundColor('#7986CB')
.borderRadius(8)
.justifyContent(FlexAlign.Center)
}
}
// ----- 5. 延伸能力 -----
@Builder
ExtensionSection() {
Column({ space: 8 }) {
Text('5. 延伸能力(Scroll + Row)')
.fontSize(16)
.fontWeight(FontWeight.Medium)
// 容器宽度充足时所有标签平铺展示;
// 容器宽度不足时末尾标签被裁剪,可通过横向滚动访问。
Scroll() {
Row({ space: 24 }) {
ForEach(this.tabItems, (item: TabItem, index: number) => {
Text(item.label)
.fontSize(16)
.fontColor(this.selectedIndex === index ? '#007DFF' : '#666666')
.fontWeight(this.selectedIndex === index ? FontWeight.Bold : FontWeight.Normal)
.padding({ left: 12, right: 12, top: 8, bottom: 8 })
.borderRadius(16)
.backgroundColor(this.selectedIndex === index ? '#E3F2FD' : '#F5F5F5')
.onClick(() => {
this.selectedIndex = index
})
}, (item: TabItem, index: number) => `${index}_${item.label}`)
}
.padding({ left: 16, right: 16 })
}
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)
.width('100%')
.height(56)
.backgroundColor('#FFFFFF')
.borderRadius(12)
}
}
// ----- 6. 隐藏能力 -----
@Builder
HideSection() {
Column({ space: 8 }) {
Text('6. 隐藏能力(displayPriority)')
.fontSize(16)
.fontWeight(FontWeight.Medium)
Row() {
ForEach(this.navItems, (item: NavItem) => {
Column({ space: 4 }) {
Image(item.icon)
.width(24)
.height(24)
Text(item.label)
.fontSize(12)
}
.width(60)
.height(60)
.justifyContent(FlexAlign.Center)
.displayPriority(item.priority)
}, (item: NavItem) => item.label)
}
.width('100%')
.justifyContent(FlexAlign.SpaceEvenly)
.height(60)
.backgroundColor('#FFFFFF')
.borderRadius(8)
.padding({ top: 8, bottom: 8 })
}
}
// ----- 7. 折行能力 -----
@Builder
WrapSection() {
Column({ space: 8 }) {
Text('7. 折行能力(FlexWrap.Wrap)')
.fontSize(16)
.fontWeight(FontWeight.Medium)
Flex({
direction: FlexDirection.Row,
wrap: FlexWrap.Wrap
}) {
ForEach(this.cardColors, (color: string, index: number) => {
Column() {
Text(`卡片${index + 1}`)
.fontSize(13)
.fontColor('#FFFFFF')
}
.width(72)
.height(72)
.backgroundColor(color)
.borderRadius(8)
.justifyContent(FlexAlign.Center)
.margin(4)
}, (color: string, index: number) => `${index}_${color}`)
}
.width('100%')
}
}
}
4.2 运行效果

4.3 代码说明
上述示例代码演示了七种自适应布局能力的核心用法,下面对关键部分进行说明:
- 数据结构定义:代码开头定义了
NavItem(导航项,含priority优先级字段)和TabItem(标签项)两个接口,为后续演示隐藏能力和延伸能力提供数据模型。 - 状态管理:使用
@State装饰器管理navItems、cardColors、tabItems和selectedIndex等响应式数据,当数据变化时 UI 自动更新。 @Builder构建函数:将每种布局能力封装为独立的@Builder函数(如StretchSection、EvenlySection),使build()方法结构清晰、易于维护。Scroll容器:最外层使用Scroll组件包裹所有布局演示区域,确保当内容超出屏幕高度时页面可纵向滚动,这是多端适配中处理内容溢出的基础手段。- 延伸能力实现:在
ExtensionSection中,使用Scroll包裹Row实现横向滚动标签栏。当容器宽度充足时所有标签平铺展示;宽度不足时末尾标签被裁剪,用户可通过横向滑动访问隐藏的标签。 - 隐藏能力实现:在
HideSection中,通过displayPriority(item.priority)为每个导航项设置显示优先级。当容器空间不足时,priority值最低的项会优先被隐藏,从而实现按需显示。 - 折行能力实现:在
WrapSection中,Flex容器设置wrap: FlexWrap.Wrap,当一行排满卡片后自动换行,无需手动计算换行逻辑。
五、关键注意事项与最佳实践
5.1 各能力的关键注意事项
- 拉伸能力:
flexGrow默认值为 0(不拉伸),flexShrink默认值为 1(会收缩)。如果希望某个子组件尺寸完全固定,需同时设置flexGrow(0)和flexShrink(0)。该能力仅在Flex容器中生效,Row/Column容器下不生效。 - 均分能力:
FlexAlign.SpaceEvenly将剩余空间均匀分配到所有间隔(包括首尾两侧),而FlexAlign.SpaceBetween只将剩余空间分配到子组件之间的间隔(首尾无间距),FlexAlign.SpaceAround则是每个子组件两侧各分配一半间隔。三者效果不同,需根据设计需求选择。 - 占比能力:
layoutWeight与百分比宽高两种方式均可实现占比。layoutWeight的优势在于按整数比例分配,适合需要精确比例控制的场景;百分比方式则更直观。注意同一容器中不要混用两种方式,可能导致布局计算不一致。 - 缩放能力:
aspectRatio接受一个正数表示宽高比(宽/高)。设置后只需指定宽或高其中一个维度,另一个维度会自动计算。适合图片、视频等需要保持比例的场景。 - 隐藏能力:
displayPriority的值越大表示显示优先级越高,当容器空间不足时,低优先级的子组件会优先被隐藏。需要注意被隐藏的组件仍占据布局计算过程,只是不渲染。此能力仅在Row/Flex等线性容器中生效。 - 折行能力:当
Flex设置wrap(FlexWrap.Wrap)后,主轴长度为auto的自适应布局会失效,默认撑满父容器。如果需要精确控制换行行为,可以通过measureText等方式计算子组件实际宽度,动态决定是否换行。
5.2 通用最佳实践
- 结合响应式布局:自适应布局解决的是窗口尺寸在一定范围内连续变化时的适配问题。当窗口尺寸跨越较大阈值(如从手机到平板)需要布局结构发生根本性变化时,应结合响应式布局(如
GridRow/GridCol、媒体查询、断点系统)共同使用。 - 避免固定像素值:优先使用
vp/fp单位或百分比,以确保在不同屏幕密度下正确缩放。 - 多种能力叠加:多种自适应能力可以叠加使用,例如一个
Flex容器中同时使用flexGrow(拉伸)和wrap(折行),但需注意叠加后的布局行为可能较为复杂,建议在真机上充分验证。
六、总结
通过掌握 HarmonyOS 提供的七种自适应布局能力,开发者可以高效地构建出能够适应多种屏幕尺寸的应用界面,真正实现“一次开发,多端适配”的目标。建议开发者在实际项目中多加练习,并结合响应式布局策略,打造出极致的多端用户体验。
更多推荐




所有评论(0)