Skip to content
🌊海洋蓝
🌸樱花粉
🍃森林绿
🔮幻夜紫
🌙暗夜黑

HarmonyOS 列表操作实战指南

📚 写给初学者的话:如果你刚开始学习 HarmonyOS 开发,不用担心!这篇文章会用最简单的方式带你掌握列表操作的核心技能。我们会从最基础的概念开始,一步步构建出复杂的列表功能。

📖 开始之前:什么是列表?

🤔 先从生活中的例子说起

在开始学习代码之前,让我们先想想生活中的"列表":

  • 📝 购物清单:一行行写着要买的东西
  • 📞 手机通讯录:一个个联系人按顺序排列
  • 🎵 音乐播放列表:一首首歌曲排成队

在 APP 开发中,列表就是把这些信息在手机屏幕上"一行一行"地展示出来。

📱 列表在 APP 中长什么样?

打开你的手机,你会发现列表无处不在:

  • 微信:聊天记录就是一个列表,每条消息占一行
  • 淘宝:商品展示就是列表,每个商品占一行或一块区域
  • 音乐 APP:歌曲列表、播放列表都是典型的列表

✨ 为什么列表这么重要?

对用户来说:

  • 📱 看得清楚:信息整整齐齐,不会眼花缭乱
  • 🔍 找得容易:可以快速扫视,找到想要的内容
  • 用得舒服:符合从上到下的阅读习惯
  • 📜 装得下:屏幕放不下的内容可以滑动查看

对开发者来说:

  • 🛠️ 开发效率高:HarmonyOS 提供了强大的列表组件
  • 🎨 样式灵活:可以做出各种美观的效果
  • 🚀 性能优秀:系统自动优化,即使数据很多也很流畅

🔧 HarmonyOS 中的列表技术

🧱 核心组件介绍

想象一下搭积木:我们用几种基本的"积木块"就能搭出各种复杂的列表。

基础积木(组件):

组件名称作用生活中的比喻官方文档
List列表容器就像一个大盒子,装下所有的列表内容📚 查看文档
ListItem单个列表项盒子里的每一个小格子,放一条信息📚 查看文档
ListItemGroup列表分组把相关的小格子归类到一起📚 查看文档

📐 组件关系图:

List (大盒子 - 整个列表)
├── ListItem (格子1 - 第一条信息)
├── ListItem (格子2 - 第二条信息)
├── ListItemGroup (分组盒子)
│   ├── ListItem (分组内的格子1)
│   └── ListItem (分组内的格子2)
└── ListItem (格子N - 最后一条信息)

🎯 学习路线图

💡 学习建议:建议按照从简单到复杂的顺序学习,每个场景都建议动手实践一遍!

难度等级学习内容适合人群预计学习时间
多类型列表项刚接触列表开发2-3 小时
⭐⭐Tabs 吸顶效果有基础列表经验1-2 小时
⭐⭐⭐分组吸顶想做通讯录类应用2-3 小时
⭐⭐⭐⭐二级联动想做电商分类页面3-4 小时

每个场景包含:

  • 🎭 场景展示:看看最终要做成什么样子
  • 🔍 原理解析:用最简单的话解释技术原理
  • 👨‍💻 代码实战:一步步跟着做,每段代码都有详细注释
  • 🎬 效果预览:看看我们的成果
  • 💡 扩展思考:举一反三,应用到其他场景

🚀 开始前的准备

环境要求:

  • ✅ 已安装 DevEco Studio
  • ✅ 已创建 HarmonyOS 项目
  • ✅ 对 ArkTS 语法有基本了解

如果你还没准备好,建议先:

  1. 📖 阅读HarmonyOS 官方入门教程
  2. 🛠️ 跟着官方 Quick Start搭建开发环境

场景一:多类型列表项 ⭐

🎯 学习目标:掌握在一个列表中展示不同类型内容的技能,做出像电商首页那样的丰富界面

🤔 什么是多类型列表项?

先从熟悉的 APP 说起:

打开你手机上的淘宝或京东,你会发现首页是这样的:

  1. 🔍 最顶部:搜索框
  2. 🖼️ 中间:轮播广告图片
  3. 🎯 下面:功能按钮(天猫超市、聚划算等)
  4. 📱 再下面:商品推荐列表

神奇的地方在于:这些完全不同的内容(搜索框、图片、按钮、列表)都在同一个可以滚动的页面里!

这就是"多类型列表项"——一个列表容器里装着各种不同类型的内容。

🏆 为什么要学这个?

实际开发中超级常用:

  • 🏪 电商 APP:首页几乎都是这种设计
  • 📰 新闻 APP:头条 + 导航 + 新闻列表
  • 🎵 音乐 APP:推荐歌单 + 排行榜 + 新歌
  • 🏠 几乎所有 APP 首页:都需要展示丰富的内容类型

学会了你就能做:

  • ✅ 复杂的首页界面
  • ✅ 丰富的内容展示
  • ✅ 用户体验良好的滚动效果
  • ✅ 下拉刷新、上拉加载等高级功能

🎯 本例效果预览

我们将实现一个旅游应用的首页,包含:

  1. 顶部搜索框:固定在页面顶部
  2. 轮播图区域:展示热门景点图片
  3. 功能网格:快速入口(如"门票"、"酒店"等)
  4. 推荐内容:景点推荐列表
  5. 交互功能:下拉刷新、上拉加载、标题吸顶

| 页面整体结构图

|

页面效果图

| | | |

|

|

|

🔍 实现原理(简单易懂版)

核心思路:像搭积木一样组装界面

想象你有一个神奇的大盒子(List),可以往里面放各种不同的"积木"(ListItem):

🗃️ List (神奇大盒子)
├── 🧩 ListItem (积木1:搜索框)
├── 🧩 ListItem (积木2:轮播图)
├── 🧩 ListItem (积木3:功能按钮网格)
├── 🧩 ListItem (积木4:推荐标题)
├── 🧩 ListItem (积木5:推荐内容1)
├── 🧩 ListItem (积木6:推荐内容2)
└── 🧩 ...更多积木

🛠️ 需要用到的工具(组件):

工具名称用途生活中的比喻何时使用
Refresh下拉刷新容器像给手机"拉一下"就能更新内容需要下拉刷新功能时
List列表主容器装所有内容的大盒子几乎总是需要
ListItem单个列表项盒子里的每个小格子每一块内容都需要
Swiper轮播图组件可以左右滑动的相册需要图片轮播时
Grid网格布局像九宫格一样排列的格子需要整齐排列按钮时

🔄 用户操作流程:

用户下拉页面 🔽

触发刷新事件 🔄

模拟获取新数据 📡

更新界面内容 ✨

用户滚动到底部 📜

触发加载更多 ⬆️

追加新内容 ➕

💡 新手提示:不用一次性掌握所有组件,我们会一步步来,每个步骤都有详细说明!

👨‍💻 动手实践:一步步实现

💡 开始前的提醒:建议在 DevEco Studio 中新建一个项目,跟着教程一步步敲代码,这样学习效果最好!

🔧 步骤一:搭建搜索框(难度:⭐)

🎯 目标: 做一个像淘宝首页那样的搜索框

📝 学习要点:

  • 了解 Row 布局(水平排列)
  • 学会使用 Image 和 TextInput 组件
  • 掌握基本的样式设置

💭 思路: 搜索框 = 搜索图标 + 输入框,用 Row 把它们横向排列

typescript
// 🏗️ 搜索框组件 - 新手友好版
@Component
struct SearchHeader {
  build() {
    Row() {  // Row是水平布局,让内容从左到右排列
      // 🔍 搜索图标
      Image($r('app.media.search_icon'))
        .width(20)    // 图标宽度20像素
        .height(20)   // 图标高度20像素

      // 📝 搜索输入框
      TextInput({ placeholder: '搜索景点、酒店...' })
        .layoutWeight(1)    // 占据剩余所有空间
        .margin({ left: 10 }) // 左边距10像素,和图标保持距离
    }
    .padding(16)            // 整个搜索框内边距16像素
    .backgroundColor(Color.White)  // 白色背景
  }
}

📖 代码解释:

  • Row():让里面的内容水平排列(从左到右)
  • Image():显示搜索图标
  • TextInput():用户可以输入文字的框
  • layoutWeight(1):让输入框占据除图标外的所有空间
  • padding(16):内容和边框之间留 16 像素的空隙

✨ 效果预览:

🎉 恭喜! 你已经学会了基础的组件使用和布局!接下来我们要做更酷的轮播图...

📸 步骤二:制作轮播图(难度:⭐⭐)

🎯 目标: 做一个可以自动播放的图片轮播,就像电商 APP 首页的广告图

📝 学习要点:

  • 掌握 Swiper 组件的基本用法
  • 学会使用 ForEach 循环渲染数据
  • 了解自动播放和指示器设置

💭 思路: 轮播图 = 多张图片 + 自动切换 + 底部小圆点

🔧 实现代码:

typescript
// 📸 轮播图组件 - 带详细注释
ListItem() {  // 这是List里的一个项目,专门放轮播图
  Swiper() {  // Swiper让图片可以左右滑动
    // 🔄 ForEach循环:把数组里的每张图片都显示出来
    ForEach(this.bannerImages, (item: string) => {
      Image(item)               // 显示图片
        .width('100%')          // 宽度占满整个容器
        .height(200)            // 高度固定200像素
        .borderRadius(8)        // 圆角8像素,看起来更美观
    })
  }
  .autoPlay(true)               // ✨ 自动播放(像电视换台一样)
  .interval(3000)               // ⏰ 每3秒自动切换一次
  .indicator(true)              // 🔵 显示底部小圆点(告诉用户当前看的是第几张)
  .padding(16)                  // 四周留16像素空隙
}

📊 数据准备:

typescript
// 📷 轮播图片数组 - 你可以换成自己的图片
@State bannerImages: string[] = [
  'https://example.com/banner1.jpg',  // 第一张图
  'https://example.com/banner2.jpg',  // 第二张图
  'https://example.com/banner3.jpg'   // 第三张图
]

📖 代码解释:

  • Swiper():神奇的轮播容器,让内容可以左右滑动
  • ForEach():循环渲染,有几张图片就显示几张
  • autoPlay(true):开启自动播放模式
  • interval(3000):3000 毫秒 = 3 秒,设置切换间隔
  • indicator(true):显示底部的小圆点导航

✨ 效果预览:

💡 小贴士:如果图片加载不出来,检查一下网络地址是否正确,或者先用本地图片测试

🎯 步骤三:制作功能网格(难度:⭐⭐)

🎯 目标: 做一个像手机桌面那样的图标网格,放各种功能入口

📝 学习要点:

  • 掌握 Grid 网格布局
  • 理解行列模板的设置
  • 学会处理点击事件

💭 思路: 网格 = 多个按钮整齐排列,就像九宫格游戏

🔧 实现代码:

typescript
// 🎯 功能网格组件 - 新手详解版
ListItem() {  // 又是List里的一个项目
  Grid() {    // Grid是网格布局,让内容像表格一样排列
    // 🔄 循环显示每个功能按钮
    ForEach(this.functionList, (item: FunctionItem) => {
      GridItem() {  // 网格中的一个格子
        Column() {  // Column是垂直布局,图标在上,文字在下
          // 🎨 功能图标
          Image(item.icon)
            .width(40)              // 图标宽40像素
            .height(40)             // 图标高40像素

          // 📝 功能名称
          Text(item.title)
            .fontSize(12)           // 文字大小12像素
            .margin({ top: 8 })     // 和图标保持8像素距离
        }
        .justifyContent(FlexAlign.Center)  // 内容居中显示
      }
      .onClick(() => {
        // 🖱️ 点击事件:用户点击按钮时会执行这里的代码
        console.log(`用户点击了${item.title}`)
        // 这里可以添加跳转页面、弹窗等功能
      })
    })
  }
  .columnsTemplate('1fr 1fr 1fr 1fr')  // 📐 4列,每列宽度相等
  .rowsTemplate('1fr 1fr')             // 📐 2行,每行高度相等
  .height(160)                         // 整个网格高度160像素
  .padding(16)                         // 四周留16像素空隙
}

📊 数据准备:

typescript
// 🛠️ 功能列表数据
interface FunctionItem {
  icon: string    // 图标地址
  title: string   // 功能名称
}

@State functionList: FunctionItem[] = [
  { icon: 'app.media.hotel_icon', title: '酒店' },
  { icon: 'app.media.ticket_icon', title: '门票' },
  { icon: 'app.media.food_icon', title: '美食' },
  { icon: 'app.media.car_icon', title: '租车' },
  { icon: 'app.media.guide_icon', title: '导游' },
  { icon: 'app.media.more_icon', title: '更多' }
]

📖 代码解释:

  • Grid():网格布局容器,像 Excel 表格一样
  • GridItem():网格中的每一个格子
  • Column():垂直布局,让图标和文字上下排列
  • columnsTemplate('1fr 1fr 1fr 1fr'):分成 4 列,每列宽度相等
  • rowsTemplate('1fr 1fr'):分成 2 行,每行高度相等
  • onClick():点击事件处理函数

✨ 效果预览:

🎮 互动体验:运行代码后试着点击不同的按钮,看看控制台的输出信息

步骤 4:创建推荐内容列表

目标: 添加可滚动的推荐内容列表

关键点:

  • 先添加一个标题 ListItem(设置 sticky 属性)
  • 然后添加多个内容 ListItem
  • 每个内容项包含图片、标题、描述等
typescript
// 推荐标题(吸顶效果)
ListItem() {
  Text('推荐景点')
    .fontSize(18)
    .fontWeight(FontWeight.Bold)
    .padding(16)
    .backgroundColor(Color.White)
}
.sticky(Sticky.Normal) // 关键:设置吸顶效果

// 推荐内容项
ForEach(this.recommendList, (item: RecommendItem) => {
  ListItem() {
    Row() {
      Image(item.image)
        .width(80)
        .height(80)
        .borderRadius(8)

      Column() {
        Text(item.title)
          .fontSize(16)
          .fontWeight(FontWeight.Medium)
        Text(item.description)
          .fontSize(14)
          .fontColor(Color.Gray)
          .margin({ top: 4 })
        Text(`¥${item.price}`)
          .fontSize(16)
          .fontColor(Color.Red)
          .margin({ top: 8 })
      }
      .alignItems(HorizontalAlign.Start)
      .margin({ left: 12 })
      .layoutWeight(1)
    }
    .padding(16)
    .alignItems(VerticalAlign.Top)
  }
})

实现效果:

步骤 5:添加刷新和加载功能

目标: 实现下拉刷新和上拉加载更多功能

关键点:

  • 用 Refresh 组件包裹整个 List
  • 添加 onRefreshing 回调处理下拉刷新
  • 添加 onReachEnd 回调处理上拉加载
typescript
// 完整页面结构
@Component
struct HomePage {
  @State isRefreshing: boolean = false
  @State recommendList: RecommendItem[] = []

  build() {
    Column() {
      // 顶部搜索框(固定)
      SearchHeader()

      // 可刷新的内容区域
      Refresh({ refreshing: this.isRefreshing }) {
        List() {
          // 轮播图
          ListItem() { /* Swiper内容 */ }

          // 功能网格
          ListItem() { /* Grid内容 */ }

          // 推荐标题(吸顶)
          ListItem() { /* 标题内容 */ }
          .sticky(Sticky.Normal)

          // 推荐列表
          ForEach(this.recommendList, (item) => {
            ListItem() { /* 推荐项内容 */ }
          })
        }
        .onReachEnd(() => {
          // 上拉加载更多
          this.loadMoreData()
        })
      }
      .onRefreshing(() => {
        // 下拉刷新
        this.refreshData()
      })
    }
  }

  // 刷新数据
  refreshData() {
    this.isRefreshing = true
    // 模拟网络请求
    setTimeout(() => {
      this.recommendList = this.getNewData()
      this.isRefreshing = false
    }, 2000)
  }

  // 加载更多数据
  loadMoreData() {
    // 模拟加载更多
    setTimeout(() => {
      this.recommendList.push(...this.getMoreData())
    }, 1000)
  }
}

🎬 最终实现效果

通过以上步骤,我们成功实现了一个功能完整的多类型列表页面:

主要功能展示:

下拉刷新 + 标题吸顶效果上拉加载更多效果
用户下拉时显示刷新动画,"推荐景点"标题在滚动时自动吸顶滚动到底部时自动加载更多内容,提升用户体验

核心特性总结:

  • 多类型内容:轮播图、网格、列表完美融合
  • 下拉刷新:模拟真实的数据更新场景
  • 上拉加载:无限滚动,数据分页加载
  • 标题吸顶:重要信息始终可见
  • 流畅交互:符合用户操作习惯

适用场景回顾: 这种实现方式特别适合需要展示丰富内容的首页场景,如电商 APP 首页、新闻资讯首页、旅游 APP 首页等。

Tabs 吸顶场景

📖 场景说明

什么是 Tabs 吸顶效果?

想象一下你在使用今日头条、网易新闻等新闻 APP 时的体验:

  1. 页面顶部有搜索框和标题
  2. 中间有分类标签(如"推荐"、"热点"、"科技"、"娱乐"等)
  3. 下面是对应分类的新闻列表
  4. 关键点:当你向上滑动时,顶部内容会消失,但分类标签会"粘"在屏幕顶部不动

这就是"Tabs 吸顶"效果!它让用户在浏览长列表时,依然能够方便地切换不同分类。

为什么需要这个效果?

  • 🎯 提升用户体验:无论滚动到哪里,都能快速切换分类
  • 📱 节省屏幕空间:隐藏不重要的顶部内容,留更多空间给内容
  • 🔄 符合使用习惯:用户已经习惯了这种交互方式

常见应用场景:

  • 📰 新闻资讯 APP:分类新闻浏览
  • 🛒 电商 APP:商品分类浏览
  • 🎵 音乐 APP:歌单分类
  • 📺 视频 APP:内容分类

🎯 本例效果预览

我们将实现一个新闻资讯 APP 的首页,包含:

  1. 顶部区域:搜索框、用户头像等(滚动时会隐藏)
  2. 图片横幅:热门新闻展示(滚动时会隐藏)
  3. Tabs 标签:新闻分类导航(滚动时吸顶显示)
  4. 内容列表:对应分类的新闻列表(可正常滚动)

| 页面整体结构图

|

页面效果图

| | | |

|

|

|

🔧 实现原理

核心思路:嵌套滚动 + 高度计算

实现 Tabs 吸顶的关键在于理解"嵌套滚动"的概念:

页面结构:
┌─────────────────────┐
│   顶部搜索区域        │ ← 滚动时会隐藏
├─────────────────────┤
│   图片横幅区域        │ ← 滚动时会隐藏
├─────────────────────┤
│   Tabs标签区域       │ ← 滚动时吸顶(关键!)
├─────────────────────┤
│                     │
│   内容列表区域        │ ← 可以正常滚动
│   (新闻列表)        │
│                     │
└─────────────────────┘

技术实现要点:

  1. 整体布局使用 Stack

    • 底层:完整的页面内容(包括顶部区域)
    • 顶层:固定的 Tabs 标签(初始时透明或隐藏)
  2. 关键属性 - nestedScroll

    typescript
    List().nestedScroll({
      scrollForward: NestedScrollMode.PARENT_FIRST, // 向上滚动时优先滚动父容器
      scrollBackward: NestedScrollMode.SELF_FIRST, // 向下滚动时优先滚动自己
    });
  3. 高度计算

    typescript
    // 计算Tabs应该显示的位置
    .height(`calc(100% - ${this.topHeight}px)`)

工作流程:

用户向上滚动 → 顶部内容逐渐隐藏 → 当滚动到Tabs位置时 → Tabs固定在顶部 → 继续滚动内容列表

关键组件说明:

  • Tabs:标签页容器,提供分类切换功能
  • Stack:堆叠布局,实现层级效果
  • List + nestedScroll:嵌套滚动的核心
  • Scroll:滚动容器,承载顶部内容

👨‍💻 开发步骤

步骤 1:创建自定义 Tabs 标签栏

目标: 创建一个美观的分类标签栏,支持点击切换

关键点:

  • 使用自定义 tabBar 而不是默认样式
  • 支持滑动切换和点击切换
  • 标签样式要清晰易识别
typescript
@Component
struct CustomTabBar {
  @State currentIndex: number = 0
  private tabTitles: string[] = ['推荐', '热点', '科技', '娱乐', '体育']

  build() {
    Row() {
      ForEach(this.tabTitles, (title: string, index: number) => {
        Column() {
          Text(title)
            .fontSize(16)
            .fontColor(this.currentIndex === index ? Color.Red : Color.Black)
            .fontWeight(this.currentIndex === index ? FontWeight.Bold : FontWeight.Normal)

          // 底部指示线
          Divider()
            .width(20)
            .height(2)
            .color(Color.Red)
            .opacity(this.currentIndex === index ? 1 : 0)
            .margin({ top: 4 })
        }
        .layoutWeight(1)
        .padding({ vertical: 12 })
        .onClick(() => {
          this.currentIndex = index
          // 切换到对应标签页
        })
      })
    }
    .width('100%')
    .backgroundColor(Color.White)
  }
}

实现效果:

步骤 2:创建顶部搜索区域

目标: 实现顶部搜索框和用户信息区域(滚动时会隐藏)

关键点:

  • 包含搜索框、用户头像、消息图标等
  • 这部分内容在滚动时会被隐藏
  • 布局要美观且功能完整
typescript
@Component
struct TopSearchArea {
  build() {
    Row() {
      // 搜索框
      Row() {
        Image($r('app.media.search_icon'))
          .width(16)
          .height(16)
        Text('搜索新闻、资讯...')
          .fontSize(14)
          .fontColor(Color.Gray)
          .margin({ left: 8 })
      }
      .layoutWeight(1)
      .padding({ horizontal: 12, vertical: 8 })
      .backgroundColor('#F5F5F5')
      .borderRadius(20)

      // 右侧图标
      Row() {
        Image($r('app.media.message_icon'))
          .width(24)
          .height(24)
          .margin({ right: 12 })

        Image($r('app.media.avatar'))
          .width(32)
          .height(32)
          .borderRadius(16)
      }
      .margin({ left: 16 })
    }
    .width('100%')
    .padding(16)
    .backgroundColor(Color.White)
  }
}

实现效果:

步骤 3:创建图片横幅和内容列表

目标: 添加热门新闻横幅和新闻列表内容

关键点:

  • 横幅图片要吸引眼球
  • 新闻列表要支持滚动
  • 内容要丰富且布局合理
typescript
@Component
struct NewsContent {
  @State newsList: NewsItem[] = []

  build() {
    Column() {
      // 热门新闻横幅
      Image($r('app.media.hot_news_banner'))
        .width('100%')
        .height(200)
        .objectFit(ImageFit.Cover)
        .borderRadius(8)
        .margin(16)

      // 新闻列表
      List() {
        ForEach(this.newsList, (news: NewsItem) => {
          ListItem() {
            Row() {
              Column() {
                Text(news.title)
                  .fontSize(16)
                  .fontWeight(FontWeight.Medium)
                  .maxLines(2)
                  .textOverflow({ overflow: TextOverflow.Ellipsis })

                Text(news.summary)
                  .fontSize(14)
                  .fontColor(Color.Gray)
                  .maxLines(1)
                  .margin({ top: 4 })

                Row() {
                  Text(news.source)
                    .fontSize(12)
                    .fontColor(Color.Gray)
                  Text(news.time)
                    .fontSize(12)
                    .fontColor(Color.Gray)
                    .margin({ left: 16 })
                }
                .margin({ top: 8 })
              }
              .layoutWeight(1)
              .alignItems(HorizontalAlign.Start)

              Image(news.image)
                .width(80)
                .height(60)
                .borderRadius(4)
                .margin({ left: 12 })
            }
            .padding(16)
            .alignItems(VerticalAlign.Top)
          }
        })
      }
      .layoutWeight(1)
    }
  }
}

实现效果:

步骤 4:实现 Tabs 吸顶效果

目标: 使用 nestedScroll 属性实现 Tabs 标签的吸顶效果

关键点:

  • 使用 Stack 布局实现层级效果
  • 配置 nestedScroll 属性
  • 计算正确的高度值
typescript
@Component
struct TabsStickyPage {
  @State topHeight: number = 200 // 顶部区域高度

  build() {
    Stack({ alignContent: Alignment.Top }) {
      // 底层:完整页面内容
      Column() {
        TopSearchArea()        // 顶部搜索区域

        Tabs() {
          TabContent() {
            NewsContent()      // 推荐新闻内容
          }.tabBar('推荐')

          TabContent() {
            NewsContent()      // 热点新闻内容
          }.tabBar('热点')

          // 更多标签页...
        }
        .barPosition(BarPosition.Start)
        .layoutWeight(1)
      }

      // 顶层:固定的Tabs标签(吸顶时显示)
      Column() {
        CustomTabBar()
      }
      .width('100%')
      .position({ x: 0, y: this.topHeight })
      .zIndex(1)

      // 内容区域List(关键:配置nestedScroll)
      List() {
        // 内容项...
      }
      .nestedScroll({
        scrollForward: NestedScrollMode.PARENT_FIRST,
        scrollBackward: NestedScrollMode.SELF_FIRST
      })
      .height(`calc(100% - ${this.topHeight}px)`)
      .margin({ top: this.topHeight })
    }
    .width('100%')
    .height('100%')
  }
}

核心配置说明:

  • NestedScrollMode.PARENT_FIRST:向上滚动时优先滚动父容器(隐藏顶部内容)
  • NestedScrollMode.SELF_FIRST:向下滚动时优先滚动自己(显示顶部内容)
  • calc(100% - ${this.topHeight}px):动态计算内容区域高度

🎬 最终实现效果

通过以上步骤,我们成功实现了 Tabs 吸顶的完整效果:

核心功能展示:

效果说明:

  1. 初始状态:顶部搜索区域、图片横幅、Tabs 标签都正常显示
  2. 向上滚动:顶部内容逐渐隐藏,Tabs 标签开始上移
  3. 吸顶状态:Tabs 标签固定在屏幕顶部,内容列表继续滚动
  4. 向下滚动:Tabs 标签回到原位,顶部内容重新显示

技术特点总结:

  • 流畅的滚动体验:使用 nestedScroll 实现自然的滚动效果
  • 精确的吸顶定位:通过高度计算确保 Tabs 准确吸顶
  • 完整的交互功能:支持标签切换和内容滚动
  • 良好的用户体验:符合主流 APP 的交互习惯

适用场景回顾: 这种 Tabs 吸顶效果特别适合内容分类较多的应用,如新闻资讯、电商分类、视频分类等场景,能够显著提升用户的浏览效率。

分组吸顶场景

📖 场景说明

什么是分组吸顶效果?

这是一个非常实用的功能,最典型的例子就是手机通讯录:

  1. 联系人按照姓名首字母分组(A、B、C、D...)
  2. 右侧有一个字母索引条(A-Z)
  3. 滚动时,当前分组的字母标题会"吸"在顶部
  4. 点击右侧字母,左侧列表会快速跳转到对应分组
  5. 滚动左侧列表时,右侧对应字母会高亮显示

为什么需要这个效果?

  • 🔍 快速定位:在大量数据中快速找到目标内容
  • 📍 位置感知:用户始终知道当前浏览到哪个分组
  • 高效导航:一键跳转到任意分组
  • 👀 视觉清晰:分组标题吸顶,分类一目了然

常见应用场景:

  • 📞 通讯录:按姓名首字母分组的联系人列表
  • 🏙️ 城市选择:按首字母分组的城市列表
  • 🏢 公司选择:按首字母分组的公司列表
  • 📚 词典应用:按首字母分组的词汇列表
  • 🎵 音乐列表:按歌手首字母分组的歌曲

🎯 本例效果预览

我们将实现一个城市选择页面,包含:

  1. 左侧城市列表

    • 当前城市(定位获取)
    • 热门城市(常用城市)
    • 按字母分组的所有城市(A-Z)
    • 分组标题支持吸顶效果
  2. 右侧字母索引

    • A-Z 字母快速导航
    • 当前分组字母高亮显示
    • 点击字母快速跳转
  3. 双向联动效果

    • 滚动左侧列表 → 右侧字母高亮变化
    • 点击右侧字母 → 左侧列表跳转到对应分组

| 页面整体结构图

|

页面效果图

| | | |

|

|

|

🔧 实现原理详解

核心思想:分组 + 吸顶 + 双向联动

想象一下,我们要实现的效果就像是一个"智能书签系统":

  • 📚 书本:整个城市列表
  • 🏷️ 书签:每个字母分组的标题
  • 📍 智能吸顶:当前章节的书签始终显示在顶部
  • 🔗 快速索引:右侧字母导航就像目录,点击直接跳转

技术架构图:

Stack (堆叠布局)
├── 左侧城市列表 (List)
│   ├── ListItemGroup (当前城市)
│   │   ├── 分组标题 (sticky吸顶)
│   │   └── 城市项目 (ListItem)
│   ├── ListItemGroup (热门城市)
│   │   ├── 分组标题 (sticky吸顶)
│   │   └── 城市项目 (ListItem)
│   └── ListItemGroup (A-Z分组)
│       ├── 分组标题 (sticky吸顶)
│       └── 城市项目 (ListItem)
└── 右侧字母索引 (List)
    ├── 字母A (ListItem)
    ├── 字母B (ListItem)
    └── ...

关键技术组件:

组件作用关键属性/方法说明
ListItemGroup分组容器sticky: StickyStyle.Header实现分组标题吸顶效果
Stack布局容器分层显示左侧列表 + 右侧索引的叠加布局
List列表容器onScrollIndexscrollToIndex监听滚动、控制跳转
ListItem列表项数据展示城市信息的具体展示

核心技术点详解:

  1. 🎯 分组吸顶机制

    typescript
    ListItemGroup({ sticky: StickyStyle.Header }) {
      // 分组标题 - 会自动吸顶
      // 分组内容 - 正常滚动
    }
  2. 📡 滚动监听机制

    typescript
    List() {
      // 监听滚动位置变化
      .onScrollIndex((start, end) => {
        // 根据当前显示的项目更新右侧字母高亮
      })
    }
  3. ⚡ 快速跳转机制

    typescript
    // 点击右侧字母时
    this.listController.scrollToIndex(targetIndex);

工作流程:

  1. 初始化:构建分组数据,计算每个分组的起始索引
  2. 滚动监听:实时监听列表滚动位置
  3. 状态同步:根据滚动位置更新右侧字母高亮
  4. 快速跳转:点击字母时跳转到对应分组
  5. 吸顶显示:当前分组标题自动吸顶

🛠️ 开发步骤详解

步骤 1:构建数据结构 📊

目标:创建完整的城市数据结构,支持分组和索引

关键实现点

  • 定义城市数据模型
  • 构建当前城市、热门城市、字母分组数据
  • 计算每个分组的起始索引位置

核心代码示例

typescript
// 城市数据模型
interface CityInfo {
  name: string;
  code?: string;
}

// 分组数据结构
interface CityGroup {
  title: string;
  cities: CityInfo[];
  startIndex: number; // 在整个列表中的起始位置
}

// 构建完整数据
const cityData = {
  current: { title: "当前城市", cities: [currentCity] },
  hot: { title: "热门城市", cities: hotCities },
  groups: alphabetGroups, // A-Z分组
};

视觉效果:数据结构清晰,为后续功能奠定基础


步骤 2:创建 Stack 分层布局 🏗️

目标:使用 Stack 组件实现左右分层显示

关键实现点

  • Stack 容器包含左侧城市列表和右侧字母索引
  • 右侧字母索引悬浮在左侧列表之上
  • 合理设置对齐方式和层级关系

核心代码示例

typescript
Stack({ alignContent: Alignment.End }) {
  // 左侧城市列表(底层)
  List() {
    // 城市列表内容
  }
  .width('100%')
  .height('100%')

  // 右侧字母索引(顶层)
  List() {
    // 字母导航内容
  }
  .width('30vp')
  .height('100%')
}

视觉效果:左右布局清晰,字母索引悬浮显示


步骤 3:实现左侧分组列表 📋

目标:使用 ListItemGroup 创建支持吸顶的分组列表

关键实现点

  • 使用 ListItemGroup 包装每个分组
  • 设置 sticky 属性实现标题吸顶
  • 渲染当前城市、热门城市、字母分组

核心代码示例

typescript
List() {
  // 当前城市分组
  ListItemGroup({
    header: this.groupHeader('当前城市'),
    sticky: StickyStyle.Header
  }) {
    ForEach(currentCities, (city) => {
      ListItem() {
        this.cityItem(city)
      }
    })
  }

  // 热门城市分组
  ListItemGroup({
    header: this.groupHeader('热门城市'),
    sticky: StickyStyle.Header
  }) {
    // 热门城市内容
  }

  // A-Z字母分组
  ForEach(alphabetGroups, (group) => {
    ListItemGroup({
      header: this.groupHeader(group.title),
      sticky: StickyStyle.Header
    }) {
      // 分组城市内容
    }
  })
}

视觉效果:分组清晰,标题支持吸顶显示


步骤 4:创建右侧字母索引 🔤

目标:实现可点击的字母导航,支持快速跳转

关键实现点

  • 渲染 A-Z 字母列表
  • 监听字母点击事件
  • 实现高亮状态切换
  • 调用 scrollToIndex 跳转到对应分组

核心代码示例

typescript
List() {
  ForEach(alphabetList, (letter, index) => {
    ListItem() {
      Text(letter)
        .fontSize(12)
        .fontColor(this.currentLetter === letter ? '#007DFF' : '#666')
        .fontWeight(this.currentLetter === letter ? 600 : 400)
    }
    .onClick(() => {
      // 跳转到对应分组
      const targetIndex = this.getGroupStartIndex(letter);
      this.listController.scrollToIndex(targetIndex);
      this.currentLetter = letter;
    })
  })
}

视觉效果:字母导航清晰,点击有反馈,支持快速跳转


步骤 5:实现双向联动效果 🔄

目标:滚动左侧列表时,右侧字母自动高亮;点击右侧字母时,左侧列表跳转

关键实现点

  • 监听 onScrollIndex 事件
  • 根据当前显示区域计算对应字母
  • 更新右侧字母高亮状态
  • 实现平滑的联动效果

核心代码示例

typescript
List() {
  // 城市列表内容
}
.onScrollIndex((start, end) => {
  // 根据当前显示的索引范围,计算对应的字母分组
  const currentGroup = this.getCurrentGroupByIndex(start);
  if (currentGroup && this.currentLetter !== currentGroup.title) {
    this.currentLetter = currentGroup.title;
  }
})

视觉效果:滚动流畅,字母高亮准确,用户体验良好

相关文档链接

🎬 最终实现效果

通过以上步骤,我们成功实现了分组吸顶的完整功能:

核心功能展示:

效果说明:

  1. 分组显示:城市按照"当前城市"、"热门城市"、"A-Z 字母"清晰分组
  2. 吸顶效果:滚动时,当前分组标题自动吸附在顶部
  3. 字母索引:右侧 A-Z 字母导航,当前分组字母高亮显示
  4. 快速跳转:点击右侧字母,左侧列表立即跳转到对应分组
  5. 双向联动:滚动左侧列表,右侧字母高亮状态实时更新

技术特点总结:

  • 智能分组:支持多种分组类型(当前、热门、字母)
  • 精准吸顶:ListItemGroup 的 sticky 属性确保标题准确吸顶
  • 快速导航:字母索引提供一键跳转功能
  • 实时联动:滚动和点击事件完美同步
  • 用户友好:符合用户对通讯录等应用的使用习惯

适用场景回顾: 这种分组吸顶效果广泛应用于需要大量数据分类展示的场景,如通讯录、城市选择、公司列表等,能够显著提升用户查找效率和使用体验。

性能优势:

  • 🚀 高效渲染:List 组件的虚拟滚动确保大数据量下的流畅性能
  • 💾 内存优化:只渲染可见区域的列表项,节省内存占用
  • 快速响应:原生组件确保交互响应速度

二级联动场景

📖 场景说明

什么是二级联动效果?

这是一个经典的"主从"关系界面,最常见的例子就是电商 APP 的商品分类页面:

  1. 左侧一级分类:显示主要分类(如"服装"、"数码"、"家居"等)
  2. 右侧二级分类:显示选中一级分类下的子分类
  3. 联动关系:点击左侧分类 → 右侧内容立即更新
  4. 反向联动:滚动右侧内容 → 左侧对应分类高亮
  5. 吸顶效果:右侧列表的分组标题支持吸顶显示

为什么需要这个效果?

  • 🎯 层级清晰:将复杂的分类体系分解为两个层级,降低认知负担
  • 🔄 高效导航:左侧提供快速分类切换,右侧展示详细内容
  • 👀 状态同步:用户始终知道当前浏览的是哪个分类
  • 📱 空间利用:充分利用屏幕空间,展示更多信息

常见应用场景:

  • 🛒 电商分类:商品的一级分类和二级分类选择
  • 🎨 编辑工具:风格分类和具体风格选择
  • 📚 内容分类:文章分类和子分类浏览
  • 🏢 组织架构:部门和子部门的层级展示
  • 🎵 音乐分类:音乐类型和具体歌单分类
  • 🍔 餐饮菜单:菜品分类和具体菜品展示

🎯 本例效果预览

我们将实现一个商品分类选择页面,包含:

  1. 左侧一级分类导航

    • 垂直排列的主要分类列表
    • 当前选中分类高亮显示
    • 点击切换分类功能
  2. 右侧二级内容区域

    • 显示选中分类的详细子分类
    • 支持分组标题吸顶效果
    • 丰富的内容展示(图片+文字)
  3. 双向联动机制

    • 主动联动:点击左侧分类 → 右侧内容切换
    • 被动联动:滚动右侧内容 → 左侧分类高亮更新
    • 状态保持:切换分类时保持滚动位置

🏗️ 页面结构设计

整体布局架构:

页面整体结构图页面效果图
左右分栏布局:左侧导航 + 右侧内容实际效果:清晰的分类层级和丰富的内容展示

🔧 实现原理详解

核心思想:双 List 联动 + 状态同步

想象一下,这就像是一个"智能目录系统":

  • 📖 左侧目录:显示章节标题,点击可快速跳转
  • 📄 右侧内容:显示具体内容,滚动时目录自动跟随
  • 🔗 智能联动:目录和内容始终保持同步

技术架构:

Row (水平布局)
├── 左侧导航区域 (List)
│   ├── 分类1 (ListItem) - 可点击
│   ├── 分类2 (ListItem) - 可点击
│   └── 分类N (ListItem) - 可点击
└── 右侧内容区域 (List)
    ├── ListItemGroup (分类1内容)
    │   ├── 分组标题 (sticky吸顶)
    │   └── 子分类项目 (ListItem)
    ├── ListItemGroup (分类2内容)
    └── ListItemGroup (分类N内容)

关键技术点:

  1. 🎯 双向监听机制

    • 左侧 List 监听点击事件 → 控制右侧 List 跳转
    • 右侧 List 监听滚动事件 → 更新左侧 List 高亮
  2. 📡 状态同步算法

    typescript
    // 右侧滚动时,计算当前显示的分类
    .onScrollIndex((start, end) => {
      const currentCategory = this.getCategoryByIndex(start);
      this.updateLeftHighlight(currentCategory);
    })
  3. ⚡ 精准跳转控制

    typescript
    // 左侧点击时,跳转到对应分类
    .onClick(() => {
      const targetIndex = this.getCategoryStartIndex(category);
      this.rightListController.scrollToIndex(targetIndex);
    })

相关文档链接

🛠️ 开发步骤详解

步骤 1:构建数据结构 📊

目标:创建支持二级联动的分类数据结构

关键实现点

  • 定义一级分类和二级分类的数据模型
  • 建立分类之间的映射关系
  • 计算每个分类在右侧列表中的起始位置

核心代码示例

typescript
// 分类数据模型
interface Category {
  id: string;
  name: string;
  icon?: string;
  subCategories: SubCategory[];
  startIndex: number; // 在右侧列表中的起始位置
}

interface SubCategory {
  id: string;
  name: string;
  image?: string;
  description?: string;
}

// 构建完整的分类数据
const categoryData: Category[] = [
  {
    id: '1',
    name: '服装鞋帽',
    subCategories: [...],
    startIndex: 0
  },
  // 更多分类...
];

步骤 2:创建左右分栏布局 🏗️

目标:使用 Row 组件实现左右分栏显示

关键实现点

  • Row 容器包含左侧导航和右侧内容
  • 合理分配左右区域的宽度比例
  • 设置合适的分割线或背景色区分

核心代码示例

typescript
Row() {
  // 左侧分类导航
  List() {
    ForEach(categories, (category) => {
      ListItem() {
        this.categoryNavItem(category)
      }
      .onClick(() => this.onCategoryClick(category))
    })
  }
  .width('25%')
  .backgroundColor('#F5F5F5')

  // 右侧内容区域
  List() {
    ForEach(categories, (category) => {
      ListItemGroup({
        header: this.categoryHeader(category.name),
        sticky: StickyStyle.Header
      }) {
        ForEach(category.subCategories, (subCategory) => {
          ListItem() {
            this.subCategoryItem(subCategory)
          }
        })
      }
    })
  }
  .width('75%')
  .onScrollIndex((start, end) => this.onRightScroll(start, end))
}

步骤 3:实现联动控制逻辑 🔄

目标:实现左右列表的双向联动效果

关键实现点

  • 左侧点击事件处理
  • 右侧滚动事件监听
  • 状态同步和高亮更新
  • 防抖处理避免频繁更新

核心代码示例

typescript
// 左侧分类点击处理
onCategoryClick(category: Category) {
  this.currentCategoryId = category.id;
  this.rightListController.scrollToIndex(category.startIndex);
}

// 右侧列表滚动处理
onRightScroll(start: number, end: number) {
  const currentCategory = this.getCategoryByIndex(start);
  if (currentCategory && this.currentCategoryId !== currentCategory.id) {
    this.currentCategoryId = currentCategory.id;
  }
}

// 根据索引获取对应分类
getCategoryByIndex(index: number): Category | null {
  for (const category of this.categories) {
    const endIndex = category.startIndex + category.subCategories.length;
    if (index >= category.startIndex && index < endIndex) {
      return category;
    }
  }
  return null;
}

🎬 最终实现效果

通过以上步骤,我们成功实现了二级联动的完整功能:

核心功能展示:

效果说明:

  1. 分类导航:左侧显示主要分类,当前选中分类高亮显示
  2. 内容展示:右侧显示选中分类的详细子分类内容
  3. 主动联动:点击左侧分类,右侧内容立即切换到对应分类
  4. 被动联动:滚动右侧内容,左侧分类高亮状态自动更新
  5. 吸顶效果:右侧分组标题支持吸顶显示,提升浏览体验

技术特点总结:

  • 响应迅速:点击和滚动事件响应及时,用户体验流畅
  • 状态同步:左右两侧状态始终保持一致
  • 视觉清晰:分类层级清晰,内容组织有序
  • 交互友好:符合用户对分类选择界面的使用习惯
  • 性能优化:使用 List 组件的虚拟滚动,支持大量数据展示

适用场景总结: 这种二级联动效果特别适合需要展示层级分类数据的场景,如电商分类、内容分类、组织架构等,能够帮助用户快速定位和浏览目标内容,显著提升应用的易用性和用户满意度。


🎓 学习总结与进阶指南

📚 知识点回顾

通过本教程,你已经掌握了:

技能等级掌握内容实用性
基础级List、ListItem、Image、Text 等基础组件⭐⭐⭐⭐⭐
进阶级Swiper 轮播、Grid 网格、下拉刷新⭐⭐⭐⭐
高级级sticky 吸顶、nestedScroll 嵌套滚动、双向联动⭐⭐⭐

🚀 实战建议

立即可以做的项目:

  1. 📱 仿淘宝首页:应用多类型列表项技能
  2. 📰 新闻 APP:使用 Tabs 吸顶功能
  3. 📞 通讯录应用:练习分组吸顶效果
  4. 🛒 购物分类页:实现二级联动

进阶学习方向:

  • 🎨 动画效果:为列表添加炫酷的过渡动画
  • 🔄 状态管理:学习复杂数据的状态管理
  • 🌐 网络请求:结合真实 API 接口获取数据
  • 📱 响应式设计:适配不同尺寸的设备

💡 开发小贴士

性能优化:

  • ✅ 列表数据较多时,List 组件会自动进行虚拟滚动优化
  • ✅ 图片建议使用适当尺寸,避免内存浪费
  • ✅ 合理使用@State 状态管理,避免不必要的重新渲染

调试技巧:

  • 🐛 使用console.log()输出调试信息
  • 🐛 利用 DevEco Studio 的预览功能实时查看效果
  • 🐛 善用官方文档和示例代码

代码规范:

  • 📝 组件名称使用大驼峰命名(如 SearchHeader)
  • 📝 添加有意义的注释,方便后续维护
  • 📝 保持代码结构清晰,一个组件完成一个功能

📖 相关资源

官方文档:

学习社区:

🎯 下一步学习建议

  1. 巩固基础:多敲代码,熟练掌握基础组件
  2. 实战练习:选择一个感兴趣的 APP 界面进行仿制
  3. 深入学习:探索更多高级组件和 API
  4. 参与社区:加入开发者群体,交流学习心得

🌟 记住:编程是一个不断实践和提升的过程,每写一行代码都是在进步!加油!

如果这篇文章对你有帮助,欢迎点赞、收藏和分享!有问题欢迎在评论区讨论~

Released under the MIT License.