布局是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.NoWrapbuild() {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)}@BuilderbuildControlPanel() {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)}@BuilderbuildItemControls() {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)}@BuilderbuildStackControls() {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 = truebuild() {Column({ space: 30 }) {Text('Stack实战案例').fontSize(20).fontWeight(FontWeight.Bold)// 案例1:用户头像带状态this.buildUserAvatar()// 案例2:进度条叠加this.buildProgressStack()// 案例3:卡片叠加效果this.buildCardStack()}.width('100%').padding(20)}@BuilderbuildUserAvatar() {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)}@BuilderbuildProgressStack() {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)}@BuilderbuildCardStack() {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)}@BuilderbuildGridControls() {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)}@BuilderbuildGridInfo() {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]}
}
需要参加鸿蒙认证的请点击 鸿蒙认证链接