CommonListFlows — HarmonyOS List 组件四大经典场景实战解析
项目地址:https://gitcode.com/harmonyos_samples/CommonListFlows 技术栈:HarmonyOS / ArkTS / ArkUI
项目简介
在移动应用开发中,列表流是最常见、最核心的 UI 布局形式之一。无论是电商应用的商品列表、社交应用的信息流,还是工具类应用的城市选择器,都离不开列表组件的支撑。HarmonyOS 提供了强大的 List 组件,支持垂直方向线性排列子组件,能够高效渲染任意数量的图文视图。
CommonListFlows 是 HarmonyOS 官方示例项目,专注于展示基于 List 组件的四大经典列表流场景:
- 多类型列表项场景:一个列表中混合展示轮播图、宫格、图文卡片等多种布局形式
- Tab 吸顶场景:下滑时 Tab 栏固定在顶部,内容区域继续滚动
- 分组吸顶场景:城市列表按字母分组,组标题随滚动吸顶,支持右侧字母导航
- 二级联动场景:左侧导航与右侧内容联动,点击导航跳转对应内容,滚动内容高亮对应导航
本项目代码简洁、注释清晰,是学习和参考 HarmonyOS 列表开发的优质素材。

上图展示了应用首页,四个按钮分别对应四大列表流场景。
功能概览
| 场景 | 核心组件 | 关键技术 |
|---|---|---|
| 多类型列表项 | List + ListItem + ListItemGroup | 下拉刷新、上拉加载、吸顶效果 |
| Tab 吸顶 | Tabs + Scroll + List | nestedScroll 嵌套滚动、自定义 TabBar |
| 分组吸顶 | List + ListItemGroup | 字母索引导航、分组吸顶 |
| 二级联动 | List + ListItemGroup | 双列表联动、滚动位置同步 |
工程结构
entry/src/main/ets
├── entryability
│ └── EntryAbility.ets // 程序入口
├── entrybackupability
│ └── EntryBackupAbility.ets // 备份能力
├── pages
│ ├── Index.ets // 首页(导航入口)
│ ├── HomePage.ets // 多类型列表项场景
│ ├── ManagerPage.ets // Tab 吸顶场景
│ ├── CityList.ets // 分组吸顶场景
│ ├── CategoryPage.ets // 二级联动场景
│ └── CustomListItem.ets // 自定义列表项组件
└── model
└── LinkDataModel.ets // 二级联动场景数据模型核心实现解析
场景一:多类型列表项(HomePage.ets)
多类型列表项场景模拟了一个典型的电商首页,在一个 List 组件中混合展示了轮播图、宫格入口、双列图片和景区分类列表四种不同的布局形式。

上图展示了多类型列表项场景,包含搜索栏、轮播图、宫格入口、双列图片和景区列表。
核心代码:
List({ space: 12 }) {
// 轮播图
ListItem() {
Swiper() {
ForEach(this.swiperContent, (item: SwiperType) => {
Stack({ alignContent: Alignment.BottomStart }) {
Image($r(item.pic))
}
})
}
.autoPlay(true)
.duration(1000)
.indicator(new DotIndicator().selectedColor(Color.White))
}
// 宫格入口
ListItem() {
Grid() {
ForEach(this.gridTitle, (item: Resource) => {
GridItem() {
Column() {
Image($r('app.media.pic1'))
.width(44).height(44).borderRadius(22)
Text(item).fontSize(12)
}
}
})
}
.columnsTemplate('1fr 1fr 1fr 1fr 1fr')
}
// 景区分类列表(使用 ListItemGroup 实现分组吸顶)
ForEach(this.scenicSpotTitle, (item: Resource) => {
ListItemGroup({ header: this.scenicSpotHeader(item) }) {
ForEach(this.scenicSpotArray, (scenicSpotItem: Resource) => {
ListItem() {
this.scenicSpotDetailBuilder(scenicSpotItem);
}
})
}
})
}
.sticky(StickyStyle.Header) // 分组标题吸顶
.onReachEnd(() => { // 上拉加载更多
if (this.scenicSpotArray.length >= 20) {
this.noMoreData = true;
return;
}
setTimeout(() => {
this.scenicSpotArray.push('scenic area' + (this.scenicSpotArray.length + 1));
}, 500)
})技术要点:
- 混合布局:通过
ListItem包裹不同类型的子组件(Swiper、Grid、Column),实现一个列表中展示多种布局形式 - 下拉刷新:使用
Refresh组件包裹List,通过onRefreshing回调模拟网络请求 - 上拉加载:监听
onReachEnd事件,在列表滚动到底部时自动加载更多数据 - 分组吸顶:
ListItemGroup的header属性配合sticky(StickyStyle.Header),实现景区分类标题吸顶效果
场景二:Tab 吸顶(ManagerPage.ets)
Tab 吸顶场景模拟了新闻类应用的常见布局:顶部搜索栏 + 内容区域 + 底部 TabBar。当用户下滑时,顶部的分类 Tab("关注"、"热搜"、"推荐"、"更多")会固定在屏幕顶部。

上图展示了 Tab 吸顶场景,包含搜索栏、Banner、Tab 分类栏和底部 TabBar。
核心代码:
Stack({ alignContent: Alignment.Top }) {
// 内容区域(可滚动)
Scroll(this.scrollController) {
Column() {
Image($r('app.media.pic5')) // Banner 图
.width('100%').height(186)
// 自定义 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;
})
})
}
// Tabs 嵌套 List(实现 Tab 内容滚动)
Tabs({ barPosition: BarPosition.Start, controller: this.contentTabController }) {
TabContent() {
List({ space: 10, scroller: this.listScroller }) {
CustomListItem({ imgUrl: $r('app.media.pic1'), title: $r('app.string.manager_content') })
// ... 更多列表项
}
.nestedScroll({
scrollForward: NestedScrollMode.PARENT_FIRST,
scrollBackward: NestedScrollMode.SELF_FIRST
})
}
.tabBar('follow')
// ... 其他 TabContent
}
}
}
// 顶部搜索栏(固定在 Stack 顶部)
Row() {
Image($r('app.media.app_icon')).width(30).height(30)
Search({ placeholder: $r('app.string.want_search') })
.width('100%').height(40).layoutWeight(1)
Text($r('app.string.search'))
}
.width('100%').height(60)
}技术要点:
- Stack 层级布局:使用
Stack将搜索栏固定在顶部,内容区域在下方滚动 - 嵌套滚动:
nestedScroll属性设置PARENT_FIRST和SELF_FIRST,实现外层 Scroll 与内层 List 的协调滚动 - 自定义 TabBar:通过
Row+Text实现自定义 TabBar,点击时切换TabsController的索引 - 底部导航:外层
Tabs组件实现底部 TabBar 导航
场景三:分组吸顶(CityList.ets)
分组吸顶场景模拟了城市选择器,左侧是按字母分组的城市列表,右侧是字母导航栏。滑动城市列表时,组标题会吸顶显示;点击右侧字母,左侧列表会滚动到对应位置。

上图展示了分组吸顶场景,包含当前城市、热门城市和按字母分组的城市列表,右侧有字母导航。
核心代码:
Stack({ alignContent: Alignment.End }) {
// 城市列表
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) // 组标题吸顶
.onScrollIndex((index: number) => {
// 滚动时同步更新右侧导航高亮
this.selectNavIndex = index - 2;
})
// 右侧字母导航
Column() {
List({ scroller: this.navListScroller }) {
ForEach(this.groupWorldList, (item: string, index: number) => {
ListItem() {
Text(item)
.fontColor(index === this.selectNavIndex ? '#FFFFFF' : Color.Black)
.backgroundColor(index === this.selectNavIndex ? '#bEDAF2' : Color.Transparent)
.onClick(() => {
this.selectNavIndex = index;
this.isClickScroll = true;
// 点击字母导航,滚动城市列表到对应位置
this.cityScroller.scrollToIndex(index + 2, false, ScrollAlign.START);
})
}
})
}
}
.width('10%')
}技术要点:
- ListItemGroup 分组:使用
ListItemGroup的header属性定义分组标题,配合sticky实现吸顶 - 字母导航联动:右侧字母列表点击时,通过
scrollToIndex方法控制左侧城市列表滚动到对应位置 - 滚动同步:监听
onScrollIndex事件,在左侧列表滚动时同步更新右侧导航的高亮状态 - 冲突处理:通过
isClickScroll状态变量区分点击导航和手动滚动,避免两者冲突
场景四:二级联动(CategoryPage.ets)
二级联动场景模拟了电商分类页面,左侧是一级分类导航,右侧是对应的商品列表。点击左侧导航,右侧滚动到对应分类;滚动右侧内容,左侧对应导航高亮显示。

上图展示了二级联动场景,左侧是分类导航(精选服饰高亮),右侧是对应的商品列表。
核心代码:
Row() {
// 左侧分类导航
List({ scroller: this.navTitleScroller }) {
ForEach(this.categoryList, (item: NavTitleModel, index: number) => {
ListItem() {
Text(item.titleName)
.fontSize(this.currentTitleId === index ? 16 : 14)
.fontColor(this.currentTitleId === index ? '#0A59F7' : Color.Black)
.fontWeight(this.currentTitleId === index ? FontWeight.Bold : FontWeight.Normal)
.onClick(() => {
// 点击左侧导航,右侧滚动到对应分类
this.listChange(index, true);
})
}
})
}
.width(100)
// 右侧商品列表
List({ scroller: this.goodsListScroller }) {
ForEach(this.categoryList, (item: NavTitleModel) => {
ListItemGroup({ space: 12, header: this.goodsHeaderBuilder(item.titleName) }) {
ForEach(item.goodsList, (goodsItem: GoodsDataModel) => {
ListItem() {
Row() {
Image(goodsItem.imgUrl).height('100%').aspectRatio(1)
Column() {
Text(goodsItem.goodsName).fontSize(14).maxLines(2)
Text('¥' + goodsItem.price).fontSize(18).fontColor(Color.Red)
}
.layoutWeight(1)
}
.width('100%').height(96)
}
})
}
})
}
.sticky(StickyStyle.Header)
.onScrollIndex((index: number) => {
// 右侧滚动时,左侧导航高亮同步
this.listChange(index, false)
})
}
// 联动控制方法
listChange(index: number, isGoods: boolean) {
if (this.currentTitleId !== index) {
this.currentTitleId = index;
if (isGoods) {
// 点击左侧,右侧滚动
this.goodsListScroller.scrollToIndex(index);
} else {
// 滚动右侧,左侧高亮
this.navTitleScroller.scrollToIndex(index);
}
}
}技术要点:
- 双列表布局:使用
Row包裹左右两个List,左侧固定宽度,右侧自适应 - 双向联动:
listChange方法统一处理双向联动逻辑,通过isGoods参数区分触发来源 - 滚动同步:点击左侧时调用
goodsListScroller.scrollToIndex,滚动右侧时调用navTitleScroller.scrollToIndex - 视觉反馈:通过
currentTitleId状态变量控制左侧导航的字体大小、颜色、粗细变化
数据模型(LinkDataModel.ets)
二级联动场景使用了简单的数据模型来组织分类和商品数据:
export interface GoodsDataModel {
titleId: number,
goodsId: number,
goodsName: string,
imgUrl: Resource,
price: number,
}
export interface NavTitleModel {
titleId: number,
titleName: Resource,
goodsList: GoodsDataModel[]
}
class GoodsViewModel {
getLinkData(): NavTitleModel[] {
let linkDataList: NavTitleModel[] = [];
let titleId: number = 0;
linkData.forEach((item: LinkDataModel) => {
if (titleId !== item.titleId) {
linkDataList.push({ titleId: item.titleId, titleName: item.titleName, goodsList: [] });
}
// 为每个分类生成 10 个商品
for (let i = 1; i <= 10; i++) {
linkDataList[linkDataList.length - 1].goodsList.push({
titleId,
goodsId: item.goodsId + i,
goodsName: uiContext?.getHostContext()!.resourceManager.getStringSync(item.goodsName.id) + i,
imgUrl: item.imgUrl,
price: Number(i.toString() + item.price.toString())
})
}
titleId = item.titleId;
})
return linkDataList;
}
}运行效果
以下是四个场景的实际运行截图:
| 多类型列表项 | Tab 吸顶 |
|---|---|
![]() | ![]() |
| 分组吸顶 | 二级联动 |
|---|---|
![]() | ![]() |
总结
CommonListFlows 项目通过四个典型场景,全面展示了 HarmonyOS List 组件的核心能力和常用技巧:
- ListItemGroup + sticky:实现分组吸顶效果,适用于分类列表、城市选择器等场景
- nestedScroll:解决嵌套滚动冲突,实现 Tab 吸顶等复杂交互
- Scroller + scrollToIndex:实现列表位置控制和双向联动
- Refresh + onReachEnd:实现下拉刷新和上拉加载,完善列表交互体验
这些技术不仅适用于示例中的场景,也可以灵活应用到电商、社交、工具等各类应用的列表开发中。对于正在学习 HarmonyOS 开发的开发者来说,这是一个非常值得深入研究的示例项目。