HarmonyOS布局艺术:Flex、Stack、Grid等容器组件的使用技巧

布局是UI设计的核心,HarmonyOS提供了强大的容器组件系统。本文将深入讲解Flex、Stack、Grid等核心布局组件的使用技巧和最佳实践。

一、Flex弹性布局详解

1.1 Flex基础布局

@Component
struct FlexBasicExample {
  @State direction: FlexDirection = FlexDirection.Row
  @State justifyContent: FlexAlign = FlexAlign.Start
  @State alignItems: ItemAlign = ItemAlign.Start
  @State wrap: FlexWrap = FlexWrap.NoWrap

  build() {
    Column({ space: 20 }) {
      // 控制面板
      this.buildControlPanel()
      
      // Flex布局演示区域
      Text('Flex布局演示').fontSize(18).fontWeight(FontWeight.Bold)
      
      Flex({
        direction: this.direction,
        justifyContent: this.justifyContent,
        alignItems: this.alignItems,
        wrap: this.wrap
      }) {
        ForEach([1, 2, 3, 4, 5, 6], (item: number) => {
          Text(`Item ${item}`)
            .width(60)
            .height(60)
            .backgroundColor(this.getItemColor(item))
            .fontColor(Color.White)
            .textAlign(TextAlign.Center)
            .fontSize(14)
        })
      }
      .width('100%')
      .height(200)
      .padding(10)
      .backgroundColor('#F8F9FA')
      .border({ width: 1, color: '#E0E0E0' })
    }
    .width('100%')
    .padding(20)
  }

  @Builder
  buildControlPanel() {
    Column({ space: 10 }) {
      Text('Flex布局控制').fontSize(16).fontWeight(FontWeight.Bold)
      
      // 方向控制
      Row({ space: 5 }) {
        Text('方向:').fontSize(14).width(60)
        Button('Row').onClick(() => this.direction = FlexDirection.Row).stateEffect(this.direction === FlexDirection.Row)
        Button('Column').onClick(() => this.direction = FlexDirection.Column).stateEffect(this.direction === FlexDirection.Column)
        Button('RowReverse').onClick(() => this.direction = FlexDirection.RowReverse).stateEffect(this.direction === FlexDirection.RowReverse)
        Button('ColumnReverse').onClick(() => this.direction = FlexDirection.ColumnReverse).stateEffect(this.direction === FlexDirection.ColumnReverse)
      }
      
      // 主轴对齐
      Row({ space: 5 }) {
        Text('主轴:').fontSize(14).width(60)
        Button('Start').onClick(() => this.justifyContent = FlexAlign.Start).stateEffect(this.justifyContent === FlexAlign.Start)
        Button('Center').onClick(() => this.justifyContent = FlexAlign.Center).stateEffect(this.justifyContent === FlexAlign.Center)
        Button('End').onClick(() => this.justifyContent = FlexAlign.End).stateEffect(this.justifyContent === FlexAlign.End)
        Button('SpaceBetween').onClick(() => this.justifyContent = FlexAlign.SpaceBetween).stateEffect(this.justifyContent === FlexAlign.SpaceBetween)
      }
      
      // 交叉轴对齐
      Row({ space: 5 }) {
        Text('交叉轴:').fontSize(14).width(60)
        Button('Start').onClick(() => this.alignItems = ItemAlign.Start).stateEffect(this.alignItems === ItemAlign.Start)
        Button('Center').onClick(() => this.alignItems = ItemAlign.Center).stateEffect(this.alignItems === ItemAlign.Center)
        Button('End').onClick(() => this.alignItems = ItemAlign.End).stateEffect(this.alignItems === ItemAlign.End)
        Button('Stretch').onClick(() => this.alignItems = ItemAlign.Stretch).stateEffect(this.alignItems === ItemAlign.Stretch)
      }
    }
    .width('100%')
    .padding(15)
    .backgroundColor('#FFFFFF')
    .borderRadius(8)
  }

  getItemColor(index: number): string {
    const colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FECA57', '#FF9FF3']
    return colors[index % colors.length]
  }
}

1.2 Flex高级特性

@Component
struct FlexAdvancedExample {
  @State items: any[] = [
    { id: 1, name: 'Item1', flexGrow: 1, flexShrink: 1, alignSelf: ItemAlign.Auto },
    { id: 2, name: 'Item2', flexGrow: 2, flexShrink: 1, alignSelf: ItemAlign.Center },
    { id: 3, name: 'Item3', flexGrow: 1, flexShrink: 2, alignSelf: ItemAlign.End }
  ]

  build() {
    Column({ space: 20 }) {
      Text('Flex高级特性').fontSize(20).fontWeight(FontWeight.Bold)
      
      // Flex容器
      Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceAround }) {
        ForEach(this.items, (item: any) => {
          Text(item.name)
            .flexGrow(item.flexGrow)
            .flexShrink(item.flexShrink)
            .alignSelf(item.alignSelf)
            .minWidth(60)
            .height(80)
            .backgroundColor(this.getItemColor(item.id))
            .fontColor(Color.White)
            .textAlign(TextAlign.Center)
            .padding(10)
        })
      }
      .width('100%')
      .height(120)
      .padding(10)
      .backgroundColor('#F5F5F5')
      
      // 控制面板
      this.buildItemControls()
    }
    .width('100%')
    .padding(20)
  }

  @Builder
  buildItemControls() {
    Column({ space: 15 }) {
      ForEach(this.items, (item: any, index: number) => {
        Column({ space: 8 }) {
          Text(`${item.name} 控制`).fontSize(14).fontWeight(FontWeight.Medium)
          
          Row({ space: 10 }) {
            Text(`flexGrow: ${item.flexGrow}`).fontSize(12).width(80)
            Slider({
              value: item.flexGrow,
              min: 0,
              max: 3,
              step: 1
            }).onChange((value: number) => {
              item.flexGrow = value
            }).layoutWeight(1)
          }
          
          Row({ space: 10 }) {
            Text(`flexShrink: ${item.flexShrink}`).fontSize(12).width(80)
            Slider({
              value: item.flexShrink,
              min: 0,
              max: 3,
              step: 1
            }).onChange((value: number) => {
              item.flexShrink = value
            }).layoutWeight(1)
          }
          
          Row({ space: 5 }) {
            Text('alignSelf:').fontSize(12).width(80)
            Button('Auto').onClick(() => item.alignSelf = ItemAlign.Auto).stateEffect(item.alignSelf === ItemAlign.Auto)
            Button('Start').onClick(() => item.alignSelf = ItemAlign.Start).stateEffect(item.alignSelf === ItemAlign.Start)
            Button('Center').onClick(() => item.alignSelf = ItemAlign.Center).stateEffect(item.alignSelf === ItemAlign.Center)
            Button('End').onClick(() => item.alignSelf = ItemAlign.End).stateEffect(item.alignSelf === ItemAlign.End)
          }
        }
        .width('100%')
        .padding(10)
        .backgroundColor('#FFFFFF')
        .borderRadius(6)
      })
    }
  }

  getItemColor(id: number): string {
    const colors = ['#FF6B6B', '#4ECDC4', '#45B7D1']
    return colors[(id - 1) % colors.length]
  }
}

二、Stack层叠布局

2.1 Stack基础应用

@Component
struct StackBasicExample {
  @State alignment: Alignment = Alignment.Center
  @State stackItems: any[] = [
    { id: 1, content: '底层', width: 200, height: 150, color: '#FF6B6B', zIndex: 1 },
    { id: 2, content: '中层', width: 160, height: 120, color: '#4ECDC4', zIndex: 2 },
    { id: 3, content: '顶层', width: 120, height: 90, color: '#45B7D1', zIndex: 3 }
  ]

  build() {
    Column({ space: 20 }) {
      Text('Stack层叠布局').fontSize(20).fontWeight(FontWeight.Bold)
      
      // Stack容器
      Stack({ alignContent: this.alignment }) {
        ForEach(this.stackItems, (item: any) => {
          Text(item.content)
            .width(item.width)
            .height(item.height)
            .backgroundColor(item.color)
            .fontColor(Color.White)
            .textAlign(TextAlign.Center)
            .fontSize(16)
            .zIndex(item.zIndex)
        })
      }
      .width(300)
      .height(250)
      .backgroundColor('#F8F9FA')
      .padding(10)
      
      // 控制面板
      this.buildStackControls()
    }
    .width('100%')
    .padding(20)
    .alignItems(HorizontalAlign.Center)
  }

  @Builder
  buildStackControls() {
    Column({ space: 15 }) {
      Text('对齐方式控制').fontSize(16).fontWeight(FontWeight.Medium)
      
      // 对齐方式按钮组
      Grid() {
        GridItem() {
          Button('TopStart').onClick(() => this.alignment = Alignment.TopStart)
            .stateEffect(this.alignment === Alignment.TopStart)
        }
        GridItem() {
          Button('Top').onClick(() => this.alignment = Alignment.Top)
            .stateEffect(this.alignment === Alignment.Top)
        }
        GridItem() {
          Button('TopEnd').onClick(() => this.alignment = Alignment.TopEnd)
            .stateEffect(this.alignment === Alignment.TopEnd)
        }
        
        GridItem() {
          Button('Start').onClick(() => this.alignment = Alignment.Start)
            .stateEffect(this.alignment === Alignment.Start)
        }
        GridItem() {
          Button('Center').onClick(() => this.alignment = Alignment.Center)
            .stateEffect(this.alignment === Alignment.Center)
        }
        GridItem() {
          Button('End').onClick(() => this.alignment = Alignment.End)
            .stateEffect(this.alignment === Alignment.End)
        }
        
        GridItem() {
          Button('BottomStart').onClick(() => this.alignment = Alignment.BottomStart)
            .stateEffect(this.alignment === Alignment.BottomStart)
        }
        GridItem() {
          Button('Bottom').onClick(() => this.alignment = Alignment.Bottom)
            .stateEffect(this.alignment === Alignment.Bottom)
        }
        GridItem() {
          Button('BottomEnd').onClick(() => this.alignment = Alignment.BottomEnd)
            .stateEffect(this.alignment === Alignment.BottomEnd)
        }
      }
      .columnsTemplate('1fr 1fr 1fr')
      .rowsTemplate('1fr 1fr 1fr')
      .columnsGap(5)
      .rowsGap(5)
      .height(120)
      
      // zIndex控制
      Text('层级控制').fontSize(16).fontWeight(FontWeight.Medium)
      ForEach(this.stackItems, (item: any) => {
        Row({ space: 10 }) {
          Text(`${item.content} zIndex:`).fontSize(14).width(100)
          Slider({
            value: item.zIndex,
            min: 1,
            max: 5,
            step: 1
          }).onChange((value: number) => {
            item.zIndex = value
          }).layoutWeight(1)
          Text(item.zIndex.toString()).fontSize(14).width(30)
        }
      })
    }
    .width('100%')
    .maxWidth(400)
    .padding(15)
    .backgroundColor('#FFFFFF')
    .borderRadius(8)
  }
}

2.2 Stack实战案例

@Component
struct StackPracticalExample {
  @State progress: number = 60
  @State badgeCount: number = 3
  @State isOnline: boolean = true

  build() {
    Column({ space: 30 }) {
      Text('Stack实战案例').fontSize(20).fontWeight(FontWeight.Bold)
      
      // 案例1:用户头像带状态
      this.buildUserAvatar()
      
      // 案例2:进度条叠加
      this.buildProgressStack()
      
      // 案例3:卡片叠加效果
      this.buildCardStack()
    }
    .width('100%')
    .padding(20)
  }

  @Builder
  buildUserAvatar() {
    Column({ space: 10 }) {
      Text('1. 用户头像带状态').fontSize(16).fontWeight(FontWeight.Medium)
      
      Stack({ alignContent: Alignment.BottomEnd }) {
        // 头像
        Image($r('app.media.user_avatar'))
          .width(80)
          .height(80)
          .borderRadius(40)
          .border({ width: 3, color: Color.White })
        
        // 在线状态
        Circle({ width: 16, height: 16 })
          .fill(this.isOnline ? '#34C759' : '#FF3B30')
          .stroke({ width: 2, color: Color.White })
          .offset({ x: -5, y: -5 })
      }
      .onClick(() => {
        this.isOnline = !this.isOnline
      })
    }
    .alignItems(HorizontalAlign.Center)
  }

  @Builder
  buildProgressStack() {
    Column({ space: 10 }) {
      Text('2. 进度条叠加效果').fontSize(16).fontWeight(FontWeight.Medium)
      
      Stack() {
        // 背景条
        Row()
          .width(200)
          .height(12)
          .backgroundColor('#E5E5EA')
          .borderRadius(6)
        
        // 进度条
        Row()
          .width(`${this.progress}%`)
          .height(12)
          .backgroundColor('#007DFF')
          .borderRadius(6)
        
        // 进度文本
        Text(`${this.progress}%`)
          .fontSize(10)
          .fontColor(Color.White)
          .fontWeight(FontWeight.Bold)
          .offset({ x: this.progress * 2 - 10, y: 0 })
      }
      .width(200)
      .height(20)
      
      Slider({
        value: this.progress,
        min: 0,
        max: 100,
        step: 1
      })
      .width(200)
      .onChange((value: number) => {
        this.progress = value
      })
    }
    .alignItems(HorizontalAlign.Center)
  }

  @Builder
  buildCardStack() {
    Column({ space: 10 }) {
      Text('3. 卡片叠加效果').fontSize(16).fontWeight(FontWeight.Medium)
      
      Stack({ alignContent: Alignment.Center }) {
        // 底层卡片
        Column({ space: 10 }) {
          Text('卡片标题')
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
          Text('这是卡片内容描述信息')
            .fontSize(12)
            .fontColor('#666')
        }
        .width(180)
        .height(120)
        .padding(15)
        .backgroundColor('#FFECB3')
        .borderRadius(12)
        .shadow({ radius: 8, color: '#40000000', offsetX: 0, offsetY: 4 })
        .offset({ x: -5, y: -5 })
        .zIndex(1)

        // 中层卡片
        Column({ space: 10 }) {
          Text('卡片标题')
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
          Text('这是卡片内容描述信息')
            .fontSize(12)
            .fontColor('#666')
        }
        .width(180)
        .height(120)
        .padding(15)
        .backgroundColor('#C8E6C9')
        .borderRadius(12)
        .shadow({ radius: 8, color: '#40000000', offsetX: 0, offsetY: 4 })
        .offset({ x: 0, y: 0 })
        .zIndex(2)

        // 顶层卡片
        Column({ space: 10 }) {
          Text('卡片标题')
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
          Text('这是卡片内容描述信息')
            .fontSize(12)
            .fontColor('#666')
        }
        .width(180)
        .height(120)
        .padding(15)
        .backgroundColor('#BBDEFB')
        .borderRadius(12)
        .shadow({ radius: 12, color: '#40000000', offsetX: 0, offsetY: 6 })
        .offset({ x: 5, y: 5 })
        .zIndex(3)
      }
      .height(140)
    }
    .alignItems(HorizontalAlign.Center)
  }
}

三、Grid网格布局

3.1 Grid基础布局

@Component
struct GridBasicExample {
  @State columnsTemplate: string = '1fr 1fr 1fr'
  @State rowsTemplate: string = '1fr 1fr'
  @State columnsGap: number = 10
  @State rowsGap: number = 10
  @State gridItems: number[] = [1, 2, 3, 4, 5, 6]

  build() {
    Column({ space: 20 }) {
      Text('Grid网格布局').fontSize(20).fontWeight(FontWeight.Bold)
      
      // Grid容器
      Grid() {
        ForEach(this.gridItems, (item: number) => {
          GridItem() {
            Text(`Item ${item}`)
              .width('100%')
              .height('100%')
              .backgroundColor(this.getItemColor(item))
              .fontColor(Color.White)
              .textAlign(TextAlign.Center)
              .fontSize(14)
          }
        })
      }
      .columnsTemplate(this.columnsTemplate)
      .rowsTemplate(this.rowsTemplate)
      .columnsGap(this.columnsGap)
      .rowsGap(this.rowsGap)
      .width('100%')
      .height(200)
      .padding(10)
      .backgroundColor('#F8F9FA')
      
      // 控制面板
      this.buildGridControls()
    }
    .width('100%')
    .padding(20)
  }

  @Builder
  buildGridControls() {
    Column({ space: 15 }) {
      Text('Grid布局控制').fontSize(16).fontWeight(FontWeight.Medium)
      
      // 列模板控制
      Row({ space: 10 }) {
        Text('列模板:').fontSize(14).width(80)
        TextInput({ placeholder: '例如: 1fr 1fr 1fr' })
          .onChange((value: string) => {
            this.columnsTemplate = value
          })
          .layoutWeight(1)
      }
      
      // 行模板控制
      Row({ space: 10 }) {
        Text('行模板:').fontSize(14).width(80)
        TextInput({ placeholder: '例如: 1fr 1fr' })
          .onChange((value: string) => {
            this.rowsTemplate = value
          })
          .layoutWeight(1)
      }
      
      // 间距控制
      Row({ space: 10 }) {
        Text('列间距:').fontSize(14).width(80)
        Slider({
          value: this.columnsGap,
          min: 0,
          max: 30,
          step: 1
        }).onChange((value: number) => {
          this.columnsGap = value
        }).layoutWeight(1)
        Text(this.columnsGap.toString()).fontSize(14).width(30)
      }
      
      Row({ space: 10 }) {
        Text('行间距:').fontSize(14).width(80)
        Slider({
          value: this.rowsGap,
          min: 0,
          max: 30,
          step: 1
        }).onChange((value: number) => {
          this.rowsGap = value
        }).layoutWeight(1)
        Text(this.rowsGap.toString()).fontSize(14).width(30)
      }
      
      // 预设模板
      Text('预设模板:').fontSize(14)
      Row({ space: 5 }) {
        Button('2x2').onClick(() => {
          this.columnsTemplate = '1fr 1fr'
          this.rowsTemplate = '1fr 1fr'
        })
        Button('3x2').onClick(() => {
          this.columnsTemplate = '1fr 1fr 1fr'
          this.rowsTemplate = '1fr 1fr'
        })
        Button('4x3').onClick(() => {
          this.columnsTemplate = '1fr 1fr 1fr 1fr'
          this.rowsTemplate = '1fr 1fr 1fr'
        })
        Button('不规则').onClick(() => {
          this.columnsTemplate = '2fr 1fr 1fr'
          this.rowsTemplate = '100px 1fr'
        })
      }
    }
    .width('100%')
    .padding(15)
    .backgroundColor('#FFFFFF')
    .borderRadius(8)
  }

  getItemColor(index: number): string {
    const colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FECA57', '#FF9FF3']
    return colors[index % colors.length]
  }
}

3.2 Grid高级应用

@Component
struct GridAdvancedExample {
  @State gridData: any[] = [
    { id: 1, name: '音乐', icon: '🎵', span: { column: 1, row: 1 } },
    { id: 2, name: '视频', icon: '🎬', span: { column: 1, row: 1 } },
    { id: 3, name: '游戏', icon: '🎮', span: { column: 2, row: 1 } },
    { id: 4, name: '阅读', icon: '📚', span: { column: 1, row: 2 } },
    { id: 5, name: '社交', icon: '👥', span: { column: 1, row: 1 } },
    { id: 6, name: '工具', icon: '🛠️', span: { column: 1, row: 1 } }
  ]

  build() {
    Column({ space: 20 }) {
      Text('Grid高级应用 - 不规则网格').fontSize(20).fontWeight(FontWeight.Bold)
      
      // 不规则Grid布局
      Grid() {
        ForEach(this.gridData, (item: any) => {
          GridItem(item.span) {
            Column({ space: 8 }) {
              Text(item.icon)
                .fontSize(24)
              Text(item.name)
                .fontSize(12)
                .fontColor(Color.White)
            }
            .width('100%')
            .height('100%')
            .justifyContent(FlexAlign.Center)
            .alignItems(HorizontalAlign.Center)
            .backgroundColor(this.getCategoryColor(item.id))
            .borderRadius(8)
            .onClick(() => {
              console.log(`点击了: ${item.name}`)
            })
          }
        })
      }
      .columnsTemplate('1fr 1fr 1fr')
      .rowsTemplate('80px 80px 80px')
      .columnsGap(10)
      .rowsGap(10)
      .width('100%')
      .height(280)
      .padding(10)
      .backgroundColor('#F8F9FA')
      
      // 网格项详细信息
      this.buildGridInfo()
    }
    .width('100%')
    .padding(20)
  }

  @Builder
  buildGridInfo() {
    Column({ space: 10 }) {
      Text('网格项详细信息').fontSize(16).fontWeight(FontWeight.Medium)
      
      Grid() {
        ForEach(this.gridData, (item: any) => {
          GridItem() {
            Row({ space: 8 }) {
              Text(item.icon).fontSize(16)
              Column({ space: 2 }) {
                Text(item.name).fontSize(12).fontWeight(FontWeight.Medium)
                Text(`跨${item.span.column}列${item.span.row}行`).fontSize(10).fontColor('#666')
              }
            }
            .width('100%')
            .padding(8)
            .backgroundColor('#FFFFFF')
            .borderRadius(6)
          }
        })
      }
      .columnsTemplate('1fr 1fr')
      .rowsTemplate('1fr 1fr 1fr')
      .columnsGap(8)
      .rowsGap(8)
      .width('100%')
      .height(120)
    }
  }

  getCategoryColor(id: number): string {
    const colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FECA57', '#FF9FF3']
    return colors[(id - 1) % colors.length]
  }
}

需要参加鸿蒙认证的请点击 鸿蒙认证链接

posted @ 2025-10-30 10:54  ifeng918  阅读(11)  评论(0)    收藏  举报