HarmonyOS7 列表流实战-------Tab 吸顶页的骨架先别写乱
文章目录
源码获取
如果你想一边对照文章一边实操,建议直接把示例工程拉到本地。项目 Git 地址:https://gitcode.com/HarmonyOS_Samples/CommonListFlows。
ManagerPage 这页特别像很多人第一次做内容频道页时会写出来的“事故现场”。
顶部有搜索区,中间有头图,下面是一排内容分类,再往下还有内容列表,最底下甚至还有一层底部导航。东西一多,最容易发生的事不是不会写,而是层级写着写着就糊了。
所以这篇我不打算先讲细节,我先带你把骨架掰顺。

因为这类页面真正难的地方,从来都不是某个组件名,而是你有没有先把“谁包谁、谁压谁、谁滚谁”这几件事想明白。
先别被页面热闹吓到,它其实就四层
如果你直接盯源码,很容易觉得这页像一团线。
但你只要按层去看,它就突然没那么可怕了。ManagerPage 你可以先拆成四层:
- 最外层是
NavDestination - 里面包了一层底部
Tabs - 第一个底部页签内部用
Stack叠了两块区域 Stack上面是搜索区,下面是滚动内容区
核心结构大概就是这样:
NavDestination() {
Tabs({ barPosition: BarPosition.End, controller: this.tabsController }) {
TabContent() {
Stack({ alignContent: Alignment.Top }) {
Scroll(this.scrollController) {
Column() {
Image($r('app.media.pic5'))
Column() {
Row({ space: 16 }) {
// 内容分类 Tab
}
Tabs({ barPosition: BarPosition.Start, controller: this.contentTabController }) {
// 内容区
}
}
}
}
Row() {
// 顶部搜索区
}
}
}
.tabBar(this.tabBuilder($r('app.media.mine'), $r('app.string.tabBar1'), 0))
}
}
别急着记代码,先记这句更有用的话: 复杂页面先拆层,不要先抠细节。
为什么这里一定要用 Stack
很多人第一次看到 Stack 会觉得它有点多余,心里会冒出一句: 直接 Column 不行吗?
真不太行。
因为这个页面的搜索区不是普通内容块,它更像一个“压在内容上方”的区域。用户往下滑时,下面内容在动,但顶部搜索区需要稳定地待在最上面。

这时候 Stack({ alignContent: Alignment.Top }) 就特别顺手。它不是在炫技,而是在明确层级:
- 下层是可以滚动的内容
- 上层是固定在顶部的搜索区
这层关系一旦理顺,页面的气质就会稳定很多。
底部 Tabs 和中间那排文字 Tab,根本不是一回事
这是 ManagerPage 最容易把人看乱的点。
项目最外层先放了一组底部标签栏:
Tabs({ barPosition: BarPosition.End, controller: this.tabsController }) {
TabContent() {
// 首页内容
}
.tabBar(this.tabBuilder($r('app.media.mine'), $r('app.string.tabBar1'), 0))
TabContent() {
Text($r('app.string.tabBar2_content'))
}
.tabBar(this.tabBuilder($r('app.media.mine'), $r('app.string.tabBar2'), 1))
}
.barHeight(56)
.vertical(false)
.barMode(BarMode.Fixed)
这组 Tabs 是整页级别的大导航。你可以把它理解成 App 底部模块切换。
而页面中间那排“关注、热搜、推荐、更多”,它切的是当前页面内部的内容分类,不是整个页面模块。两层导航看着都叫 Tab,但职责完全不同。
这个边界你如果不先分清,后面读联动逻辑会特别乱。
中间那排文字 Tab,本质上是一个自定义分类条
项目没有直接用默认的 TabBar 展示内容分类,而是自己画了一排文字:
Row({ space: 16 }) {
ForEach(this.tabArray, (item: string, index: number) => {
Text(item)
.fontColor(this.currentTabIndex === index ? '#0a59f7' : Color.Black)
.onClick(() => {
this.contentTabController.changeIndex(index)
this.currentTabIndex = index
})
}, (item: string) => item)
}

这段写法特别像真实项目里的频道分类条。
因为很多时候,产品并不想要默认那套系统 Tab 样式,而是希望你自己控制字色、间距、选中状态、甚至交互动效。那最稳的办法,就是像这里一样: 显示层自己画,切换还是交给 TabsController。
为什么这页的骨架一定要按顺序搭
我很不建议你一上来就学着把 ManagerPage 一口气写完。
更稳的做法是按下面这个顺序走:
- 先把最外层底部
Tabs搭出来 - 再把第一页的
Stack结构搭出来 - 然后把顶部搜索区和滚动内容区分开
- 最后再往内容区里塞分类条、列表和联动逻辑
这个顺序特别重要。
因为一旦你先去抠内部列表,后面才发现外层滚动层级没搭好,返工会很烦。复杂页面一定先搭骨头,再长肉。
tabBuilder 单独抽出来,其实很像在给后面省命
项目把底部标签栏的每个按钮都统一交给 tabBuilder():
@Builder
tabBuilder(img: Resource, title: Resource, index: number) {
Column() {
Image(img)
.fillColor(this.currentIndex === index ? '#0a59f7' : '#66000000')
Text(title)
.fontColor(this.currentIndex === index ? '#0a59f7' : '#66000000')
}
.onClick(() => {
this.currentIndex = index
this.tabsController.changeIndex(this.currentIndex)
})
}
这种写法对教程特别友好,也对实战特别友好。
原因很简单: 底部导航一旦统一封装,后面无论你是改图标色、改文字色、改交互,都会轻松很多。你不会为了五个 Tab 去维护五份长得差不多的代码。
小白最该从这页学走的,不是某个属性名
而是一种拆页面的习惯。
这页真正教你的,是下面这几件事:
- 大导航和小导航要分层
- 固定顶层区域最好用
Stack管 - 可滚动内容区最好单独收口
- 自定义分类条和真实内容切换可以分开处理
这一套思路你一旦熟了,后面看复杂频道页、个人主页、内容流详情页,都会顺眼很多。
现在就能做的练手动作
如果你想马上上手,不用一口气全改,先做这三个最稳:
- 把底部导航从 5 项减成 3 项
- 把中间内容分类改成你自己的 3 个栏目
- 把顶部搜索区文案换成你自己的演示词
先做这种不伤筋骨的小改动,你会更容易建立对这页结构的控制感。
最后一句
ManagerPage 这种页面最怕的不是代码多,而是你一开始就没把层级想清楚。
只要你先把“外层导航、叠层结构、顶部区域、滚动内容区”这四件事看顺,后面的嵌套滚动、Tab 联动、自定义列表项就不会再像一团毛线。复杂页面没那么可怕,怕的是你一上来就冲进细节里打架。
更多推荐


所有评论(0)