HarmonyOS 5开发从入门到精通(三):布局系统与组件样式
HarmonyOS 5开发从入门到精通(三):布局系统与组件样式
一、五大核心布局容器
在HarmonyOS应用开发中,布局容器是构建界面的骨架。ArkUI提供了五种核心布局容器,每种都有其特定的应用场景。
1.1 Column垂直布局
Column是最基础的布局容器之一,用于将子组件垂直排列。
@Entry
@Component
struct ColumnExample {
build() {
Column({ space: 20 }) {
Text('标题')
.fontSize(24)
.fontWeight(FontWeight.Bold)
Text('这是一个使用Column布局的示例')
.fontSize(16)
.fontColor('#666')
Button('确认按钮')
.width(200)
.height(40)
.margin({ top: 30 })
}
.width('100%')
.height('100%')
.padding(16)
.backgroundColor('#F5F5F5')
}
}
关键特性:
space:设置子组件之间的垂直间距- 默认垂直方向排列,水平居左对齐
- 适用于设置页、列表、表单等垂直结构
1.2 Row水平布局
Row容器用于水平排列子组件,适合创建水平导航栏或图标与文本的组合。
@Entry
@Component
struct RowExample {
build() {
Row() {
Image($r('app.media.user_avatar'))
.width(40)
.height(40)
.borderRadius(20)
.margin({ right: 12 })
Column() {
Text('张三')
.fontSize(18)
.fontWeight(FontWeight.Medium)
Text('高级工程师')
.fontSize(14)
.fontColor('#999')
}
.alignItems(HorizontalAlign.Start)
Blank() // 空白填充,将内容推到两侧
Button('关注')
.width(80)
.height(32)
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
}
}
应用场景:标题栏、用户信息行、水平选项卡等
1.3 Stack层叠布局
Stack允许子组件层叠显示,非常适合创建悬浮按钮、角标、蒙层等效果。
@Entry
@Component
struct StackExample {
build() {
Stack({ alignContent: Alignment.BottomEnd }) {
// 底层内容
Column() {
Text('主要内容区域')
.fontSize(20)
.margin({ bottom: 20 })
Image($r('app.media.product_image'))
.width(200)
.height(200)
.objectFit(ImageFit.Cover)
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
// 悬浮按钮
Button('+')
.width(56)
.height(56)
.fontSize(24)
.fontWeight(FontWeight.Bold)
.backgroundColor('#007DFF')
.fontColor(Color.White)
.borderRadius(28)
.margin(20)
.shadow({ radius: 8, color: '#40007DFF', offsetX: 0, offsetY: 4 })
}
.width('100%')
.height('100%')
.backgroundColor('#F0F0F0')
}
}
1.4 Flex弹性布局
Flex布局是Row和Column的超集,提供了更强大的对齐和空间分配能力。
@Entry
@Component
struct FlexExample {
@State tags: string[] = ['科技', '体育', '娱乐', '财经', '健康', '教育', '旅游', '美食']
build() {
Column() {
Text('标签云')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 16 })
// Flex自动换行布局
Flex({ direction: FlexDirection.Row, wrap: FlexWrap.Wrap }) {
ForEach(this.tags, (tag) => {
Text(tag)
.fontSize(14)
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.backgroundColor('#F0F0F0')
.borderRadius(16)
.margin({ right: 8, bottom: 8 })
})
}
.width('100%')
.justifyContent(FlexAlign.Start)
// 空间分配示例
Flex({ direction: FlexDirection.Row }) {
Text('主要内容')
.flexGrow(1) // 占据剩余空间
.height(60)
.backgroundColor('#E3F2FD')
.textAlign(TextAlign.Center)
.padding(12)
Button('操作')
.width(100)
.height(60)
.backgroundColor('#2196F3')
.fontColor(Color.White)
}
.width('100%')
.margin({ top: 20 })
}
.width('100%')
.height('100%')
.padding(16)
.backgroundColor(Color.White)
}
}
Flex核心属性:
flexDirection:主轴方向(Row/Column)justifyContent:主轴对齐方式alignItems:交叉轴对齐方式flexGrow:子元素弹性扩展比例wrap:是否自动换行
1.5 Grid网格布局
Grid适合创建规整的宫格布局,如应用菜单、图片墙、商品展示等。
@Entry
@Component
struct GridExample {
private menus = [
{ name: '首页', icon: $r('app.media.home') },
{ name: '分类', icon: $r('app.media.category') },
{ name: '购物车', icon: $r('app.media.cart') },
{ name: '我的', icon: $r('app.media.profile') },
{ name: '设置', icon: $r('app.media.settings') },
{ name: '帮助', icon: $r('app.media.help') }
]
build() {
Column() {
Text('功能菜单')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 20 })
Grid() {
ForEach(this.menus, (item) => {
GridItem() {
Column() {
Image(item.icon)
.width(40)
.height(40)
.margin({ bottom: 8 })
Text(item.name)
.fontSize(14)
.fontColor('#333')
}
.width('100%')
.height(120)
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.backgroundColor(Color.White)
.borderRadius(12)
.shadow({ radius: 2, color: '#10000000', offsetX: 0, offsetY: 1 })
}
})
}
.columnsTemplate('1fr 1fr 1fr') // 三列等宽
.rowsTemplate('120px 120px') // 两行固定高度
.columnsGap(12)
.rowsGap(12)
.width('100%')
}
.width('100%')
.height('100%')
.padding(16)
.backgroundColor('#F5F5F5')
}
}
二、样式与外观设计
2.1 尺寸单位与相对布局
在HarmonyOS中,推荐使用相对单位来确保界面在不同设备上的适配性。
| 单位类型 | 说明 | 使用场景 |
|---|---|---|
| vp (Virtual Pixel) | 虚拟像素,根据屏幕密度自动缩放 | 推荐用于组件尺寸 |
| fp (Font Pixel) | 字体像素,支持用户字体大小设置 | 必须用于字体大小 |
| % (百分比) | 相对于父容器的比例 | 宽度、高度、边距 |
| px | 物理像素 | 尽量避免使用 |
Column() {
// 使用百分比设置宽度
Text('自适应宽度')
.width('90%') // 父容器宽度的90%
.height(60)
.backgroundColor('#E3F2FD')
.textAlign(TextAlign.Center)
.margin({ bottom: 10 })
// 使用vp单位
Text('固定宽度')
.width(200)
.height(60)
.backgroundColor('#FFECB3')
.textAlign(TextAlign.Center)
.margin({ bottom: 10 })
// 使用layoutWeight分配剩余空间
Row() {
Text('左侧')
.layoutWeight(1) // 占据1份空间
.height(60)
.backgroundColor('#C8E6C9')
.textAlign(TextAlign.Center)
Text('右侧')
.layoutWeight(2) // 占据2份空间(是左侧的2倍宽)
.height(60)
.backgroundColor('#FFCDD2')
.textAlign(TextAlign.Center)
}
.width('100%')
.height(80)
}
.width('100%')
2.2 颜色与背景样式
HarmonyOS提供了丰富的颜色和背景样式设置方法。
Column() {
// 使用预定义颜色
Text('预定义颜色')
.fontSize(18)
.fontColor(Color.Blue)
.margin({ bottom: 10 })
// 使用十六进制颜色值
Text('十六进制颜色')
.fontSize(18)
.fontColor('#FF5722')
.margin({ bottom: 10 })
// 渐变背景
Text('渐变背景')
.fontSize(18)
.fontColor(Color.White)
.width(200)
.height(60)
.textAlign(TextAlign.Center)
.backgroundImage(
'linear-gradient(135deg, #667eea 0%, #764ba2 100%)'
)
.borderRadius(8)
.margin({ bottom: 10 })
// 边框样式
Text('边框样式')
.fontSize(18)
.width(200)
.height(60)
.textAlign(TextAlign.Center)
.backgroundColor(Color.White)
.border({ width: 2, color: '#2196F3', style: BorderStyle.Solid })
.borderRadius(12)
}
.width('100%')
.height('100%')
.padding(16)
.backgroundColor('#F5F5F5')
2.3 阴影与视觉效果
Column() {
// 基础阴影
Text('基础阴影效果')
.fontSize(18)
.width(200)
.height(60)
.textAlign(TextAlign.Center)
.backgroundColor(Color.White)
.borderRadius(8)
.shadow({ radius: 4, color: '#30000000', offsetX: 0, offsetY: 2 })
.margin({ bottom: 20 })
// 强烈阴影
Text('强烈阴影效果')
.fontSize(18)
.width(200)
.height(60)
.textAlign(TextAlign.Center)
.backgroundColor(Color.White)
.borderRadius(8)
.shadow({ radius: 12, color: '#40000000', offsetX: 0, offsetY: 6 })
.margin({ bottom: 20 })
// 模糊背景
Text('毛玻璃效果')
.fontSize(18)
.fontColor(Color.White)
.width(200)
.height(60)
.textAlign(TextAlign.Center)
.backdropBlur(10) // 背景模糊
.backgroundColor('#80FFFFFF') // 半透明背景
.borderRadius(8)
}
.width('100%')
.height('100%')
.padding(16)
.backgroundImage('linear-gradient(45deg, #667eea, #764ba2)')
三、响应式布局技巧
3.1 断点系统与媒体查询
HarmonyOS 5提供了内置的断点系统,可以根据窗口宽度范围应用不同的布局。
import mediaquery from '@ohos.mediaquery';
@Entry
@Component
struct ResponsiveLayout {
@State currentBreakpoint: string = 'md';
aboutToAppear() {
// 监听窗口变化
this.setupBreakpointListener();
}
private setupBreakpointListener() {
const breakpointListener = mediaquery.matchMediaSync(
'(320vp <= width < 600vp)',
(result: mediaquery.MediaQueryResult) => {
if (result.matches) {
this.currentBreakpoint = 'md';
}
}
);
}
@Builder
buildMobileLayout() {
Column() {
Text('移动端布局')
.fontSize(20)
.fontWeight(FontWeight.Bold)
List() {
ListItem() {
// 移动端列表项
}
}
}
}
@Builder
buildDesktopLayout() {
GridRow() {
GridCol({ span: 6 }) {
// 桌面端网格项
}
}
}
build() {
Column() {
if (this.currentBreakpoint === 'sm' || this.currentBreakpoint === 'md') {
this.buildMobileLayout();
} else {
this.buildDesktopLayout();
}
}
}
}
3.2 自适应栅格系统
利用GridRow和GridCol实现响应式栅格布局。
@Entry
@Component
struct ResponsiveGrid {
build() {
Column() {
GridRow({ columns: 12, gutter: { x: 12, y: 12 } }) {
// 小屏:12列全宽,中屏:6列,大屏:4列
GridCol({ span: { sm: 12, md: 6, lg: 4 } }) {
this.buildCard('卡片1')
}
GridCol({ span: { sm: 12, md: 6, lg: 4 } }) {
this.buildCard('卡片2')
}
GridCol({ span: { sm: 12, md: 6, lg: 4 } }) {
this.buildCard('卡片3')
}
}
.width('100%')
.layoutWeight(1)
}
.padding(12)
}
@Builder
buildCard(title: string) {
Column() {
Text(title)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 8 })
Text('这是卡片内容描述')
.fontSize(14)
.fontColor('#666')
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(12)
.shadow({ radius: 2, color: '#10000000', offsetX: 0, offsetY: 1 })
}
}
四、综合实战:个人名片组件
现在让我们综合运用所学知识,创建一个响应式的个人名片组件。
@Entry
@Component
struct ProfileCard {
@State userInfo = {
name: '张三',
title: '高级全栈工程师',
company: '华为技术有限公司',
avatar: $r('app.media.user_avatar'),
skills: ['JavaScript', 'TypeScript', 'HarmonyOS', 'ArkUI', 'Node.js'],
stats: { posts: 128, followers: 2846, following: 562 }
}
build() {
Column() {
// 顶部背景
Stack({ alignContent: Alignment.TopStart }) {
// 背景横幅
Column() {
Blank()
.height(120)
.backgroundImage('linear-gradient(45deg, #667eea, #764ba2)')
}
.width('100%')
// 头像区域
Column() {
Image(this.userInfo.avatar)
.width(80)
.height(80)
.borderRadius(40)
.border({ width: 4, color: Color.White })
.shadow({ radius: 8, color: '#40000000', offsetX: 0, offsetY: 2 })
}
.width('100%')
.height(120)
.justifyContent(FlexAlign.End)
.alignItems(HorizontalAlign.Center)
.margin({ bottom: 40 })
}
.width('100%')
.height(160)
// 用户信息
Column() {
Text(this.userInfo.name)
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 4 })
Text(this.userInfo.title)
.fontSize(16)
.fontColor('#666')
.margin({ bottom: 2 })
Text(this.userInfo.company)
.fontSize(14)
.fontColor('#999')
.margin({ bottom: 20 })
// 技能标签
Flex({ direction: FlexDirection.Row, wrap: FlexWrap.Wrap }) {
ForEach(this.userInfo.skills, (skill) => {
Text(skill)
.fontSize(12)
.padding({ left: 8, right: 8, top: 4, bottom: 4 })
.backgroundColor('#F0F5FF')
.fontColor('#1890FF')
.borderRadius(12)
.margin({ right: 8, bottom: 8 })
})
}
.justifyContent(FlexAlign.Center)
.margin({ bottom: 20 })
// 数据统计
Divider()
.strokeWidth(1)
.color('#F0F0F0')
.margin({ bottom: 16 })
Row() {
this.buildStatItem('动态', this.userInfo.stats.posts.toString())
this.buildStatItem('粉丝', this.userInfo.stats.followers.toString())
this.buildStatItem('关注', this.userInfo.stats.following.toString())
}
.width('100%')
.justifyContent(FlexAlign.SpaceAround)
}
.padding({ left: 16, right: 16, bottom: 16 })
// 操作按钮
Row() {
Button('关注')
.layoutWeight(1)
.height(40)
.backgroundColor('#1890FF')
.fontColor(Color.White)
.margin({ right: 8 })
Button('发消息')
.layoutWeight(1)
.height(40)
.backgroundColor(Color.White)
.fontColor('#1890FF')
.border({ width: 1, color: '#1890FF' })
}
.width('100%')
.padding(16)
}
.width('100%')
.height('100%')
.backgroundColor(Color.White)
.borderRadius(16)
.shadow({ radius: 16, color: '#20000000', offsetX: 0, offsetY: 8 })
.margin(16)
}
@Builder
buildStatItem(label: string, value: string) {
Column() {
Text(value)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#333')
Text(label)
.fontSize(12)
.fontColor('#999')
}
.alignItems(HorizontalAlign.Center)
}
}
五、布局最佳实践
5.1 性能优化建议
- 避免过度嵌套:尽量减少布局容器的嵌套层级
- 使用合适的布局:根据需求选择最简洁的布局方式
- 列表优化:对于长列表使用
LazyForEach替代ForEach - 组件复用:将重复使用的布局封装为自定义组件
5.2 代码组织技巧
// 好的实践:使用@Builder分离布局逻辑
@Component
struct WellOrganizedComponent {
@Builder
buildHeader() {
Row() {
// 头部内容
}
}
@Builder
buildContent() {
Column() {
// 主体内容
}
}
@Builder
buildFooter() {
Row() {
// 底部内容
}
}
build() {
Column() {
this.buildHeader()
this.buildContent()
this.buildFooter()
}
}
}
六、总结
通过本篇教程,您已经掌握了:
✅ 五大核心布局容器的使用场景和技巧
✅ 样式与外观设计的完整知识体系
✅ 响应式布局的实现方法和断点系统
✅ 综合实战案例的开发经验
✅ 性能优化和代码组织的最佳实践
关键知识点回顾:
- Column和Row是基础线性布局,Stack用于层叠,Flex提供弹性能力,Grid适合宫格
- 使用vp、fp、百分比等相对单位实现适配
- 通过媒体查询和栅格系统实现响应式布局
- 合理组织代码结构,提高可维护性
下一篇我们将深入探讨状态管理与数据绑定,这是声明式UI开发的核心概念。建议您动手实践本文中的案例,特别是综合实战部分,这将帮助您更好地理解和掌握布局系统的各种技巧。
浙公网安备 33010602011771号