HarmonyOS 7 实战 07:折叠屏适配与平行视界实战
展开后像个“大号手机”,用户可不买账
折叠屏现在是 HarmonyOS 生态里的绝对主力,但很多开发者还没转过弯来。我见过不少应用,在直板手机上跑得好好的,一展开折叠屏,要么内容拉得老长,要么两边留白巨大,要么就是简单把手机界面等比例放大。
你想想,用户花大价钱买个折叠屏,展开后看到的还是直板机的 UI,心里能舒服吗?
有些同学会说:“我加个自适应宽高不就完了?” 真没那么简单。屏幕比例变了,交互逻辑也得跟着变。比如展开后,左边看列表,右边看详情,这就是我们常说的“平行视界”。
这篇就聊聊怎么用 ArkUI 搞定折叠屏适配和 ParallelView,代码都是能直接跑的,坑也帮你踩好了。
一、先搞懂断点,别用 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 别设死,最好根据屏幕宽度动态计算。
三、折叠状态变化:别让数据丢了
折叠屏有个特殊场景:用户正在看详情页,突然把手机折上了。这时候屏幕变窄,平行视界要切回单页模式。
如果处理不好,用户会发现详情页没了,直接回到了首页。这体验简直是灾难。
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`);
多跑几遍,你就对折叠屏的尺寸变化有感觉了。
五、那些让你 debug 到凌晨的坑
坑一:onWindowStageCreate 里获取宽度不准。
很多同学在 Ability 的 onWindowStageCreate 里去计算屏幕宽度,然后传给页面。结果发现展开折叠屏时,这里根本不会重新执行。正确做法是在 UI 组件的生命周期里用 mediaquery 监听。
坑二:图片变形。
折叠屏展开后宽高比变了,如果你给图片设置了固定宽高,它就会被拉伸。用 objectFit(ImageFit.Contain) 或者 .aspectRatio() 来保持比例。
坑三:手写笔和触控的交互差异。
有些折叠屏支持手写笔,如果你自己写了拖拽、缩放逻辑,记得测试一下手写笔的交互,鼠标事件和触控事件在折叠屏上可能会混用。
六、总结
折叠屏适配没那么玄乎,核心就是三件事:
-
用
mediaquery监听断点,别写死 px。 -
用
Navigation的Split模式,一行代码搞定平行视界。 -
多做多设备预览,提前发现问题。
更多推荐

所有评论(0)