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 性能优化建议

  1. 避免过度嵌套:尽量减少布局容器的嵌套层级
  2. 使用合适的布局:根据需求选择最简洁的布局方式
  3. 列表优化:对于长列表使用LazyForEach替代ForEach
  4. 组件复用:将重复使用的布局封装为自定义组件

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开发的核心概念。建议您动手实践本文中的案例,特别是综合实战部分,这将帮助您更好地理解和掌握布局系统的各种技巧。

posted @ 2025-12-23 18:07  奇崽  阅读(0)  评论(0)    收藏  举报