折叠屏适配最常见的错误,是代码里写一句 if (deviceType === 'foldable'),然后展开态走双栏、折叠态走单栏。在直板机上测试没问题,一到分屏、半悬停、平行视界就乱:明明设备是折叠屏,窗口宽度已经被压到 500vp 以下,代码还在按“折叠屏展开”渲染双栏,控件直接溢出。设备型号是固定的,窗口宽度是动态的。HarmonyOS 的一多适配,判断依据必须是窗口的实时可用宽度,不是设备类型。

先想清楚为什么设备判断靠不住。折叠屏合拢时窗口窄,展开时窗口宽,半悬停时可能只占一半屏幕,平行视界开启后右侧页面还会按比例缩放窗口宽度。同一个设备在不同形态下,窗口宽度可以从 300vp 跳到 800vp 以上。你用设备型号做判断,等于假设“这台设备永远只有一种窗口宽度”,这个假设在分屏和多窗口场景下不成立。正确的断点策略是:窗口宽度小于 600vp 走手机单栏,600vp 到 840vp 走平板窄窗口,大于 840vp 走双栏并列;哪怕是大屏平板,分屏后窗口缩到 600vp 以下,也自动切回单栏。

设备型号是假的,窗口宽度才是真的

用 onAreaChange 或窗口对象拿实时宽度,把断点判断做成一个可复用的函数:

typescript

type Breakpoint = 'sm' | 'md' | 'lg'

function getBreakpoint(windowWidthVp: number): Breakpoint {
  if (windowWidthVp < 600) {
    return 'sm'
  }
  if (windowWidthVp < 840) {
    return 'md'
  }
  return 'lg'
}

@Entry
@ComponentV2
struct AdaptivePage {
  @Local windowWidth: number = 360

  build() {
    Column() {
      Text(`当前窗口宽度: ${this.windowWidth}vp`)
      if (getBreakpoint(this.windowWidth) === 'sm') {
        this.SingleColumn()
      } else if (getBreakpoint(this.windowWidth) === 'md') {
        this.NarrowTablet()
      } else {
        this.DualColumn()
      }
    }
    .onAreaChange((oldArea, newArea) => {
      this.windowWidth = Number(newArea.width)
    })
  }

  @Builder
  SingleColumn() {
    Column() {
      Text('手机单栏布局')
    }.width('100%')
  }

  @Builder
  NarrowTablet() {
    Column() {
      Text('平板窄窗口布局')
    }.width('100%')
  }

  @Builder
  DualColumn() {
    Row() {
      Column() { Text('左栏') }.layoutWeight(1)
      Column() { Text('右栏') }.layoutWeight(1)
    }.width('100%')
  }
}

onAreaChange 在组件区域尺寸变化时触发,newArea.width 是当前可用宽度。getBreakpoint 只认宽度,不认设备。执行结果是分屏拖动、折叠开合、平行视界切换时,布局跟着窗口宽度走。常见错误是用 display.getDefaultDisplaySync().width 拿屏幕宽度而不是窗口宽度,分屏时屏幕没变、窗口变了,判断还是错的;另一个错误是把断点写成固定 px,不同密度设备上表现不一致。

GridRow别套Column

断点判断只解决“什么时候换布局”,具体布局内部尽量用 GridRow 和 GridCol 的栅格系统,不要手写大量嵌套容器去算排列。GridRow 会跟随窗口宽度调整列数,代码量小,出错概率也低。但要注意,GridRow 嵌套超过两层,布局成本会明显上升,外层遍历所有内层,内层又重复测量子项,形成双重遍历和重复测量。更常见的错误是在 GridRow 里再套 Column 做局部排列,栅格的自适应能力被绕过去了,嵌套反而更深。能用 Row、Spacer、layoutWeight 解决的,就别往 GridRow 里塞 Column。

折痕避让也别手算坐标。HarmonyOS 7 的 FoldSplitContainer 内置了 avoidFoldLine 属性,开启后系统自动识别物理折痕位置,给左右两栏留出避让间距,合拢、展开、半悬停三种形态都能用。手算偏移量在不同品牌、不同尺寸的折叠屏上很难对齐,交给容器更稳。

开合时别把业务状态一起重建

折叠屏开合、分屏切换时,布局会重新排布,组件可能销毁重建。如果你的表单数据、滚动位置、导航栈实例都挂在页面组件的 @State 里,布局一重建,用户填了一半的内容就没了。正确的做法是把核心业务状态放到与视图生命周期解绑的状态管理类里,布局变化只重新排布 UI,不触碰业务数据。这一点和普通页面状态管理不冲突,只是在折叠屏场景下,窗口形态变化比手机更频繁,状态被“顺手重建”的机会更多。适配折叠屏,先把窗口宽度当唯一断点依据,再把 GridRow 用对、把折痕交给容器、把业务状态和视图解耦,大部分形态切换的坑就能提前绕开。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐