羽毛球工具 App HarmonyOS 6.0 实战(02/10):ArkUI 响应式布局
系列第 2 篇。本文聚焦一件事:不要为手机、平板、2in1 各写一套页面,而是在同一套业务页面上建立可维护的响应式外壳。
一、真实问题背景
羽毛球工具的使用场景天然跨设备:组织者可能用手机录入人员,场边可能用平板看对阵安排,2in1 设备适合横屏展示实时计分。如果只按手机竖屏设计,平板会显得空;如果按平板写死宽布局,手机又会拥挤。
当前项目在entry/src/main/module.json5中声明了phone、tablet、2in1。这不是为了“多写几个设备名”,而是让工程从配置层就承认多端体验是产品目标。
二、目标与边界
响应式适配要解决三个层次:
1. 设备宽度变化后,页面知道自己处在哪个断点。2. 主导航在小屏用底部 Tab,大屏用侧边导航。3. 业务页面内部使用相同状态,不因为布局切换丢数据。
边界是:本文不讲折叠屏姿态、不讲跨设备流转,也不讲手表端。这些属于后续协同体验文章。本文只讲当前 App 已经落地的 ArkUI 响应式基础。
三、断点系统设计
项目把断点监听放在common/src/main/ets/utils/BreakpointSystem.ets,而不是分散到每个页面。核心思路是:入口 Ability 初始化断点系统,断点变化写入AppStorage('currentBreakpoint'),页面通过@StorageProp读取。
private static smQuery: string = '(width<600vp)'; private static mdQuery: string = '(600vp<=width<840vp)'; private static lgQuery: string = '(840vp<=width<1080vp)'; private static xlQuery: string = '(1080vp<=width)'; BreakpointSystem.smListener = mediaquery.matchMediaSync(BreakpointSystem.smQuery); BreakpointSystem.smListener.on('change', (result: mediaquery.MediaQueryResult) => { if (result.matches) { BreakpointSystem.updateBreakpoint(BreakpointConstants.BREAKPOINT_SM); } });这个方案的优点是低侵入。业务页面不需要自己维护窗口监听,也不需要把窗口对象层层传参。只要读取currentBreakpoint,就能决定布局密度、边距、列数和导航方式。
四、入口页面切换导航
entry/src/main/ets/pages/Index.ets是主壳页面。它用@StorageLink('active_tab_index')维护当前 Tab,用@StorageProp('currentBreakpoint')读取断点。
@StorageLink('active_tab_index') currentIndex: number = 0; @StorageProp('currentBreakpoint') currentBreakpoint: string = 'sm'; isSmall(): boolean { return this.currentBreakpoint === BreakpointConstants.BREAKPOINT_SM; }构建 UI 时,小屏走底部 Tab,大屏走左侧导航。业务内容通过PageContent()分发,首页、对阵、计分、排名、我的页仍然是同一批 features 组件。
if (this.isSmall()) { Column() { this.PageContent(); Row() { this.BottomTabItem(this.tabs[0], 0); this.BottomTabItem(this.tabs[1], 1); this.BottomTabItem(this.tabs[2], 2); this.BottomTabItem(this.tabs[3], 3); this.BottomTabItem(this.tabs[4], 4); } } } else { Row() { Column() { this.SideNavItem(this.tabs[0], 0); this.SideNavItem(this.tabs[1], 1); this.SideNavItem(this.tabs[2], 2); this.SideNavItem(this.tabs[3], 3); this.SideNavItem(this.tabs[4], 4); } this.PageContent(); } }五、业务页面如何跟随
业务页面也读取currentBreakpoint。例如费用页、对阵页、排名页会根据断点调整 padding、最大宽度、卡片密度。我的页头像选择区域则进一步根据真实区域宽度计算列数,避免在平板上仍然按手机三列挤在一起。
相关源码包括:
-features/src/main/ets/home/HomePage.ets-features/src/main/ets/fee/FeeInputPage.ets-features/src/main/ets/stats/StatsPage.ets-features/src/main/ets/me/MePage.ets-common/src/main/ets/utils/BreakpointConstants.ets
这类页面适配最容易踩的坑是“只改外壳,不改内容”。外壳变宽后,如果内部仍然写死宽度或用过大的字号,平板体验依旧不自然。
六、取舍与风险
当前项目选择mediaquery + AppStorage,没有引入更复杂的设备类型判断。这样做的取舍是:宽度优先,逻辑简单,适合工具型 App;缺点是无法直接表达折叠态、悬浮窗态、外接屏状态。后续如果要做平板大屏记分牌或跨设备展示,需要增加更细的窗口上下文。
另一个风险是@Builder中读取响应式状态时,可能出现状态变化但局部组件不刷新的情况。项目的做法是尽量把依赖@StorageProp的布局判断放在主 build 分支,或者显式传参给 Builder。
七、构建验证
验证命令:
& 'D:\HuaweiDevelopFormalStudy\DevEco Studio\tools\hvigor\bin\hvigorw.bat' assembleHap --mode module -p product=default --no-daemon验证时间:2026-06-28。当前结果为BUILD SUCCESSFUL。截图来自doc/screenshots_current/phone和doc/screenshots_current/tablet,用于证明不是只在代码里写了断点,而是真的覆盖了手机和平板两种显示状态。
八、官方参考
ArkUI 响应式布局和媒体查询能力应以官方文档为准,可从 HarmonyOS ArkUI 指南 进入,并结合当前 SDK 的@kit.ArkUIAPI 说明核对。
九、工程验收清单
-module.json5声明phone/tablet/2in1。-BreakpointSystem统一维护断点,不在每个页面重复监听。- 主壳页面小屏底部导航,大屏侧边导航。- 业务页面通过@StorageProp('currentBreakpoint')跟随布局。- 手机和平板截图都能对应到同一套源码。
十、小结
响应式布局不是把元素变大,也不是给平板加几个空白。更实用的方式是:状态统一、导航换形、内容调密度。这个羽毛球工具的实现还不复杂,但已经足够支撑后续跨设备文章:手机控制、平板展示、手表计分都可以继续复用这套状态和页面分层。
十一、下一篇衔接
下一篇讲本地优先数据方案:为什么这个 App 用Preferences + AppStorage管住对局、费用和设置,而不是让每个页面自己保存一份状态。
