项目演示


在这里插入图片描述
在这里插入图片描述

鸿蒙原生ArkTS布局方式之百分比Width/Height自适应布局深度指南

第1章 鸿蒙ArkTS布局体系概述

1.1 鸿蒙HarmonyOS NEXT开发环境

HarmonyOS NEXT是华为公司推出的新一代智能终端操作系统,采用全新的纯血鸿蒙内核,为开发者提供了更加统一、高效的应用开发体验。ArkTS作为HarmonyOS的原生开发语言,结合了TypeScript的类型安全特性和声明式UI的简洁语法,成为鸿蒙应用开发的首选语言。

在HarmonyOS NEXT中,应用开发基于Stage模型,通过@Entry装饰器标记页面入口,使用@Component装饰器定义自定义组件。这种声明式的开发方式使得UI布局和业务逻辑能够清晰分离,提高了代码的可维护性和可读性。

1.2 ArkUI布局系统架构

ArkUI是HarmonyOS的UI框架,提供了丰富的布局能力。其布局系统主要包括以下几种核心布局方式:

  1. Flex布局:基于弹性盒子模型,支持水平和垂直方向的灵活排列,是最常用的布局方式
  2. Column布局:垂直方向的线性布局,适用于从上到下的排列
  3. Row布局:水平方向的线性布局,适用于从左到右的排列
  4. Grid布局:网格布局,适用于二维数据展示,如图片墙、表格等
  5. Stack布局:层叠布局,支持组件的堆叠显示,适用于覆盖层等场景
  6. RelativeContainer布局:相对布局,基于锚点进行定位,适用于复杂的相对位置关系

这些布局方式可以相互组合,构建出复杂而灵活的UI界面。百分比布局作为一种尺寸定义方式,可以与上述任何布局方式结合使用。

1.3 百分比布局在响应式设计中的地位

在移动应用开发中,不同设备的屏幕尺寸和分辨率差异很大。从手机到平板,再到折叠屏设备,屏幕尺寸可以从几英寸到十几英寸不等。如何让应用在不同设备上都能良好地显示,是开发者面临的一个重要挑战。

百分比布局作为响应式设计的核心技术之一,能够让组件根据父容器的尺寸自动调整,从而实现跨设备的自适应显示效果。与固定像素布局相比,百分比布局具有以下优势:

  • 自适应能力强:组件尺寸随父容器动态变化,无需为不同屏幕尺寸编写多套代码
  • 代码简洁:使用百分比值定义尺寸,代码更加简洁明了
  • 维护成本低:布局逻辑统一,便于后期维护和扩展
  • 用户体验好:在不同设备上都能保持一致的视觉比例

第2章 百分比Width/Height布局核心概念

2.1 百分比尺寸的定义与原理

在ArkTS中,百分比尺寸是指组件的宽度或高度相对于其父容器可用空间的比例。通过width()height()方法设置百分比值,可以实现组件的自适应尺寸。

.width('50%')  // 宽度占父容器的50%
.height('30%') // 高度占父容器的30%

百分比尺寸的计算遵循以下规则:

  1. 相对性:子组件的百分比尺寸是相对于直接父容器的可用空间计算的,而不是相对于屏幕或其他祖先容器
  2. 约束性:父容器必须有明确的尺寸约束(固定尺寸或百分比尺寸),否则子组件的百分比尺寸无法正确计算
  3. 动态性:百分比尺寸会在布局测量阶段动态计算,当父容器尺寸变化时,子组件尺寸会自动更新

2.2 width()方法详解

width()方法用于设置组件的宽度,支持多种参数类型:

// 固定像素值:宽度为200px
.width(200)

// 百分比值:宽度占父容器的50%
.width('50%')

// 自适应内容:根据内容自动调整宽度
.width('auto')

// 占满父容器:宽度占父容器的100%
.width('100%')

当使用百分比值时,组件的实际宽度计算公式为:
实际宽度 = 父容器宽度 × 百分比

需要注意的是,百分比宽度的计算不包括父容器的padding和border。如果父容器设置了padding,子组件的可用空间会相应减少。

2.3 height()方法详解

height()方法用于设置组件的高度,用法与width()类似:

// 固定像素值:高度为100px
.height(100)

// 百分比值:高度占父容器的30%
.height('30%')

// 自适应内容:根据内容自动调整高度
.height('auto')

// 占满父容器:高度占父容器的100%
.height('100%')

当使用百分比值时,组件的实际高度计算公式为:
实际高度 = 父容器高度 × 百分比

与宽度不同的是,高度百分比的计算需要父容器有明确的高度约束。如果父容器的高度是auto(由内容撑开),那么子组件的百分比高度可能无法正确计算。

2.4 百分比尺寸与其他尺寸类型的对比

在ArkTS中,组件尺寸主要有以下几种类型:

尺寸类型 语法示例 特点 适用场景
固定像素 .width(200) 尺寸固定不变,不随父容器变化 需要精确控制的元素,如图标、按钮等
百分比 .width('50%') 随父容器动态变化,实现响应式布局 需要自适应的容器、卡片等
自适应 .width('auto') 根据内容自动调整尺寸 文本、图片等内容型组件
弹性 .flexGrow(1) 根据剩余空间分配尺寸,实现弹性布局 Flex布局中的弹性元素

在实际开发中,需要根据具体场景选择合适的尺寸类型。例如,在响应式布局中,可以使用百分比定义容器尺寸,使用固定像素定义图标尺寸,使用自适应定义文本尺寸。

第3章 百分比布局的实际应用场景

3.1 场景一:等分容器布局

在实际开发中,经常需要将容器等分为多个部分。例如,在一个横向容器中放置两个各占50%宽度的子组件,实现左右分栏布局。

@Entry
@Component
struct EqualSplitLayout {
  build() {
    Flex({ direction: FlexDirection.Row }) {
      // 左侧容器:占宽度50%
      Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
        Text('左侧容器')
          .fontSize(16)
          .fontColor('#ffffff')
      }
      .width('50%')
      .height(200)
      .backgroundColor('#FF6B6B')

      // 右侧容器:占宽度50%
      Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
        Text('右侧容器')
          .fontSize(16)
          .fontColor('#ffffff')
      }
      .width('50%')
      .height(200)
      .backgroundColor('#4ECDC4')
    }
    .width('100%')
  }
}

在这个示例中,父容器是一个水平方向的Flex布局,宽度占满屏幕。两个子容器各占父容器宽度的50%,实现了等分效果。需要注意的是,如果子容器需要设置margin,百分比值需要相应调整,避免溢出。

3.2 场景二:响应式卡片布局

卡片布局是移动应用中常见的UI模式,使用百分比布局可以实现卡片的自适应宽度。例如,在一个列表中显示多个卡片,每张卡片的宽度根据屏幕尺寸自动调整。

@Entry
@Component
struct ResponsiveCardLayout {
  @State cards: string[] = ['卡片1', '卡片2', '卡片3', '卡片4']

  build() {
    Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.SpaceEvenly }) {
      Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceEvenly }) {
        ForEach(this.cards, (cardName: string, index: number) => {
          Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
            Text(cardName)
              .fontSize(14)
              .fontColor('#ffffff')
          }
          .width('48%')
          .height('45%')
          .backgroundColor(this.getCardColor(index))
          .borderRadius(12)
        })
      }
      .width('90%')
      .height(200)
      .backgroundColor('#F5F5F5')
    }
    .width('100%')
    .height(300)
  }

  getCardColor(index: number): string {
    const colors: string[] = ['#FF6B6B', '#4ECDC4', '#FFE66D', '#A29BFE']
    return colors[index % colors.length]
  }
}

在这个示例中,每张卡片的宽度占父容器的48%,留出2%的间距。使用justifyContent: FlexAlign.SpaceEvenly可以实现卡片之间的均匀分布。这种布局方式可以确保在不同屏幕尺寸下,卡片都能保持良好的显示效果。

3.3 场景三:多层嵌套百分比布局

百分比布局支持多层嵌套,每层的百分比都是相对于其父容器计算的。这种特性使得开发者可以构建复杂的嵌套布局结构。

@Entry
@Component
struct NestedPercentageLayout {
  build() {
    // 第一层:占屏幕100%
    Flex({ direction: FlexDirection.Column }) {
      // 第二层:占第一层80%
      Flex({ direction: FlexDirection.Row }) {
        // 第三层:占第二层50%
        Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
          Text('嵌套层级1')
            .fontSize(12)
            .fontColor('#ffffff')
        }
        .width('50%')
        .height('50%')
        .backgroundColor('#FF6B6B')

        // 第三层:占第二层50%
        Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
          Text('嵌套层级2')
            .fontSize(12)
            .fontColor('#ffffff')
        }
        .width('50%')
        .height('50%')
        .backgroundColor('#4ECDC4')
      }
      .width('80%')
      .height(150)
      .backgroundColor('#E0E0E0')
    }
    .width('100%')
    .height('100%')
  }
}

在这个示例中,布局结构分为三层:

  • 第一层(根容器):宽度和高度都占屏幕的100%
  • 第二层:宽度占第一层的80%,高度为150px
  • 第三层:两个子容器各占第二层宽度的50%,高度的50%

需要注意的是,每层的百分比都是相对于直接父容器计算的,因此在嵌套布局中需要仔细计算各层的尺寸关系。

3.4 场景四:混合尺寸布局

在实际开发中,常常需要混合使用固定尺寸和百分比尺寸。例如,一个页面可能包含固定高度的标题栏、百分比宽度的内容区和固定高度的底部栏。

@Entry
@Component
struct MixedLayout {
  build() {
    Flex({ direction: FlexDirection.Column }) {
      // 顶部固定高度的标题栏
      Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
        Text('固定高度标题栏')
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
      }
      .width('100%')
      .height(80)
      .backgroundColor('#2C3E50')

      // 中间占满剩余空间的内容区
      Flex({ direction: FlexDirection.Row }) {
        // 左侧固定宽度的侧边栏
        Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
          Text('固定宽度侧边栏')
            .fontSize(14)
            .fontColor('#ffffff')
        }
        .width(120)
        .height('100%')
        .backgroundColor('#3498DB')

        // 右侧占满剩余宽度的主内容区
        Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
          Text('百分比宽度主内容区')
            .fontSize(16)
            .fontColor('#333333')
        }
        .flexGrow(1)
        .height('100%')
        .backgroundColor('#ECF0F1')
      }
      .width('100%')
      .flexGrow(1)

      // 底部固定高度的状态栏
      Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
        Text('固定高度状态栏')
          .fontSize(14)
          .fontColor('#ffffff')
      }
      .width('100%')
      .height(60)
      .backgroundColor('#2C3E50')
    }
    .width('100%')
    .height('100%')
  }
}

在这个示例中:

  • 标题栏和状态栏使用固定高度(80px和60px)
  • 侧边栏使用固定宽度(120px)
  • 主内容区使用flexGrow(1)占满剩余空间

这种混合尺寸布局方式在实际开发中非常常见,能够满足不同组件的尺寸需求。

第4章 百分比布局与Flex布局的结合使用

4.1 Flex布局基础

Flex布局是ArkUI中最常用的布局方式,基于弹性盒子模型,支持灵活的弹性布局。通过Flex布局,开发者可以轻松实现水平和垂直方向的排列,以及组件之间的对齐和间距控制。

Flex({
  direction: FlexDirection.Column,    // 排列方向:Column垂直,Row水平
  justifyContent: FlexAlign.Center,   // 主轴对齐方式
  alignItems: ItemAlign.Center        // 交叉轴对齐方式
})

Flex布局的核心属性包括:

  • direction:控制排列方向,可选值为FlexDirection.Column(垂直)和FlexDirection.Row(水平)
  • justifyContent:控制主轴方向上的对齐方式,可选值包括StartCenterEndSpaceBetweenSpaceAroundSpaceEvenly
  • alignItems:控制交叉轴方向上的对齐方式,可选值包括StartCenterEndStretch

4.2 Flex属性与百分比布局的协同

在Flex布局中,百分比布局可以与以下属性配合使用,实现更加灵活的布局效果:

  1. flexGrow:控制组件在主轴方向上的弹性增长。当父容器有剩余空间时,flexGrow值越大的组件分配到的空间越多。
  2. flexShrink:控制组件在主轴方向上的弹性收缩。当父容器空间不足时,flexShrink值越大的组件收缩越多。
  3. justifyContent:控制主轴方向上的对齐方式,可以与百分比布局配合使用,实现组件的均匀分布。
  4. alignItems:控制交叉轴方向上的对齐方式,可以与百分比布局配合使用,实现组件的垂直对齐。

4.3 实践:弹性百分比布局

下面是一个结合Flex布局和百分比布局的实践示例,展示如何实现弹性百分比布局:

@Entry
@Component
struct FlexPercentageLayout {
  @State items: string[] = ['弹性项1', '弹性项2', '弹性项3']

  build() {
    Flex({ 
      direction: FlexDirection.Row, 
      justifyContent: FlexAlign.SpaceEvenly,
      alignItems: ItemAlign.Center
    }) {
      ForEach(this.items, (item: string) => {
        Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
          Text(item)
            .fontSize(14)
            .fontColor('#ffffff')
        }
        .width('30%')
        .height('60%')
        .backgroundColor('#3498DB')
        .borderRadius(8)
      })
    }
    .width('90%')
    .height(150)
    .backgroundColor('#F5F5F5')
    .borderRadius(12)
  }
}

在这个示例中,三个子组件各占父容器宽度的30%,高度的60%。使用justifyContent: FlexAlign.SpaceEvenly实现了组件之间的均匀分布。这种布局方式既保证了组件的自适应尺寸,又实现了良好的视觉效果。

4.4 实践:响应式网格布局

结合Flex布局和百分比布局,可以实现响应式网格布局,根据屏幕尺寸自动调整列数:

@Entry
@Component
struct ResponsiveGridLayout {
  @State gridItems: string[] = Array.from({ length: 12 }, (_, i) => `项目${i + 1}`)
  @State columns: number = 3

  build() {
    Flex({ direction: FlexDirection.Column }) {
      Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceEvenly }) {
        ForEach(this.gridItems, (item: string) => {
          Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
            Text(item)
              .fontSize(14)
              .fontColor('#ffffff')
          }
          .width(`${100 / this.columns - 2}%`)
          .height(80)
          .backgroundColor('#3498DB')
          .borderRadius(8)
        })
      }
      .width('90%')
      .flexWrap(FlexWrap.Wrap)
    }
    .width('100%')
    .height('100%')
    .padding({ top: 20 })
  }
}

在这个示例中,通过动态计算百分比宽度,实现了响应式网格布局。当列数变化时,每个网格项的宽度会自动调整,确保布局始终保持良好的显示效果。

第5章 百分比布局的边界处理与溢出控制

5.1 溢出问题的产生原因

在使用百分比布局时,溢出问题是一个常见的挑战。溢出问题主要由以下原因产生:

  1. 百分比总和超过100%:当多个子组件的百分比尺寸总和超过100%时,会导致溢出。
  2. margin/padding的影响:子组件的margin和padding会增加组件的实际尺寸,如果不考虑这些因素,可能导致溢出。
  3. 内容超出:当组件的内容超出其尺寸时,也会导致溢出。

5.2 解决方案一:调整百分比值

最直接的解决方案是调整百分比值,为间距和其他因素留出空间:

// 两个子组件各占48%,留出4%作为间距
.width('48%')  // 而不是50%

这种方法简单直接,但需要手动计算百分比值,不够灵活。

5.3 解决方案二:使用Flex布局的justifyContent

利用Flex布局的justifyContent属性可以自动分配间距,避免手动计算百分比值:

Flex({ 
  direction: FlexDirection.Row,
  justifyContent: FlexAlign.SpaceEvenly
}) {
  Flex() {
    Text('子组件1')
  }
  .width('48%')
  .height(100)

  Flex() {
    Text('子组件2')
  }
  .width('48%')
  .height(100)
}
.width('100%')

使用SpaceEvenly可以实现组件之间的均匀分布,无需手动设置margin。

5.4 解决方案三:使用padding替代margin

将子组件的margin改为父容器的padding,可以避免子组件间距影响布局计算:

Flex({ direction: FlexDirection.Row }) {
  Flex() {
    Text('子组件1')
  }
  .width('50%')
  .height(100)

  Flex() {
    Text('子组件2')
  }
  .width('50%')
  .height(100)
}
.width('90%')
.height(100)
.padding({ left: 10, right: 10 })

这种方法将间距从子组件转移到父容器,避免了子组件间距对百分比计算的影响。

5.5 解决方案四:使用overflow属性

当内容确实需要溢出时,可以使用overflow属性控制溢出行为:

Flex({ direction: FlexDirection.Row }) {
  Text('这是一段很长的文本内容,可能会超出容器宽度')
    .fontSize(14)
}
.width('50%')
.height(50)
.overflow({ overflow: Overflow.Hidden })

overflow属性支持以下值:

  • Overflow.Hidden:隐藏溢出内容
  • Overflow.Scroll:显示滚动条
  • Overflow.Visible:显示溢出内容

5.6 解决方案五:使用boxSizing属性

在CSS中,boxSizing属性可以控制元素的尺寸计算方式。在ArkTS中,虽然没有直接的boxSizing属性,但可以通过合理的布局结构来实现类似的效果。

Flex({ direction: FlexDirection.Row }) {
  Flex() {
    Text('子组件1')
  }
  .width('50%')
  .height(100)
  .padding(10)
}
.width('100%')
.height(100)

在这个示例中,子组件的padding是包含在其百分比宽度内的,不会导致溢出。

第6章 百分比布局的性能优化

6.1 布局性能的影响因素

布局性能主要受以下因素影响:

  • 布局层级深度:布局层级越深,布局计算的复杂度越高
  • 组件数量:组件数量越多,布局计算的时间越长
  • 尺寸计算复杂度:百分比尺寸需要动态计算,比固定尺寸的计算复杂度更高
  • 重排和重绘频率:频繁的布局变化会导致频繁的重排和重绘,影响性能

6.2 优化策略一:减少布局层级

过多的布局层级会增加布局计算的复杂度,应尽量减少不必要的嵌套:

// 不推荐:多层嵌套
Flex() {
  Flex() {
    Flex() {
      Text('内容')
    }
    .width('100%')
  }
  .width('100%')
}
.width('100%')

// 推荐:简化层级
Flex() {
  Text('内容')
}
.width('100%')

6.3 优化策略二:合理使用固定尺寸

对于不需要自适应的元素,使用固定尺寸可以减少布局计算量:

// 图标使用固定尺寸
Image($r('app.media.icon'))
  .width(48)
  .height(48)

// 文本容器使用百分比尺寸
Flex() {
  Text('描述文本')
}
.width('100%')

6.4 优化策略三:避免过度使用flexGrow

过多的flexGrow会增加布局计算的复杂度,应合理控制:

// 只在必要时使用flexGrow
Flex({ direction: FlexDirection.Row }) {
  Flex() {
    Text('固定宽度')
  }
  .width(100)

  Flex() {
    Text('弹性宽度')
  }
  .flexGrow(1)
}

6.5 优化策略四:使用懒加载

对于大量数据列表,使用懒加载可以减少初始布局计算量:

List() {
  ForEach(this.dataList, (item: string) => {
    ListItem() {
      Flex() {
        Text(item)
      }
      .width('100%')
      .height('100%')
    }
  })
}
.lazy(true)

6.6 优化策略五:缓存布局计算结果

对于复杂的布局计算,可以缓存计算结果,避免重复计算:

@Entry
@Component
struct OptimizedLayout {
  @State containerWidth: number = 0
  @State cachedItemWidth: number = 0

  onContainerSizeChange(width: number) {
    this.containerWidth = width
    // 缓存计算结果
    this.cachedItemWidth = width * 0.48
  }

  build() {
    Flex({ direction: FlexDirection.Row }) {
      Flex() {
        Text('子组件')
      }
      .width(this.cachedItemWidth)
      .height(100)
    }
    .width('100%')
    .onAreaChange((oldValue: Area, newValue: Area) => {
      this.onContainerSizeChange(newValue.width)
    })
  }
}

第7章 百分比布局的适配方案

7.1 不同屏幕尺寸的适配

百分比布局天然具有一定的适配能力,但在极端屏幕尺寸下仍需特殊处理。可以通过动态调整百分比值来实现更精细的适配:

@Entry
@Component
struct ScreenAdaptationLayout {
  @State screenWidth: number = 0
  @State screenHeight: number = 0

  aboutToAppear() {
    import { window } from '@ohos.window'
    window.getLastWindow((err, win) => {
      if (!err && win) {
        win.getWindowProperties((err, properties) => {
          if (!err) {
            this.screenWidth = properties.windowRect.width
            this.screenHeight = properties.windowRect.height
          }
        })
      }
    })
  }

  build() {
    Flex({ direction: FlexDirection.Column }) {
      Flex() {
        Text('自适应内容')
      }
      .width(this.screenWidth > 600 ? '40%' : '80%')
      .height('30%')
      .backgroundColor('#3498DB')
    }
    .width('100%')
    .height('100%')
  }
}

在这个示例中,根据屏幕宽度动态调整子组件的百分比宽度,实现了更精细的屏幕适配。

7.2 横竖屏切换的适配

在横竖屏切换时,百分比布局会自动调整,但需要注意布局方向的变化:

@Entry
@Component
struct OrientationAdaptationLayout {
  @State isLandscape: boolean = false

  aboutToAppear() {
    import { window } from '@ohos.window'
    window.getLastWindow((err, win) => {
      if (!err && win) {
        win.on('windowSizeChange', (size) => {
          this.isLandscape = size.width > size.height
        })
      }
    })
  }

  build() {
    Flex({ direction: this.isLandscape ? FlexDirection.Row : FlexDirection.Column }) {
      Flex() {
        Text('内容区域1')
      }
      .width(this.isLandscape ? '30%' : '100%')
      .height(this.isLandscape ? '100%' : '50%')
      .backgroundColor('#FF6B6B')

      Flex() {
        Text('内容区域2')
      }
      .width(this.isLandscape ? '70%' : '100%')
      .height(this.isLandscape ? '100%' : '50%')
      .backgroundColor('#4ECDC4')
    }
    .width('100%')
    .height('100%')
  }
}

在这个示例中,根据横竖屏状态切换布局方向和百分比尺寸,实现了良好的横竖屏适配效果。

7.3 折叠屏设备的适配

折叠屏设备提供了更大的显示空间,需要针对不同折叠状态进行适配:

@Entry
@Component
struct FoldableAdaptationLayout {
  @State foldState: number = 0

  aboutToAppear() {
    const windowClass = getContext(this) as common.UIAbilityContext
    const window = windowClass.getWindow()
  }

  build() {
    Flex({ direction: FlexDirection.Row }) {
      if (this.foldState === 0) {
        Flex({ direction: FlexDirection.Column }) {
          this.renderContent()
        }
        .width('100%')
      } else {
        Flex() {
          this.renderContent()
        }
        .width('50%')

        Flex() {
          this.renderDetail()
        }
        .width('50%')
      }
    }
    .width('100%')
    .height('100%')
  }

  @Builder
  renderContent() {
    Text('主内容区域')
      .fontSize(18)
  }

  @Builder
  renderDetail() {
    Text('详情区域')
      .fontSize(16)
  }
}

在这个示例中,根据折叠状态调整布局结构,实现了折叠屏设备的适配。

第8章 百分比布局的最佳实践

8.1 布局规划原则

在使用百分比布局时,应遵循以下规划原则:

  1. 明确布局层级:在开始编码前,先规划好布局的层级结构,明确每个容器的尺寸约束
  2. 从外到内:先确定外层容器的尺寸,再逐步向内层细化,确保每层都有明确的尺寸约束
  3. 统一单位:避免混用百分比和固定像素进行边界计算,确保尺寸计算的一致性
  4. 预留间距:在使用百分比时,为margin、padding等留出空间,避免溢出问题

8.2 代码组织规范

良好的代码组织可以提高布局的可读性和可维护性:

@Entry
@Component
struct WellOrganizedLayout {
  @State title: string = '标题'
  @State content: string = '内容'

  build() {
    Flex({ direction: FlexDirection.Column }) {
      this.renderHeader()
      this.renderContent()
      this.renderFooter()
    }
    .width('100%')
    .height('100%')
  }

  @Builder
  renderHeader() {
    Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
      Text(this.title)
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
    }
    .width('100%')
    .height('10%')
    .backgroundColor('#2C3E50')
    .fontColor('#ffffff')
  }

  @Builder
  renderContent() {
    Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
      Text(this.content)
        .fontSize(16)
        .fontColor('#333333')
    }
    .width('100%')
    .flexGrow(1)
    .backgroundColor('#ECF0F1')
  }

  @Builder
  renderFooter() {
    Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
      Text('底部')
        .fontSize(14)
        .fontColor('#ffffff')
    }
    .width('100%')
    .height('10%')
    .backgroundColor('#2C3E50')
  }
}

在这个示例中,使用@Builder装饰器将布局分解为多个独立的构建函数,提高了代码的可读性和可维护性。

8.3 调试技巧

在开发过程中,可以通过以下方法调试布局问题:

  1. 添加背景色:为容器添加不同颜色的背景,便于观察布局边界
  2. 使用边框:添加borderWidth和borderColor,查看组件的实际尺寸
  3. 打印日志:在aboutToAppear中打印组件的实际尺寸
  4. 使用预览工具:利用DevEco Studio的预览功能实时查看布局效果

8.4 常见误区

在使用百分比布局时,需要注意以下常见误区:

  1. 父容器无尺寸约束:子组件的百分比尺寸依赖于父容器的尺寸约束,如果父容器没有明确的尺寸,子组件的百分比尺寸可能无法正确计算
  2. 混用百分比和固定像素:在进行边界计算时,避免混用百分比和固定像素,应统一使用一种单位
  3. 忽略margin和padding:子组件的margin和padding会增加其实际尺寸,需要在计算百分比时考虑这些因素
  4. 过度嵌套:过多的布局嵌套会增加布局计算的复杂度,应尽量简化布局层级

第9章 完整实战案例:响应式新闻列表页面

9.1 需求分析

创建一个响应式新闻列表页面,包含以下功能:

  • 顶部标题栏(固定高度)
  • 新闻分类标签(横向滚动)
  • 新闻列表(卡片式布局,支持百分比宽度)
  • 底部导航栏(固定高度)

9.2 布局设计

页面布局结构如下:

┌─────────────────────────────────────┐
│         标题栏 (height: 80px)       │
├─────────────────────────────────────┤
│    分类标签 (横向滚动, height: 50px) │
├─────────────────────────────────────┤
│                                     │
│   ┌────────────┐ ┌────────────┐     │
│   │   新闻卡片  │ │   新闻卡片  │     │
│   │ (width:    │ │ (width:    │     │
│   │  48%,      │ │  48%,      │     │
│   │  height:   │ │  height:   │     │
│   │  200px)    │ │  200px)    │     │
│   └────────────┘ └────────────┘     │
│                                     │
│   ┌────────────┐ ┌────────────┐     │
│   │   新闻卡片  │ │   新闻卡片  │     │
│   │ (width:    │ │ (width:    │     │
│   │  48%,      │ │  48%,      │     │
│   │  height:   │ │  height:   │     │
│   │  200px)    │ │  200px)    │     │
│   └────────────┘ └────────────┘     │
│                                     │
│         flexGrow: 1                 │
├─────────────────────────────────────┤
│       底部导航栏 (height: 60px)      │
└─────────────────────────────────────┘

9.3 完整代码实现

@Entry
@Component
struct NewsListPage {
  @State categories: string[] = ['推荐', '热点', '科技', '娱乐', '体育', '财经']
  @State selectedCategory: string = '推荐'

  @State newsList: Array<{ id: number; title: string; image: string; author: string; time: string }> = [
    { id: 1, title: '鸿蒙HarmonyOS NEXT正式发布,开启全场景智慧生活新篇章', image: '', author: '科技日报', time: '10分钟前' },
    { id: 2, title: '华为Mate 70系列销量突破千万,创新技术引领行业发展', image: '', author: '财经时报', time: '30分钟前' },
    { id: 3, title: '人工智能大模型应用落地,助力企业数字化转型', image: '', author: '科技前沿', time: '1小时前' },
    { id: 4, title: '新能源汽车市场竞争加剧,智能驾驶成为新焦点', image: '', author: '汽车周刊', time: '2小时前' },
    { id: 5, title: '5G商用三年,改变生活的十大应用场景', image: '', author: '通信世界', time: '3小时前' },
    { id: 6, title: '区块链技术在金融领域的应用探索与实践', image: '', author: '金融科技', time: '4小时前' }
  ]

  @State navItems: Array<{ icon: string; label: string; active: boolean }> = [
    { icon: 'home', label: '首页', active: true },
    { icon: 'category', label: '分类', active: false },
    { icon: 'mine', label: '我的', active: false }
  ]

  build() {
    Flex({ direction: FlexDirection.Column }) {
      this.renderHeader()
      this.renderCategoryBar()
      this.renderNewsList()
      this.renderBottomNav()
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
  }

  @Builder
  renderHeader() {
    Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
      Text('鸿蒙新闻')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .fontColor('#ffffff')
    }
    .width('100%')
    .height(80)
    .backgroundColor('#2C3E50')
    .padding({ top: 20 })
  }

  @Builder
  renderCategoryBar() {
    Scroll({ direction: ScrollDirection.Horizontal, scrollBar: BarState.Off }) {
      Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center }) {
        ForEach(this.categories, (category: string) => {
          Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
            Text(category)
              .fontSize(16)
              .fontColor(this.selectedCategory === category ? '#2C3E50' : '#666666')
          }
          .width(80)
          .height(36)
          .backgroundColor(this.selectedCategory === category ? '#ECF0F1' : 'transparent')
          .borderRadius(18)
          .margin({ right: 10 })
          .onClick(() => {
            this.selectedCategory = category
          })
        })
      }
      .width('auto')
      .height('100%')
      .padding({ left: 15, right: 15 })
    }
    .width('100%')
    .height(50)
    .backgroundColor('#ffffff')
  }

  @Builder
  renderNewsList() {
    Scroll({ direction: ScrollDirection.Vertical, scrollBar: BarState.Off }) {
      Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center }) {
        ForEach(this.newsList, (news: { id: number; title: string; image: string; author: string; time: string }) => {
          this.renderNewsCard(news)
        })
      }
      .width('100%')
      .padding({ top: 10, bottom: 20 })
    }
    .width('100%')
    .flexGrow(1)
  }

  @Builder
  renderNewsCard(news: { id: number; title: string; image: string; author: string; time: string }) {
    Flex({ direction: FlexDirection.Row }) {
      Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
        Text('图片')
          .fontSize(14)
          .fontColor('#999999')
      }
      .width('40%')
      .height('100%')
      .backgroundColor('#E8E8E8')
      .borderRadius({ topLeft: 8, bottomLeft: 8 })

      Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.SpaceBetween }) {
        Text(news.title)
          .fontSize(16)
          .fontColor('#333333')
          .maxLines(2)
          .textOverflow({ overflow: TextOverflow.Ellipsis })

        Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween }) {
          Text(news.author)
            .fontSize(12)
            .fontColor('#999999')

          Text(news.time)
            .fontSize(12)
            .fontColor('#999999')
        }
      }
      .width('60%')
      .height('100%')
      .padding({ left: 12, right: 12, top: 10, bottom: 10 })
    }
    .width('90%')
    .height(200)
    .backgroundColor('#ffffff')
    .borderRadius(8)
    .margin({ bottom: 15 })
    .shadow({ radius: 4, color: '#00000020', offsetY: 2 })
  }

  @Builder
  renderBottomNav() {
    Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceAround, alignItems: ItemAlign.Center }) {
      ForEach(this.navItems, (item: { icon: string; label: string; active: boolean }) => {
        Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
          Text(item.icon)
            .fontSize(24)
            .fontColor(item.active ? '#2C3E50' : '#999999')

          Text(item.label)
            .fontSize(12)
            .fontColor(item.active ? '#2C3E50' : '#999999')
            .margin({ top: 4 })
        }
        .width('33.33%')
        .height('100%')
        .onClick(() => {
          this.navItems = this.navItems.map((nav) => ({
            ...nav,
            active: nav.label === item.label
          }))
        })
      })
    }
    .width('100%')
    .height(60)
    .backgroundColor('#ffffff')
    .borderWidth({ top: 1 })
    .borderColor('#E0E0E0')
  }
}

9.4 代码解析

这个响应式新闻列表页面展示了百分比布局的综合应用:

  1. 标题栏和底部导航栏:使用固定高度(80px和60px),宽度占满屏幕
  2. 分类标签栏:使用固定高度(50px),支持横向滚动
  3. 新闻列表:使用flexGrow(1)占满剩余空间,支持垂直滚动
  4. 新闻卡片:宽度占屏幕的90%,内部图片区域占卡片宽度的40%,内容区域占60%
  5. 底部导航项:三个导航项各占宽度的33.33%

通过合理的百分比布局和Flex布局的结合,实现了良好的响应式效果。

第10章 常见问题与解决方案

10.1 问题一:百分比尺寸不生效

现象:设置了.width('50%'),但组件宽度没有按预期显示。

原因分析

  • 父容器没有明确的尺寸约束
  • 父容器使用了width('auto')
  • 布局层级问题,子组件的父容器不是预期的容器

解决方案

Flex() {
  Flex() {
    Text('子组件')
  }
  .width('50%')
}
.width('100%')

确保父容器有明确的尺寸约束,子组件的百分比尺寸才能正确计算。

10.2 问题二:布局溢出

现象:子组件超出了父容器的边界。

原因分析

  • 百分比尺寸加上margin/padding后超出了父容器
  • 多个子组件的百分比总和超过100%
  • 内容超出了组件的尺寸

解决方案

Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceEvenly }) {
  Flex() { Text('组件1') }.width('48%')
  Flex() { Text('组件2') }.width('48%')
}
.width('100%')

调整百分比值,留出间距,或使用Flex布局的justifyContent自动分配间距。

10.3 问题三:高度百分比不生效

现象:设置了.height('30%'),但组件高度没有按预期显示。

原因分析

  • 父容器没有明确的高度约束
  • 父容器的高度依赖内容撑开(height: auto)
  • 布局方向问题,父容器是Row布局,高度由内容决定

解决方案

Flex({ direction: FlexDirection.Column }) {
  Flex() {
    Text('子组件')
  }
  .height('30%')
}
.width('100%')
.height('100%')

确保父容器有明确的高度约束,子组件的百分比高度才能正确计算。

10.4 问题四:布局闪烁

现象:在布局变化时出现闪烁现象。

原因分析

  • 频繁的重排和重绘
  • 状态更新导致布局重新计算
  • 布局计算过于复杂

解决方案

@State @Watch('onSizeChange') containerSize: number = 0

onSizeChange() {
}

使用@Watch优化状态更新,减少不必要的布局计算。

10.5 问题五:嵌套布局中百分比尺寸计算错误

现象:在嵌套布局中,子组件的百分比尺寸计算错误。

原因分析

  • 父容器的尺寸约束不明确
  • 多层嵌套导致尺寸计算复杂度增加
  • 百分比尺寸的相对性理解错误

解决方案

Flex({ direction: FlexDirection.Column }) {
  Flex({ direction: FlexDirection.Row }) {
    Flex() {
      Text('子组件')
    }
    .width('50%')
    .height('50%')
  }
  .width('80%')
  .height(200)
}
.width('100%')
.height('100%')

确保每层容器都有明确的尺寸约束,百分比尺寸的计算是相对于直接父容器的。

第11章 总结与展望

11.1 百分比布局的核心价值

百分比布局作为鸿蒙ArkTS布局体系的重要组成部分,具有以下核心价值:

  1. 响应式设计:能够自适应不同屏幕尺寸和分辨率,实现跨设备的一致体验
  2. 代码简洁:使用百分比值定义尺寸,代码更加简洁明了,无需为不同设备编写多套布局代码
  3. 维护成本低:布局逻辑统一,便于后期维护和扩展
  4. 灵活组合:可以与Flex、Grid等布局方式灵活组合,构建复杂的UI界面
  5. 用户体验好:在不同设备上都能保持一致的视觉比例,提升用户体验

11.2 未来发展方向

随着鸿蒙生态的不断发展,百分比布局也将不断演进:

  1. 更智能的适配:结合AI技术,实现更精准的自适应布局,根据设备特性自动调整布局策略
  2. 性能优化:进一步优化布局计算算法,提升渲染性能,减少布局计算时间
  3. 跨平台兼容:更好地支持多设备、多形态的布局适配,包括折叠屏、大屏设备等
  4. 可视化工具:提供更强大的可视化布局设计工具,帮助开发者快速构建布局
  5. 布局模板:提供更多的布局模板和最佳实践,降低开发者的学习成本

11.3 学习建议

对于想要深入学习百分比布局的开发者,建议:

  1. 掌握基础概念:理解百分比尺寸的计算原理和约束条件,明确百分比尺寸的相对性
  2. 实践项目:通过实际项目积累经验,理解不同场景下的最佳实践
  3. 阅读源码:深入研究ArkUI布局系统的实现原理,理解布局计算的底层机制
  4. 关注社区:参与鸿蒙开发者社区,分享经验和学习新技术
  5. 持续学习:关注鸿蒙的最新动态和API更新,保持学习的积极性

通过不断学习和实践,开发者可以熟练掌握百分比布局技术,构建出高质量、高性能的鸿蒙应用。

附录:API版本24相关说明

本文档基于HarmonyOS API版本24编写,涉及的主要API包括:

核心组件API

API 描述 使用示例
Flex() 弹性布局容器 Flex({ direction: FlexDirection.Column })
Column() 垂直线性布局容器 Column()
Row() 水平线性布局容器 Row()
Text() 文本组件 Text('内容')
Image() 图片组件 Image($r('app.media.icon'))
Scroll() 滚动容器 Scroll({ direction: ScrollDirection.Vertical })
List() 列表容器 List()
ListItem() 列表项组件 ListItem()

布局属性API

API 描述 使用示例
.width() 设置宽度 .width('50%')
.height() 设置高度 .height('30%')
.flexGrow() 弹性增长因子 .flexGrow(1)
.flexShrink() 弹性收缩因子 .flexShrink(1)
.margin() 外边距 .margin({ left: 10 })
.padding() 内边距 .padding(10)
.borderWidth() 边框宽度 .borderWidth(2)
.borderColor() 边框颜色 .borderColor('#E0E0E0')
.borderRadius() 圆角半径 .borderRadius(8)
.overflow() 溢出控制 .overflow({ overflow: Overflow.Hidden })
.shadow() 阴影效果 .shadow({ radius: 4, color: '#00000020' })

状态管理API

API 描述 使用示例
@Entry 页面入口装饰器 @Entry struct Page
@Component 组件装饰器 @Component struct Component
@State 状态变量装饰器 @State count: number = 0
@Builder 构建函数装饰器 @Builder renderContent()
@Watch 状态监听装饰器 @Watch('onChange')

枚举类型

枚举 描述 成员
FlexDirection 布局方向 Column, Row
FlexAlign 主轴对齐方式 Start, Center, End, SpaceBetween, SpaceAround, SpaceEvenly
ItemAlign 交叉轴对齐方式 Start, Center, End, Stretch
ScrollDirection 滚动方向 Horizontal, Vertical
BarState 滚动条状态 On, Off, Auto
Overflow 溢出行为 Hidden, Scroll, Visible

类型定义

类型 描述 示例
number 数字类型,用于固定像素值 200
string 字符串类型,用于百分比值 '50%', 'auto'
Array<T> 数组类型,用于列表数据 ['item1', 'item2']

以上API构成了百分比布局的核心技术栈,开发者可以根据实际需求灵活组合使用。


文档版本:v1.0
API版本:24
编写日期:2026年7月
适用平台:HarmonyOS NEXT


本文档详细介绍了鸿蒙原生ArkTS布局方式之百分比Width/Height自适应布局的核心概念、实际应用场景、性能优化策略和最佳实践,旨在帮助开发者深入理解和掌握这一重要的布局技术。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐