
文章目录前言鸿蒙的多设备生态mediaquery 媒体查询断点系统 (Breakpoint) 设计GridRow/GridCol 栅格布局实战新闻详情页的多设备适配一些踩坑经验前言鸿蒙生态里最让人头大的事情之一就是多设备适配。你写的页面在手机上跑得好好的一上平板就左边空一大片折叠屏展开更是直接社死。我之前做项目的时候一开始偷懒只做了手机适配后来产品说要适配平板和折叠屏差点推倒重来。后来系统学了一遍断点体系和栅格布局才算真正搞定。今天就聊聊这个话题。鸿蒙的多设备生态HarmonyOS 7 支持的设备形态相当丰富手机、平板、折叠屏、2in1 笔记本、智慧屏、车机。作为开发者你最常打交道的就是前四种。这几种设备的屏幕宽度差异巨大设备类型典型宽度 (vp)手机360 ~ 412折叠屏展开580 ~ 840| 平板 | 600 ~ 1280 || 2in1 | 800 ~ 1440 |如果写死布局根本没法玩。所以我们需要一套响应式方案。mediaquery 媒体查询ohos.mediaquery是鸿蒙提供的媒体查询能力可以监听屏幕宽度、高度、密度、方向等变化。基本用法很简单importmediaqueryfromohos.mediaqueryEntryComponentstruct ResponsivePage{StatescreenWidth:number0privatelistener:mediaquery.MediaQueryListener|nullnullaboutToAppear(){// 监听屏幕宽度大于等于 600vp 的情况this.listenermediaquery.matchMediaSync((width600vp))this.listener.on(change,(result:mediaquery.MediaQueryResult){if(result.matches){this.screenWidth600// 大屏模式}else{this.screenWidth360// 小屏模式}})}aboutToDisappear(){this.listener?.off(change)}build(){Column(){if(this.screenWidth600){Text(大屏模式)}else{Text(小屏模式)}}}}这里有个坑matchMediaSync的回调是在主线程执行的别在里面做太重的操作不然会卡 UI。断点系统 (Breakpoint) 设计光用 mediaquery 还不够系统化。做大型项目的时候我们需要一套断点规范。鸿蒙推荐的做法是定义三到四个断点// 断点定义exportenumBreakpointType{SMsm,// 手机竖屏 600vpMDmd,// 折叠屏/手机横屏 600~840vpLGlg,// 平板 840~1080vpXLxl,// 2in1/大屏 1080vp}实际项目中我会封装一个断点监听的 Hookimportmediaqueryfromohos.mediaqueryexportclassBreakpointSystem{privatestaticlisteners:Mapstring,mediaquery.MediaQueryListenernewMap()privatestaticcallbacks:((bp:BreakpointType)void)[][]staticinit(){construles:[string,BreakpointType][][[(width600vp),BreakpointType.SM],[(600vpwidth840vp),BreakpointType.MD],[(840vpwidth1080vp),BreakpointType.LG],[(width1080vp),BreakpointType.XL],]rules.forEach(([query,bp]){constlistenermediaquery.matchMediaSync(query)listener.on(change,(result:mediaquery.MediaQueryResult){if(result.matches){BreakpointSystem.callbacks.forEach(cbcb(bp))}})BreakpointSystem.listeners.set(query,listener)})}staticonChange(callback:(bp:BreakpointType)void){BreakpointSystem.callbacks.push(callback)}staticdestroy(){BreakpointSystem.listeners.forEach((listener){listener.off(change)})BreakpointSystem.listeners.clear()BreakpointSystem.callbacks[]}}然后在页面里用起来EntryComponentstruct MyPage{Statebreakpoint:BreakpointTypeBreakpointType.SMaboutToAppear(){BreakpointSystem.init()BreakpointSystem.onChange((bp){this.breakpointbp})}aboutToDisappear(){BreakpointSystem.destroy()}build(){Column(){// 根据断点决定列数if(this.breakpointBreakpointType.SM){this.SmallLayout()}elseif(this.breakpointBreakpointType.MD){this.MediumLayout()}else{this.LargeLayout()}}}BuilderSmallLayout(){List(){ForEach(dataList,(item:NewsItem){ListItem(){NewsCard(item)}})}}BuilderMediumLayout(){// 两列瀑布流WaterFlow(){ForEach(dataList,(item:NewsItem){FlowItem(){NewsCard(item)}})}.columnsTemplate(1fr 1fr)}BuilderLargeLayout(){// 左侧导航 右侧三列内容Row(){SideBar()WaterFlow(){ForEach(dataList,(item:NewsItem){FlowItem(){NewsCard(item)}})}.columnsTemplate(1fr 1fr 1fr)}}}GridRow/GridCol 栅格布局鸿蒙的GridRowGridCol组件提供了类似 Bootstrap 的栅格系统总共 24 列支持按断点设置每列占几格。EntryComponentstruct GridDemo{build(){Scroll(){GridRow({columns:24,gutter:12}){// 手机占满整行平板占一半大屏占三分之一GridCol({span:{sm:24,md:12,lg:8}}){Card(){Text(模块 A)}.width(100%)}GridCol({span:{sm:24,md:12,lg:8}}){Card(){Text(模块 B)}.width(100%)}GridCol({span:{sm:24,md:24,lg:8}}){Card(){Text(模块 C)}.width(100%)}}.padding(16)}}}这个栅格系统的好处是声明式的不需要你自己去监听断点变化再手动计算宽度系统自动帮你搞定。实战新闻详情页的多设备适配来看一个完整的例子。新闻详情页在手机上是单列瀑布流平板变成左右分栏列表详情折叠屏展开则是三栏布局EntryComponentstruct NewsPage{Statebreakpoint:BreakpointTypeBreakpointType.SMStateselectedNews:NewsItem|nullnullprivatenewsList:NewsItem[]getMockNews()aboutToAppear(){BreakpointSystem.init()BreakpointSystem.onChange((bp){this.breakpointbp})}aboutToDisappear(){BreakpointSystem.destroy()}build(){Row(){// 导航栏仅大屏显示if(this.breakpointBreakpointType.XL){NavSidebar().width(200)}// 列表区域if(this.breakpointBreakpointType.SM){// 手机全宽列表点击进入详情this.NewsListView()}else{// 平板及以上左侧列表this.NewsListView().width(this.breakpointBreakpointType.XL?30%:40%)// 右侧详情if(this.selectedNews){NewsDetail(this.selectedNews).width(this.breakpointBreakpointType.XL?50%:60%)}}}.width(100%).height(100%)}BuilderNewsListView(){List({space:8}){ForEach(this.newsList,(item:NewsItem){ListItem(){NewsCard(item).onClick((){if(this.breakpoint!BreakpointType.SM){this.selectedNewsitem}else{// 手机端跳转新页面router.pushUrl({url:pages/NewsDetail,params:{id:item.id}})}})}})}.padding(12)}}核心思路就两条小屏用跳转大屏用分栏。手机上列表和详情分两个页面平板以上直接左右分栏。断点驱动布局切换。通过BreakpointSystem统一管理断点状态页面根据State响应式刷新。一些踩坑经验折叠屏内外屏切换折叠屏合上和展开是两种完全不同的宽度你的断点监听要能覆盖到这两种情况。上面封装的BreakpointSystem天然支持因为它监听的是实时宽度变化。GridCol 的 span 不支持动画栅格列数切换时没有过渡动画如果想平滑过渡得自己用animateTo包一下。别在 builder 里做复杂判断Builder里的条件判断越简单越好复杂的逻辑放到计算属性或者State变量里预处理。模拟器不够用DevEco Studio 的模拟器只能模拟固定分辨率建议用 Previewer 的多设备预览功能快速检查各个断点下的效果。多设备适配是鸿蒙开发者的必修课。与其等设备多了再补不如一开始就把断点体系搭好。前期多花一天时间设计断点规范后期能省一周的返工时间。我的建议是先把 sm/md/lg 三个断点跑通xl 后面再加。