展开后像个“大号手机”,用户可不买账

折叠屏现在是 HarmonyOS 生态里的绝对主力,但很多开发者还没转过弯来。我见过不少应用,在直板手机上跑得好好的,一展开折叠屏,要么内容拉得老长,要么两边留白巨大,要么就是简单把手机界面等比例放大。

你想想,用户花大价钱买个折叠屏,展开后看到的还是直板机的 UI,心里能舒服吗?

有些同学会说:“我加个自适应宽高不就完了?” 真没那么简单。屏幕比例变了,交互逻辑也得跟着变。比如展开后,左边看列表,右边看详情,这就是我们常说的“平行视界”。

这篇就聊聊怎么用 ArkUI 搞定折叠屏适配和 ParallelView,代码都是能直接跑的,坑也帮你踩好了。

https://placeholder-01.jpg/

一、先搞懂断点,别用 px 硬扛

折叠屏适配的核心是响应式布局,而响应式布局的基础是断点(Breakpoint)。

HarmonyOS 把屏幕宽度分成了几个区间:xs、sm、md、lg、xl。直板机通常是 sm 或 md,折叠屏展开后一般是 lg。我们要做的,就是监听当前宽度落在哪个区间,然后切换对应的布局。

别再用 px 写死宽度了,要用 vp(虚拟像素)和百分比。

怎么监听?用 @ohos.mediaquery:

typescript

import mediaquery from '@ohos.mediaquery';

@Entry
@Component
struct FoldablePage {
  @State breakpoint: string = 'sm';
  private listener: mediaquery.MediaQueryListener = mediaquery.matchMediaSync('(width>=840vp)');

  aboutToAppear() {
    this.listener.on('change', (result: mediaquery.MediaQueryResult) => {
      this.breakpoint = result.matches ? 'lg' : 'sm';
      console.info('当前断点: ' + this.breakpoint);
    });
  }

  build() {
    Column() {
      if (this.breakpoint === 'lg') {
        // 折叠屏展开后的宽屏布局
        Row() {
          Text('左侧列表').layoutWeight(1)
          Text('右侧详情').layoutWeight(2)
        }
      } else {
        // 折叠状态或直板机
        Text('单列布局')
      }
    }
    .width('100%')
    .height('100%')
  }
}

这里的关键是 matchMediaSync('(width>=840vp)'),840vp 是个经验值,大约是大折叠展开后的宽度阈值。当这个条件满足时,matches 为 true,我们就切到宽屏布局。

坑来了:aboutToAppear 里获取的宽高可能不对。 如果你在 aboutToAppear 里直接获取屏幕宽高,这时候窗口可能还没完全创建,拿到的是默认值。用 mediaquery 监听就没这个问题,它会在窗口变化时实时回调。

二、平行视界:左右分栏不是简单切一半

平行视界(ParallelView)在折叠屏上简直是神仙功能。用户看资讯、逛淘宝,左边列表右边详情,体验直接拉满。

在 HarmonyOS 里实现这个,最推荐用 Navigation 组件。它天生支持分栏模式。

看代码:

typescript

@Entry
@Component
struct MainPage {
  @Provide('pageInfos') pageInfos: NavPathStack = new NavPathStack();

  build() {
    Navigation(this.pageInfos) {
      List() {
        ForEach([1, 2, 3, 4, 5], (item: number) => {
          ListItem() {
            Text('列表项 ' + item)
              .fontSize(16)
              .padding(16)
              .onClick(() => {
                this.pageInfos.pushPath({ name: 'DetailPage', param: { id: item } });
              })
          }
        })
      }
      .width('100%')
    }
    .mode(NavigationMode.Split) // 核心:开启分栏模式
    .navBarWidth(300) // 左侧导航栏宽度
    .width('100%')
    .height('100%')
  }
}

配上 NavDestination:

typescript

@Component
struct DetailPage {
  @Consume('pageInfos') pageInfos: NavPathStack;

  build() {
    NavDestination() {
      Column() {
        Text('详情页')
          .fontSize(24)
        Button('返回')
          .onClick(() => {
            this.pageInfos.pop();
          })
      }
    }
  }
}

NavigationMode.Split 是个魔法属性。在宽屏(折叠屏展开或平板)下,它会自动把 Navigation 和 NavDestination 左右并排显示。在窄屏(直板机或折叠状态)下,它会自动变成单页面堆叠模式,只需要这一行代码,适配就搞定了大半。

不过要注意,navBarWidth 别设死,最好根据屏幕宽度动态计算。

https://placeholder-02.png/

三、折叠状态变化:别让数据丢了

折叠屏有个特殊场景:用户正在看详情页,突然把手机折上了。这时候屏幕变窄,平行视界要切回单页模式。

如果处理不好,用户会发现详情页没了,直接回到了首页。这体验简直是灾难。

ArkUI 的 Navigation 组件其实已经帮我们处理了状态保存。当屏幕变窄时,它会自动将 NavDestination 压栈。但前提是你要正确使用 NavPathStack。

另外,对于自定义的折叠监听,记得在组件销毁时取消监听:

typescript

aboutToDisappear() {
  this.listener.off('change');
}

不取消的话,组件销毁了回调还在跑,内存泄漏是小事,有时候会导致应用崩溃。

还有一个非常容易踩的坑:横竖屏切换与折叠状态切换叠加。

你在手机上竖屏打开应用,然后展开折叠屏,再横过来,这一套连招下来,如果你的布局写得不健壮,直接乱套。建议大家多用 Flex 和 Grid 布局,少用绝对定位。

四、多设备预览:在 DevEco 里提前排雷

写完了代码,别只在默认的 P50 模拟器里跑。DevEco Studio 提供了多设备预览功能。

点开 Previewer,右上角可以切换设备类型,有折叠屏(展开/折叠)、平板、车机等。你在写代码的时候,随时切一下预览器,看看不同尺寸下的表现。

如果发现某个尺寸下文字被截断了,或者按钮重叠了,赶紧改。改完再切另一个尺寸。

一个非常实用的技巧: 在代码里加日志。当断点变化时,打印一下当前设备的宽度和断点类型。然后在模拟器里反复折叠展开,看日志输出。

typescript

console.info(`当前宽度: ${this.breakpoint}, 实际宽度: ${display.getDefaultDisplaySync().width}px`);

多跑几遍,你就对折叠屏的尺寸变化有感觉了。

https://placeholder-03.png/

五、那些让你 debug 到凌晨的坑

坑一:onWindowStageCreate 里获取宽度不准。
很多同学在 Ability 的 onWindowStageCreate 里去计算屏幕宽度,然后传给页面。结果发现展开折叠屏时,这里根本不会重新执行。正确做法是在 UI 组件的生命周期里用 mediaquery 监听。

坑二:图片变形。
折叠屏展开后宽高比变了,如果你给图片设置了固定宽高,它就会被拉伸。用 objectFit(ImageFit.Contain) 或者 .aspectRatio() 来保持比例。

坑三:手写笔和触控的交互差异。
有些折叠屏支持手写笔,如果你自己写了拖拽、缩放逻辑,记得测试一下手写笔的交互,鼠标事件和触控事件在折叠屏上可能会混用。

https://placeholder-04.jpg/

六、总结

折叠屏适配没那么玄乎,核心就是三件事:

  1. 用 mediaquery 监听断点,别写死 px。

  2. 用 Navigation 的 Split 模式,一行代码搞定平行视界。

  3. 多做多设备预览,提前发现问题。

Logo

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

更多推荐