山海万灵 HarmonyOS 文化知识实战(02):响应式侧栏与多设备内容宽度
把“放大页面”改成“重算可用空间”文化知识应用的首页同时容纳探索路线、统计卡片、地图和底部导航。手机上最重要的是让单手浏览的内容不拥挤屏幕变宽后如果仍把全部区域按比例拉伸卡片之间会出现大块空白正文也会变成难以扫读的长行。真正需要响应的是页面结构而不是给每个组件单独写一组宽度。山海万灵把窗口宽高交给一个布局计算类。页面壳层只保存当前的screenWidthVp与screenHeightVp首页、图鉴、探索、馆长和“我的”都从同一个入口取得侧边距、内容宽度、卡片布局和底部安全留白。这样一来切换一级入口或进入详情时不会重新发明断点规则横向空间也不会被多个页面各自扣减。窗口条件页面壳层动作内容区结果宽度不足以放下侧栏保留底部导航内容从完整窗口宽度计算保持紧凑的阅读和点击节奏宽度与高度同时达到侧栏阈值为侧栏预留固定占位主内容不被导航覆盖内容区超过页面上限继续使用居中的最大内容宽度地图和说明文字不会无限拉长用宽度和高度共同决定导航形态只看宽度容易在矮窗口里误开侧栏导航虽然能排下内容和底部操作区却会互相挤压。因此侧栏判断同时约束宽度与高度。宽屏条件满足时壳层给侧栏留下 109vp 的占位不满足时这一占位为零内容可以完整使用窗口宽度。const SHANHAI_SIDE_NAV_MIN_WIDTH: number 840; const SHANHAI_SIDE_NAV_MIN_HEIGHT: number 520; const SHANHAI_SIDE_NAV_FOOTPRINT: number 109; static usesSideNavigation(windowWidthVp: number, windowHeightVp: number): boolean { return windowWidthVp SHANHAI_SIDE_NAV_MIN_WIDTH windowHeightVp SHANHAI_SIDE_NAV_MIN_HEIGHT; } static shellContentWidth(windowWidthVp: number, windowHeightVp: number): number { const sideNavigationWidth: number ShanhaiResponsive.usesSideNavigation(windowWidthVp, windowHeightVp) ? SHANHAI_SIDE_NAV_FOOTPRINT : 0; return Math.max(0, windowWidthVp - sideNavigationWidth); }这个判断把“导航在哪里”从各业务页面中移走。首页不需要知道设备类别图鉴也不需要自行扣除侧栏宽度它们只关心壳层已经算出的可用内容区。窗口高度不足时页面会自然回到不占侧栏宽度的形态避免一套只适合桌面比例的布局出现在窄高场景中。内容宽度、内边距与可读行长分开计算内容区宽度并不等于组件真实可用宽度。先扣除导航占位再以页面最大宽度限制阅读区域最后根据断点计算左右边距才能保证标题、地图说明和卡片正文都落在稳定的行长范围内。这里的“最大宽度”是阅读约束不是设备限制更大的窗口仍然可以展示更多背景与导航只是不让文字和交互卡片无限横向扩张。const SHANHAI_DEFAULT_PAGE_MAX_WIDTH: number 920; const SHANHAI_COMPACT_SIDE_INSET: number 14; const SHANHAI_MEDIUM_SIDE_INSET: number 20; const SHANHAI_WIDE_SIDE_INSET: number 24; static pageSideInset(windowWidthVp: number): number { if (windowWidthVp 840) return SHANHAI_WIDE_SIDE_INSET; if (windowWidthVp 600) return SHANHAI_MEDIUM_SIDE_INSET; return SHANHAI_COMPACT_SIDE_INSET; } static pageContentWidth(windowWidthVp: number, windowHeightVp: number): number { return Math.min( ShanhaiResponsive.shellContentWidth(windowWidthVp, windowHeightVp), SHANHAI_DEFAULT_PAGE_MAX_WIDTH ); } static pageInnerWidth(windowWidthVp: number, windowHeightVp: number): number { const pageWidth: number ShanhaiResponsive.pageContentWidth(windowWidthVp, windowHeightVp); return Math.max(0, pageWidth - ShanhaiResponsive.pageSideInset(windowWidthVp) * 2); }计算层次输入输出解决的问题壳层窗口宽高、侧栏判定shellContentWidth避免导航与正文重叠页面壳层宽度、页面最大宽度pageContentWidth控制长行文本与地图跨度组件页面宽度、左右内边距pageInnerWidth给卡片、网格和按钮稳定边界卡片列数从剩余宽度推导探索、馆长推荐和图鉴并不共用同一种卡片。通用功能卡以 420vp 为基准图鉴卡允许在更宽的内容区排成三列。两类组件都不直接读取屏幕宽度而是先使用已经扣除侧栏与内边距后的pageInnerWidth。这能避免一个页面刚打开侧栏另一个页面仍按旧宽度排两列的错位。static cardLayoutMode( windowWidthVp: number, windowHeightVp: number, baseCardWidth: number, gap: number 12 ): ShanhaiCardLayoutMode { const availableWidth: number ShanhaiResponsive.pageInnerWidth(windowWidthVp, windowHeightVp); if (availableWidth baseCardWidth) return COMPACT; if (availableWidth baseCardWidth * 2 gap) return PAIR; return SCALED_SINGLE; } static archiveColumns(windowWidthVp: number, windowHeightVp: number): number { const availableWidth: number ShanhaiResponsive.pageInnerWidth( windowWidthVp, windowHeightVp, 1360 ); if (availableWidth 420 * 3 12 * 2) return 3; if (availableWidth 420 * 2 12) return 2; return 1; }COMPACT、SCALED_SINGLE和PAIR把卡片的呈现选择限制在少量可解释状态空间不够时维持紧凑卡片中间区间使用单列缩放满足两张基准卡片与间距后再进入双列。图鉴使用同样的可用宽度原则但可根据档案页较大的内容上限返回一、二、三列。组件因此不必硬编码“手机”“平板”名称也能随分屏和浮窗的实际空间变化。页面壳层只分发结果不复制规则窗口变化发生在最外层容器。区域变化回调更新宽高状态后首页把已计算好的isWideScreen、内容宽度、边距和底部留白传给内容组件图鉴、探索和馆长页面则接收各自需要的网格或卡片模式。布局计算类保持纯函数使组件在状态刷新时只消费结果不在渲染过程中修改设备状态。.onAreaChange((_oldValue: Area, newValue: Area) { const width: number Math.round(Number(newValue.width)); const height: number Math.round(Number(newValue.height)); if (width 0 width ! this.screenWidthVp) { this.screenWidthVp width; } if (height 0 height ! this.screenHeightVp) { this.screenHeightVp height; } }) ShanhaiHomePageContent({ isWideScreen: this.isWideScreen(), contentWidth: this.pageContentWidth(), horizontalInset: this.pageSideInset(), bottomInset: this.pageBottomInset(), panelItemWidth: this.adaptivePanelItemWidth() })手机运行界面中探索路线、三项统计、地图和底部五个入口处于同一内容节奏底部导航没有压住“继续探索”操作区。进入更大的可用窗口时导航形态、边距、内容上限和卡片列数都沿着这条计算链重新得出而不是由业务页面追加一套特例。验收时关注空间契约而非单个像素响应式页面的回归重点是“同一份状态在不同可用空间中仍能阅读和操作”。可依次检查在窄窗口确认底部导航和操作区不覆盖在满足侧栏阈值的窗口确认主内容已扣除导航占位在图鉴页观察一、二、三列切换时项目宽度与间距是否稳定在窗口变化后返回首页确认探索进度和当前入口没有被布局刷新重置。还有一个容易被忽略的边界窗口宽度变化不应触发业务数据重载。布局函数只读取宽高并返回数字或枚举探索进度、已发现神兽和当前区域仍由页面状态与数据层维护。这样在旋转、分屏或浮窗调整时组件可以重新排版却不会把用户带回初始路线也不会因为一次尺寸变化重新请求内容。把尺寸计算与业务状态分离后布局问题更容易定位卡片过窄先检查可用宽度和间距数据不刷新再检查状态链路两类问题不会互相掩盖。HarmonyOS 的响应式布局能力可以参考 官方响应式布局指南。把空间计算收口到页面壳层之后后续增加地图详情、馆长推荐或知识图谱卡片时只需选择适合的布局函数而不需要在每个新页面重写设备分支。