
HarmonyOS 折叠屏适配实战展开态、折叠态与悬停态布局设计折叠屏适配最容易犯的错误是把它当成“大一点的手机屏幕”。实际项目里展开态像小平板折叠态像普通手机悬停态又像上下分区的特殊设备。如果应用只按宽度拉伸结果就是展开态信息密度低、折叠态按钮挤压、悬停态内容被铰链区域切断。这篇文章围绕一个具体目标让同一个 HarmonyOS 页面在折叠态、展开态、悬停态下都能保持清晰布局和可继续操作的状态。一、资料定位折叠屏不是单纯改宽度类型资料/能力本文使用点官方最佳实践折叠屏应用开发指导折叠屏形态、布局重排、体验边界官方多设备多端部署概览一套工程面向多设备部署官方响应式布局响应式布局断点、栅格、内容重排官方 ArkUIArkUI 布局能力Row、Column、Grid、Blank 等布局基础本文基于 HarmonyOS 5.0.0、ArkTS、Stage 模型。示例页面以“路线详情/内容详情”类应用为背景左侧是列表或目录右侧是详情内容悬停态需要把预览和操作区拆开。二、适配流程先判断场景再重排内容折叠屏适配建议按这个顺序做获取窗口尺寸。判断当前设备形态或姿态。根据断点选择布局。重排内容区域。保持用户交互状态。使用真机或对应模拟器验证。如果跳过前两步直接写一堆if width 600后期会很难维护。折叠屏需要同时考虑宽度、高度、方向、悬停、内容优先级。三、布局架构尺寸驱动姿态感知状态保持推荐结构entry/src/main/ets/ ├── pages/trip/ │ └── FoldableTripPage.ets ├── adaptive/ │ ├── WindowSizeClass.ets │ ├── FoldPostureModel.ets │ ├── BreakpointResolver.ets │ └── AdaptiveTripLayout.ets └── state/ └── TripStateKeeper.ets模块职责WindowSizeClass计算紧凑、中等、展开尺寸FoldPostureModel描述折叠态、展开态、悬停态BreakpointResolver决定当前布局策略AdaptiveTripLayout根据策略渲染不同布局TripStateKeeper保存当前选中项、滚动位置、编辑状态折叠屏适配最怕页面自己到处写判断。把判断集中到BreakpointResolver页面就会干净很多。四、定义尺寸和姿态模型exporttypeWindowSizeClasscompact|medium|expanded;exporttypeFoldPosturefolded|unfolded|halfFolded;exportinterfaceWindowProfile{widthVp:number;heightVp:number;sizeClass:WindowSizeClass;posture:FoldPosture;}exportfunctionresolveSizeClass(widthVp:number):WindowSizeClass{if(widthVp600){returncompact;}if(widthVp840){returnmedium;}returnexpanded;}代码解释WindowSizeClass是布局断点不绑定具体设备型号。FoldPosture描述设备姿态方便处理悬停态。WindowProfile把尺寸和姿态放到一个对象里避免页面传多个散乱参数。resolveSizeClass()的阈值要结合产品设计验证不是固定真理。断点不要写死在每个组件里。否则设计师一调整断点你要改几十处。五、布局策略每种形态有明确取舍exporttypeTripLayoutModesingleColumn|twoPane|hoverSplit;exportclassBreakpointResolver{staticresolve(profile:WindowProfile):TripLayoutMode{if(profile.posturehalfFolded){returnhoverSplit;}if(profile.sizeClassexpanded){returntwoPane;}returnsingleColumn;}}代码解释折叠屏悬停态优先级最高因为它的交互方式和普通横屏不同。展开态使用双栏布局提高信息密度。紧凑态回到单栏保证按钮和文本不拥挤。页面只消费TripLayoutMode不关心复杂判断。这就是工程上更稳的做法把“判断”集中把“渲染”拆开。六、页面入口监听窗口变化后只更新布局模式EntryComponentstruct FoldableTripPage{Stateprivateprofile:WindowProfile{widthVp:360,heightVp:720,sizeClass:compact,posture:folded};StateprivatelayoutMode:TripLayoutModesingleColumn;StateprivateselectedRouteId:stringroute_001;aboutToAppear():void{this.updateWindowProfile(720,900,unfolded);}build(){AdaptiveTripLayout({mode:this.layoutMode,selectedRouteId:this.selectedRouteId,onRouteChange:(id:string){this.selectedRouteIdid;}})}privateupdateWindowProfile(widthVp:number,heightVp:number,posture:FoldPosture):void{this.profile{widthVp,heightVp,sizeClass:resolveSizeClass(widthVp),posture};this.layoutModeBreakpointResolver.resolve(this.profile);}}代码解释页面只维护profile和layoutMode不直接写多套判断。selectedRouteId独立保存布局切换时不会丢选中状态。示例里用手动方法模拟窗口变化真实项目接入窗口尺寸变化监听。AdaptiveTripLayout负责具体布局页面入口保持轻。适配折叠屏时一定要验证“布局切换后状态是否还在”。比如用户展开屏幕后当前正在看的详情不能突然回到默认第一条。七、三种布局单栏、双栏、悬停分区Componentstruct AdaptiveTripLayout{Propmode:TripLayoutMode;PropselectedRouteId:string;onRouteChange:(id:string)void(){};build(){if(this.modetwoPane){this.TwoPaneLayout()}elseif(this.modehoverSplit){this.HoverSplitLayout()}else{this.SingleColumnLayout()}}BuilderSingleColumnLayout(){Column(){RouteHeader()RouteDetail({routeId:this.selectedRouteId})RouteActionBar()}.width(100%)}BuilderTwoPaneLayout(){Row(){RouteList({selectedRouteId:this.selectedRouteId,onSelect:this.onRouteChange}).width(36%)RouteDetail({routeId:this.selectedRouteId}).layoutWeight(1)}.width(100%)}BuilderHoverSplitLayout(){Column(){RoutePreview({routeId:this.selectedRouteId}).height(48%)RouteActionPanel({routeId:this.selectedRouteId}).layoutWeight(1)}.width(100%)}}代码解释单栏布局适合折叠态优先阅读顺序。双栏布局适合展开态左列表右详情。悬停态上下分区适合上方预览、下方操作。三种布局共享selectedRouteId不因重排丢状态。不要为了复用强行写一套复杂布局。折叠屏适配的关键是“内容策略不同”而不只是容器方向不同。八、状态保持布局切换不能打断用户exportinterfaceTripViewState{selectedRouteId:string;listScrollOffset:number;editingNote:string;}exportclassTripStateKeeper{privatestate:TripViewState{selectedRouteId:route_001,listScrollOffset:0,editingNote:};updateSelectedRoute(id:string):void{this.state.selectedRouteIdid;}updateListOffset(offset:number):void{this.state.listScrollOffsetoffset;}updateEditingNote(note:string):void{this.state.editingNotenote;}snapshot():TripViewState{return{...this.state};}}代码解释TripStateKeeper保存跨布局切换必须保留的状态。snapshot()返回拷贝避免外部直接修改内部状态。选中项、滚动位置、编辑草稿都属于折叠屏切换时容易丢的状态。状态保持要早做不要等测试发现“展开后内容重置”再补。实际项目可以把它接入页面级状态管理也可以放到 ViewModel。重点是状态不要绑死在某一种布局组件里。九、交互细节按钮不能被铰链和姿态影响折叠屏适配要额外关注这些点场景风险建议悬停态按钮落在折痕附近操作区放到下半屏稳定区域展开态内容过于分散使用双栏或主从布局折叠态文字和按钮拥挤回到单栏减少横向并排横竖切换页面状态丢失把选中项和输入态提升到页面层大屏触控关键按钮离手太远常用操作靠近主要阅读区折叠屏不是“越大越放更多东西”。展开态要提高效率但不能让用户找不到主操作。十、验证方法至少覆盖六个场景1. 折叠态进入页面检查单栏阅读顺序。 2. 展开到大屏检查是否切换双栏。 3. 从展开态重新折叠检查当前详情是否保留。 4. 悬停态放置设备检查上下分区是否合理。 5. 横竖屏切换检查按钮和标题是否被遮挡。 6. 输入文字后切换形态检查草稿是否保留。建议记录下面的数据指标检查点布局模式是否符合singleColumn/twoPane/hoverSplit状态保持当前选中项、滚动位置、草稿是否保留触控区域按钮是否可点、是否过小内容完整性标题、图片、底部按钮是否被裁切性能布局切换是否卡顿十一、常见问题排查现象可能原因检查方法修复建议展开态仍是手机布局没有正确计算窗口宽度打印widthVp接入窗口尺寸变化悬停态内容被切断没有单独处理姿态检查posture增加hoverSplit切换后详情丢失状态在子组件里查看选中项位置提升到页面状态双栏内容太挤左栏比例过大真机截图对比调整栏宽和断点按钮不易点击大屏触控距离过远手持测试操作区靠近内容十二、发布前验收清单检查项是否完成折叠态、展开态、悬停态都有独立布局策略布局判断集中在 Resolver不散落在组件里当前选中内容在形态切换后不丢失输入态、滚动位置、弹窗状态经过验证按钮没有落在不适合操作的区域横竖屏切换无明显错位和裁切真机或对应模拟器完成截图验证这张清单不要只在开发机上看一眼就结束。折叠屏适配最容易漏的是“形态切换过程”而不是某一个静态页面。建议每次验证都录一段从折叠态到展开态、再到悬停态的视频重点观察当前选中项、输入框内容、底部操作栏和弹窗位置是否稳定。如果产品有多条核心路径例如首页、详情页、编辑页、支付页不要只验证首页。折叠屏用户经常在任务进行中改变设备形态页面中间态比页面初始态更容易暴露问题。十三、总结HarmonyOS 折叠屏适配的核心不是“把页面撑满”而是根据设备形态重排内容折叠态保证单手阅读。展开态提高信息密度。悬停态拆分预览和操作。状态要跨布局保持。验证必须覆盖真实形态切换。把尺寸、姿态、断点、状态这四件事分开折叠屏适配才会稳定。