HarmonyOS7 列表流实战----用一个 List 搭出多类型首页
文章目录
源码获取
如果你想一边对照文章一边实操,建议直接把示例工程拉到本地。项目 Git 地址:https://gitcode.com/HarmonyOS_Samples/CommonListFlows。
很多人第一次写首页,最容易走进一个坑: 拿 Column 硬堆。
上面塞搜索框,中间塞轮播,下面再塞宫格、卡片、推荐区。页面一开始还能看,等你想加下拉刷新、吸顶、触底加载的时候,结构立刻开始发脾气。
HomePage 这页最值钱的地方就在这儿。它没有把首页当成一堆散块,而是把整页内容都收进一个 List 里。这个思路一旦吃透,你以后做电商首页、频道页、活动页,都会顺手很多。

先把 List 当成整页骨架来看

HomePage 最外层用了 NavDestination,但真正撑起页面的是 Refresh 里面那层 List:
Refresh({ refreshing: $$this.isRefreshing }) {
List({ space: 12 }) {
ListItem() {
Swiper() {
// 轮播内容
}
}
ListItem() {
Grid() {
// 宫格内容
}
}
ListItem() {
Row() {
// 自定义图片区
}
}
ForEach(this.scenicSpotTitle, (item: Resource) => {
ListItemGroup({ header: this.scenicSpotHeader(item) }) {
ForEach(this.scenicSpotArray, (scenicSpotItem: Resource) => {
ListItem() {
this.scenicSpotDetailBuilder(scenicSpotItem)
}
})
}
})
}
}
你先别被代码块长度吓到,真正的逻辑其实很朴素:
- 固定模块直接写成
ListItem

- 同类内容用
ListItemGroup - 重复数据交给
ForEach
说白了,就是让整页所有模块待在同一个滚动体系里。
这一步特别关键。
因为首页一旦进入统一滚动体系,后面的刷新、吸顶、加载更多才会顺。
为什么不是每块自己滚自己的
很多刚入门的人会有一个直觉: 轮播是一块、宫格是一块、推荐区是一块,那就各写各的。
短期看没毛病,长期几乎一定难维护。
尤其是你想做下面这些交互时:
- 整页下拉刷新
- 分组标题吸顶
- 触底加载更多
- 整页视觉节奏统一
这时候把内容都收进一个 List,优势就出来了。用户滑一下,整页跟着走,不会出现这里能滑、那里也能滑、最后谁都不顺手的情况。
顶部三块,看着不一样,本质上都只是列表项
项目里首页最上面放了三类完全不同的内容:
- 轮播图
- 宫格入口
- 并排图片区
但它们在 List 里,身份是一样的,都是 ListItem。
轮播图为什么也要进 ListItem
ListItem() {
Swiper() {
ForEach(this.swiperContent, (item: SwiperType) => {
Stack({ alignContent: Alignment.BottomStart }) {
Image($r(item.pic))
}
}, (item: SwiperType) => JSON.stringify(item))
}
.width('100%')
.height(184)
.autoPlay(true)
.duration(1000)
}
重点不是 Swiper 有多复杂,而是你要建立一个正确观念: 轮播图在首页里也只是一个列表项。
以后你做 banner、广告位、头图卡片,基本都可以沿着这个思路放进长页面里。
宫格为什么不是单独的页面模块
ListItem() {
Grid() {
ForEach(this.gridTitle, (item: Resource) => {
GridItem() {
Column() {
Image($r('app.media.pic1'))
Text(item)
}
}
}, (item: Resource) => JSON.stringify(item))
}
.rowsGap(16)
.columnsGap(19)
.columnsTemplate('1fr 1fr 1fr 1fr 1fr')
}
这块特别像很多首页里的快捷入口区。
你可以把它理解成“列表里的一个功能区块”,而不是一个独立滚动世界。这个理解一旦有了,后面你再往首页里塞卡片、推荐、广告,就不会总想开新容器。

并排图片区最值得学的是什么
ListItem() {
Row() {
Image($r('app.media.pic1'))
.width('49%')
Image($r('app.media.pic1'))
.width('49%')
}
.width('100%')
.height(120)
.justifyContent(FlexAlign.SpaceBetween)
}
这一段其实在告诉你一件很实在的事: ListItem 里面不是只能放“标准行项目”。
它完全可以是一小块自定义布局。只要业务需要,你往里面塞 Row、Column、Grid、Swiper 都没问题。
真正进入内容流状态的是 ListItemGroup
首页往下滚到景区部分,页面才真正进入“有分组的列表流”状态:
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))
这里的读法别太快。
外层在遍历“分类标题”,内层在遍历“分类下的具体内容”。这正是很多真实业务首页常见的写法,比如:
- 按主题分组的推荐列表
- 按时间分组的内容流
- 按栏目分组的服务区块
你以后遇到类似结构,别第一反应就平铺。先想想是不是应该分组。
卡片内容什么时候该抽出去
项目没有把景区卡片一股脑堆在 build() 里,而是专门抽了一个 Builder:
@Builder
scenicSpotDetailBuilder(title: Resource) {
Column() {
Image($r('app.media.pic1'))
.width('100%')
.height(186)
Column() {
Text(title)
Text() {
Span($r('app.string.group_discount'))
Span('示例优惠价')
}
}
}
}
我很推荐新手学这个动作。
因为首页最容易写着写着就失控。今天加一块文案,明天加一个角标,后天再补个价格区,主 build() 很快就会长得像一堵墙。把复杂列表项提前拆成 Builder 或组件,后面维护会轻松很多。
这类首页最容易写歪的地方
1. 把所有模块都塞进 Column
一开始觉得快,后面想加刷新、吸顶和触底加载时就会非常别扭。
2. 只会写单一模板列表
真实首页基本不会从头到尾长一个样。你得尽早接受“同一个 List 里放多种内容”这件事。
3. 列表项已经很复杂了,还不拆
这种代码刚开始只是长,过两天就会变难改。看到一个列表项已经几十行了,就该有拆分意识。
现在就能练的三个小改动
这页很适合做练手,我建议你直接改这三处:
- 把宫格数量从 10 个改成 8 个
- 给轮播区补一行标题或说明文案
- 给景区卡片再加评分、位置或标签
你别小看这几个改动。
自己亲手动一遍,比你继续看五篇文章更能建立感觉。
真正该记住的一句话
很多人把 List 只当“长得像通讯录”的列表组件用,这其实把它看小了。
HomePage 这一页最想教你的,是另外一件事: List 不只是列表,它完全可以是整页复杂首页的骨架。 这个观念一旦立住,你后面做 ArkUI 首页会顺手很多,也不容易再拿 Column 硬顶一切了。
更多推荐



所有评论(0)