源码获取

如果你想一边对照文章一边实操,建议直接把示例工程拉到本地。项目 Git 地址:https://gitcode.com/HarmonyOS_Samples/CommonListFlows

很多人第一次写首页,最容易走进一个坑: 拿 Column 硬堆。

上面塞搜索框,中间塞轮播,下面再塞宫格、卡片、推荐区。页面一开始还能看,等你想加下拉刷新、吸顶、触底加载的时候,结构立刻开始发脾气。

HomePage 这页最值钱的地方就在这儿。它没有把首页当成一堆散块,而是把整页内容都收进一个 List 里。这个思路一旦吃透,你以后做电商首页、频道页、活动页,都会顺手很多。

先把 List 当成整页骨架来看

A hand-drawn sketch-note infographic illustrating

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

A technical blueprint diagram of the HarmonyOS7 Li

  • 同类内容用 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')
}

这块特别像很多首页里的快捷入口区。

你可以把它理解成“列表里的一个功能区块”,而不是一个独立滚动世界。这个理解一旦有了,后面你再往首页里塞卡片、推荐、广告,就不会总想开新容器。

An ink-style illustration comparing two scrolling

并排图片区最值得学的是什么
ListItem() {
  Row() {
    Image($r('app.media.pic1'))
      .width('49%')
    Image($r('app.media.pic1'))
      .width('49%')
  }
  .width('100%')
  .height(120)
  .justifyContent(FlexAlign.SpaceBetween)
}

这一段其实在告诉你一件很实在的事: ListItem 里面不是只能放“标准行项目”。

它完全可以是一小块自定义布局。只要业务需要,你往里面塞 RowColumnGridSwiper 都没问题。

真正进入内容流状态的是 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 硬顶一切了。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐