![# 平板分栏一打开右侧为什么整片空白HarmonyOS 7 splitPlaceholder 与默认详情路由实战同一套菜谱页在手机上没有问题到了平板或折叠屏展开态却像“少渲染了一半”左侧分类列表正常右侧内容区是一整块空白用户拖动分栏线后详情区还可能被压得只剩标题和半个按钮。日志没有崩溃路由表也能找到详情页问题却稳定复现。这类故障通常不是 NavDestination 构建失败而是把三个不同状态混成了一个Navigation 已进入分栏模式、NavPathStack 仍为空、内容区又没有默认占位。HarmonyOS 当前官方文档提供 splitPlaceholder 处理“分栏 空栈”展示同时用 navBarWidthRange 和 minContentWidth 约束可拖拽范围。需要特别注意splitPlaceholder 只是 UI 展示页不属于路由栈也不能获焦或响应事件。本文交付一套可直接复用的分栏切片空栈占位、可操作默认详情、拖拽宽度约束、状态日志和纯函数断言。当前机器只有 API 24 SDK 且没有 HDC 设备因此下列代码依据当前官方 API 设计但不声称完成 API 26 编译或真机分栏验证。先把四个状态跑清楚不要一上来就改路由表。准备一个只有 6 条菜谱的最小页面用同一个 NavPathStack 依次执行四条路径。路径Navigation 状态路由栈预期画面A单栏空显示左侧首页内容不展示 splitPlaceholderB分栏空左侧列表 右侧静态占位不出现整片空白C分栏1 个详情左侧列表 右侧 NavDestination 详情D分栏拖拽1 个详情navBar 与内容区都不小于约束值如果路径 B 为空白先查 splitPlaceholder如果 B 正常而 C 空白再查 navDestination 映射和 pushPathByName如果 D 才坏查 navBarWidthRange、minContentWidth 和外层容器实际宽度。三类问题不要用同一个“路由失败”概括。splitPlaceholder 不是默认详情页官方分栏指南说明Navigation 的 mode 可以控制单栏、分栏、自适应和按高宽比自适应。分栏模式下navBar 固定显示NavDestination 在内容区展示。栈为空时内容区默认可能留白splitPlaceholder 用来提供占位画面。但占位页有两个硬边界只在分栏模式且路由栈为空时出现。只负责展示不受路由栈管理不可获焦也不能响应事件。因此占位页适合放“从左侧选择一道菜”“当前没有选中项”这类说明不适合放“立即打开默认菜谱”按钮。若产品要求用户一进入平板页就能操作默认详情应显式 push 一条真实路由而不是给占位页绑定点击事件。案例一菜谱分类页用静态占位消除空白先定义一个没有交互的占位 Builder再创建 ComponentContent。占位里只表达当前状态不放按钮、输入框和焦点控件。import{ComponentContent}fromkit.ArkUI;BuilderfunctionRecipeSplitPlaceholder(){Column({space:18}){SymbolGlyph($r(sys.symbol.rectangle_3_group)).fontSize(52).fontColor([$r(sys.color.font_secondary)])Text(从左侧选择一道菜).fontSize(22).fontWeight(FontWeight.Medium)Text(这里会显示食材、步骤和烹饪提示).fontSize(15).fontColor($r(sys.color.font_secondary))}.justifyContent(FlexAlign.Center).width(100%).height(100%)}interfaceRecipeRouteParam{recipeId:string;title:string;}页面持有唯一 NavPathStack。左侧列表点击时才把 RecipeDetail 推进栈。mode 使用 AUTO_WITH_ASPECT_RATIO 交给 Navigation 根据当前形态选择单双栏拖拽条、导航栏宽度范围和内容区最小宽度作为一组配置出现。EntryComponentstruct RecipeLibraryPage{privatepathStack:NavPathStacknewNavPathStack();privateplaceholder:ComponentContentnewComponentContent(this.getUIContext(),wrapBuilder(RecipeSplitPlaceholder));privaterecipes:RecipeRouteParam[][{recipeId:mapo-tofu,title:麻婆豆腐},{recipeId:steamed-fish,title:清蒸鲈鱼},{recipeId:lotus-soup,title:莲藕排骨汤}];BuilderprivatenavList(){List(){ForEach(this.recipes,(item:RecipeRouteParam){ListItem(){Text(item.title).width(100%).padding(18)}.onClick((){this.pathStack.pushPathByName(RecipeDetail,item);})},(item:RecipeRouteParam)item.recipeId)}}BuilderprivatepageMap(name:string,param:object){if(nameRecipeDetail){RecipeDetailPage({param:paramasRecipeRouteParam})}}build(){Navigation(this.pathStack){this.navList()}.mode(NavigationMode.AUTO_WITH_ASPECT_RATIO).navDestination(this.pageMap).enableDragBar(true).navBarWidthRange([280,420]).minContentWidth(420).splitPlaceholder(this.placeholder).hideTitleBar(true).width(100%).height(100%)}}这段代码的关键不是“加了一个占位组件”而是把空栈状态公开表达出来。用户尚未选择菜谱时看到说明点击列表后真实 NavDestination 取代占位回退到空栈时占位再次出现。占位本身从不伪装成可操作页面。怎么验证宽屏启动后不点击任何列表项确认右侧出现占位。点击“麻婆豆腐”记录 pathStack.size() 从 0 变为 1右侧显示 RecipeDetail。执行 pop确认栈回到 0右侧重新显示占位。切为单栏确认占位不在首页上叠加。不要只截最终详情页。空栈、入栈和出栈三个时间点都要有状态记录才能证明不是偶然渲染成功。案例二默认就要可操作时推真实路由另一个需求是“平板打开后默认显示热门菜谱用户能立即收藏或加入购物清单”。这时 splitPlaceholder 不合适因为官方明确说明它不可获焦和响应事件。正确做法是在满足业务条件时只推一次默认路由。EntryComponentstruct FeaturedRecipePage{privatepathStack:NavPathStacknewNavPathStack();StateprivatedefaultRoutePrepared:booleanfalse;aboutToAppear():void{this.openDefaultDetailOnce();}privateopenDefaultDetailOnce():void{if(this.defaultRoutePrepared||this.pathStack.size()0){return;}this.defaultRoutePreparedtrue;constparam:RecipeRouteParam{recipeId:featured-today,title:今日推荐};this.pathStack.pushPathByName(RecipeDetail,param,false);}build(){Navigation(this.pathStack){RecipeCategoryList({pathStack:this.pathStack})}.mode(NavigationMode.AUTO_WITH_ASPECT_RATIO).navDestination(this.pageMap).enableDragBar(true).navBarWidthRange([280,420]).minContentWidth(420)}BuilderprivatepageMap(name:string,param:object){if(nameRecipeDetail){RecipeDetailPage({param:paramasRecipeRouteParam})}}}defaultRoutePrepared 防止 aboutToAppear 再次触发时重复 push。与此同时pathStack.size() 保护用户已经恢复出的路由栈如果应用从后台回来时已有详情不能再把“今日推荐”压到栈顶。两种产品语义要明确区分需求实现是否进入路由栈能否交互尚未选择给出说明splitPlaceholder否否默认展示可收藏详情pushPathByName是是拖拽分栏线时宽度约束要一起设计只开启 enableDragBar 但不定义边界用户可能把左侧拖得过宽导致详情区按钮折行、图片被挤压或表格横向溢出。navBarWidthRange 约束导航栏可调范围minContentWidth 约束右侧内容区最小宽度。它们必须结合页面实际最小可用尺寸设计。例如外层可用宽度是 980vp导航栏范围为 280–420vp内容区最小 420vp剩余空间可以覆盖分割线和两侧最小值。若外层只有 700vp却仍要求 420vp 详情区和 420vp 导航栏上限系统最终表现会受到模式切换与布局约束共同影响。不要把“设置了范围”误写成“任何尺寸都能维持双栏”。可以在业务侧保留一个纯函数只做配置预检不模拟 Navigation 内部算法typeResolvedModeStack|Split;interfaceSplitSnapshot{resolvedMode:ResolvedMode;stackSize:number;hostWidth:number;navWidth:number;minContentWidth:number;dividerWidth:number;}interfaceSplitAssessment{state:stack-home|split-placeholder|split-detail|split-width-risk;contentWidth:number;reasons:string[];}functionassessSplit(snapshot:SplitSnapshot):SplitAssessment{if(snapshot.resolvedModeStack){return{state:stack-home,contentWidth:snapshot.hostWidth,reasons:[]};}constcontentWidthsnapshot.hostWidth-snapshot.navWidth-snapshot.dividerWidth;if(contentWidthsnapshot.minContentWidth){return{state:split-width-risk,contentWidth,reasons:[content-below-minimum]};}if(snapshot.stackSize0){return{state:split-placeholder,contentWidth,reasons:[empty-stack]};}return{state:split-detail,contentWidth,reasons:[]};}这里的 resolvedMode 应来自实际运行状态或测试场景输入不能由这段函数替系统猜测 AUTO_WITH_ASPECT_RATIO 的切换阈值。它只验证“已进入分栏后当前宽度与栈状态应该对应哪种业务画面”。四组宿主断言conststackHomeassessSplit({resolvedMode:Stack,stackSize:0,hostWidth:380,navWidth:0,minContentWidth:420,dividerWidth:0});if(stackHome.state!stack-home)thrownewError(单栏首页误判);constemptySplitassessSplit({resolvedMode:Split,stackSize:0,hostWidth:980,navWidth:320,minContentWidth:420,dividerWidth:1});if(emptySplit.state!split-placeholder)thrownewError(空栈占位误判);constdetailSplitassessSplit({resolvedMode:Split,stackSize:1,hostWidth:980,navWidth:360,minContentWidth:420,dividerWidth:1});if(detailSplit.state!split-detail)thrownewError(详情状态误判);constwidthRiskassessSplit({resolvedMode:Split,stackSize:1,hostWidth:700,navWidth:360,minContentWidth:420,dividerWidth:1});if(widthRisk.state!split-width-risk)thrownewError(窄内容区没有被拦截);这些断言能检查团队自己的宽度预算和状态映射不能证明 Navigation 的高宽比模式切换、拖拽手势、焦点行为或动画已经在设备上通过。日志只记三个事实分栏故障最怕日志里只有“页面打开成功”。建议每次模式或栈变化时记录NavigationSnapshot resolvedModeSplit stackSize0 hostWidth980 navWidth320 contentWidth659 expectedStatesplit-placeholder当用户反馈右侧空白时这组信息可以快速区分resolvedModeStack不是分栏空栈问题。resolvedModeSplit 且 stackSize0应检查占位是否注入。resolvedModeSplit 且 stackSize0应检查 pageMap 与 NavDestination。contentWidth 小于业务最小值应检查拖拽范围和外层宽度。不要在日志中输出完整菜谱参数、用户输入或账号数据。route name、栈大小和尺寸已经足够定位结构问题。六个高频错误1. 把占位页做成可点击卡片splitPlaceholder 不可获焦也不能响应事件。需要交互就推真实 NavDestination不要让视觉看起来像按钮却无法点击。2. 空栈时自动 push每次前台恢复又 push 一次默认详情必须具备一次性门禁并先检查 pathStack.size()。否则生命周期重复触发会制造重复详情和异常返回链路。3. 只设置 navBarWidthRange不设置 minContentWidth左侧看似可以拖动右侧却可能被压到业务不可用。导航栏范围和内容区最低宽度应同时进入设计验收。4. 用窗口宽度代替 Navigation 实际宽度Navigation 可能位于更小的容器、分屏窗口或弹性布局中。验收要记录实际 hostWidth不要只记设备分辨率。5. hideNavBar 与 Split 语义冲突官方指南指出Split 模式同时 hideNavBar(true) 时实际会表现为单栏。若产品临时隐藏左侧栏需要重新核对占位和详情的展示预期。6. 把占位看成路由恢复结果占位不在路由栈中。进程恢复、跨设备迁移或状态持久化时应恢复真实 pathStack 数据看到占位只能说明当前是分栏空栈不能说明详情恢复成功。可复用目录features/recipe-library/ RecipeLibraryPage.ets # Navigation 根容器和分栏参数 RecipeSplitPlaceholder.ets # 纯展示占位不含交互 RecipeDetailPage.ets # 可操作 NavDestination RecipeRouteParam.ets # 明确的路由参数模型 SplitLayoutPolicy.ts # 宽度预算与状态判断 split_layout_policy.test.ts # 四组宿主断言 docs/navigation-evidence.md # 模式、栈、宽度和截图记录路由根只维护栈和页面映射占位只表达空状态详情页负责收藏、购物清单等交互宽度策略独立测试。四者拆开后空白、路由失败和拖拽挤压才不会混成一个问题。上线前检查表Navigation 使用唯一 NavPathStack未在子页面各建一套互不相干的栈。分栏空栈时配置 splitPlaceholder不再展示整片空白。占位页没有按钮、输入框、焦点或点击承诺。需要默认交互详情时使用 pushPathByName并有只执行一次的门禁。navDestination 能按 route name 构建真实 NavDestination。navBarWidthRange 与 minContentWidth 经过同一宽度预算核对。拖拽到左右边界时标题、图片、按钮和表格均不被截断。单栏、分栏空栈、分栏详情、pop 回空栈四条路径都已截图。日志记录实际模式、栈大小和容器宽度不记录用户敏感数据。API 26 工程编译、真机/模拟器焦点和拖拽结果单独留证。验证边界本文依据当前华为官方 Navigation 分栏指南、空白页 FAQ、组件导航说明和 HarmonyOS 7 升级适配资料整理。宿主纯函数断言只验证宽度预算与业务状态映射不代替 API 26 编译、AUTO_WITH_ASPECT_RATIO 实际模式选择、ComponentContent 生命周期、设备焦点、拖拽动画或路由恢复测试。官方资料Navigation 分栏开发Navigation 分栏模式下页面大量留白 FAQ组件导航和页面路由概述Navigation 跨包路由与 NavPathStackHarmonyOS 7 / API 26 升级适配指南结论平板分栏右侧空白并不等于详情页构建失败。先确认 Navigation 是否处于分栏再看 NavPathStack 是否为空空栈只需静态说明时使用 splitPlaceholder需要默认可操作内容时推真实路由同时用 navBarWidthRange 和 minContentWidth 守住拖拽边界。把“模式、栈、宽度”三件事分别记录才有可能一次定位而不是在路由表、图片加载和布局参数之间来回猜。](https://i-blog.csdnimg.cn/direct/1ac0d3e0781d4c74900973ce69ebdaf3.jpeg#pic_center)