HarmonyOS7 列表流实战-----分组列表吸顶原来就这几步
文章目录
源码获取
如果你想一边对照文章一边实操,建议直接把示例工程拉到本地。项目 Git 地址:https://gitcode.com/HarmonyOS_Samples/CommonListFlows。

第一次看到“分组标题吸顶”的页面,很多人会本能地觉得这玩意儿很高级。
然后脑子里立刻开始脑补: 要不要监听滚动距离,要不要自己算偏移,要不要动态改定位。结果还没写代码,人已经先把自己吓着了。
说实话,这种担心大多是想多了。
在 ArkUI 的 List 体系里,分组吸顶真正关键的不是你会不会手搓动画,而是你有没有把列表结构组织对。这个项目里的 HomePage 和 CityList,刚好把这件事讲得很明白。

吸顶这件事,难点不在动画,而在分组
先把概念掰直。
所谓分组吸顶,就是你往下滑的时候,当前分组标题固定在顶部,等下一个分组上来,再把它顶走。
这种交互非常常见:
- 城市选择页
- 通讯录
- 分类商品列表
- 按日期分组的消息页
所以你现在学的不是一个花哨技巧,而是一类特别高频的页面基础能力。
真正的前提不是 sticky,而是 ListItemGroup
先看 HomePage 里景区分组的写法:
ForEach(this.scenicSpotTitle, (item: Resource) => {
ListItemGroup({ header: this.scenicSpotHeader(item) }) {
ForEach(this.scenicSpotArray, (scenicSpotItem: Resource) => {
ListItem() {
this.scenicSpotDetailBuilder(scenicSpotItem)
}
}, (scenicSpotItem: Resource) => JSON.stringify(scenicSpotItem))
}
}, (item: Resource) => JSON.stringify(item))
这段代码最值得记住的不是遍历细节,而是结构关系:
- 外层按分类分组
- 每一组都用
ListItemGroup - 组里面再放具体
ListItem
这一步才是吸顶真正的地基。
因为框架只有先知道“这是一组内容,这是一组的头部”,后面才有可能帮你做吸顶。你如果连分组关系都没建立,光盯着吸顶效果本身,基本是在白忙活。

标题头为什么最好单独抽出来
项目把分组头专门写成了一个 Builder:
@Builder
scenicSpotHeader(title: Resource) {
Column() {
Text(title)
.width('100%')
.height(50)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.backgroundColor(0xF1F3F5)
}
}
这一步很实用。
因为分组标题通常不是只出现一次。你今天想改高度,明天想改背景色,后天又想加图标或者间距,如果一开始就散落在各处,后面会改得很烦。
抽出来以后,结构更清楚,维护也轻松。
真正让标题吸住顶部的,就这一行
很多人以为要写很长的滚动逻辑,其实项目里真正生效的是这句:
.sticky(StickyStyle.Header)
它直接挂在 List 上:
List({ space: 12 }) {
// 列表内容
}
.sticky(StickyStyle.Header)
说白了,ArkUI 已经把常见分组吸顶能力准备好了。你要做的不是重新发明一遍,而是把它需要的结构喂对。
所以这里的正确心态应该是:
- 先把分组关系建好
- 再把分组头定义清楚
- 最后让
List开启sticky
顺序别反。
如果只看一个页面来练,我更推荐 CityList
HomePage 里的分组吸顶是入门版,CityList 则更像标准模板。
它的核心结构是这样的:
List({ scroller: this.cityScroller }) {
ListItemGroup({ header: this.itemHead($r('app.string.current_city')) }) {
ListItem() {
Text(this.currentCity)
}
}
ListItemGroup({ header: this.itemHead($r('app.string.popular_cities')) }) {
ForEach(this.hotCities, (item: string) => {
ListItem() {
this.textContent(item)
}
})
}
ForEach(this.groupWorldList, (item: string) => {
ListItemGroup({ header: this.itemHead(item) }) {
ForEach(this.getCitiesWithGroupName(item), (cityItem: City) => {
ListItem() {
this.textContent(cityItem.city)
}
})
}
})
}
.sticky(StickyStyle.Header)
这页为什么特别适合拿来练?
因为结构太典型了:
- 上面是特殊分组,比如当前城市、热门城市
- 下面是按字母切开的标准分组
- 所有内容都统一放进
ListItemGroup - 吸顶逻辑完全交给
sticky
这是一种特别干净的示范。
你自己复刻时,最少得满足这三个条件
如果你准备照着做一个类似页面,至少要保证这三点同时成立:
- 外层容器是
List - 每一组内容都用
ListItemGroup - 组头通过
header提供,并在列表上开启.sticky(StickyStyle.Header)
少任何一个,效果都不会完整。
别在这一步偷懒。
有个细节很多人真会省,但我建议别省
那就是分组头的背景色。
你可能会觉得,标题都显示出来了,背景色有没有都差不多。真不是。
分组头一旦吸到顶部,它其实是在内容上方“悬着”的。背景如果不明确,下面列表内容会透出来,页面看着立刻廉价很多。这个项目里不管是景区标题还是城市标题,都专门给了背景色,这不是装饰,是经验。
这种结构以后能直接套到哪类页面
别把它只当城市页技巧。
这一套你以后完全可以原样迁过去:
- 城市选择页按字母分组
- 订单页按日期分组
- 消息中心按类型分组
- 商品列表按品类分组
- 文档中心按月份分组
它们本质一样,都是“同类内容归一组,组头负责提示,滚动时让组头持续在线”。
我最想提醒小白的三个误区
误区一: 以为吸顶一定要自己算滚动距离
普通分组吸顶真不用。先把结构写对,再谈更复杂的动画需求。
误区二: 把标题直接塞进 ListItem
视觉上看着像有标题,但框架并不知道它是“组头”,自然也不会帮你吸顶。
误区三: 标题区样式写得太敷衍
高度、背景、间距全乱来,最后吸顶动作虽然发生了,页面质感却很差。
给你一个非常适合练手的小作业
你可以在 CityList 上先做两件小事:
- 给字母标题提一点对比度,比如更粗的字重或者更明确的背景
- 给城市项加分割线或者卡片样式
这两个改动不会碰核心逻辑,但能让你很直接地感受到: 列表结构没变,页面气质却能差很多。
最后一句
分组吸顶这件事,真正难的从来不是 API,而是你有没有先把“分组”这件事想清楚。
一旦你接受“组用 ListItemGroup 管,吸顶交给 sticky”这套思路,很多原本看起来像高阶页面的东西会突然变简单。别自己先把它想复杂了,框架其实已经替你省了不少力气。
更多推荐


所有评论(0)