文章目录源码获取吸顶这件事难点不在动画而在分组真正的前提不是 sticky而是 ListItemGroup标题头为什么最好单独抽出来真正让标题吸住顶部的就这一行如果只看一个页面来练我更推荐 CityList你自己复刻时最少得满足这三个条件有个细节很多人真会省但我建议别省这种结构以后能直接套到哪类页面我最想提醒小白的三个误区误区一: 以为吸顶一定要自己算滚动距离误区二: 把标题直接塞进 ListItem误区三: 标题区样式写得太敷衍给你一个非常适合练手的小作业最后一句源码获取如果你想一边对照文章一边实操建议直接把示例工程拉到本地。项目 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:BuilderscenicSpotHeader(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顺序别反。如果只看一个页面来练我更推荐CityListHomePage里的分组吸顶是入门版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”这套思路很多原本看起来像高阶页面的东西会突然变简单。别自己先把它想复杂了框架其实已经替你省了不少力气。