第5篇:列表渲染——展示大量数据

本课目标:掌握ForEach循环渲染和List组件,能展示大量数据
**作者:**中文编程倡导者—— 李金雨
预计课时:2课时(90分钟)
难度等级:⭐⭐⭐(进阶)


一、开篇引入

1.1 生活中的列表

生活中到处都有"列表":

  • 📋 班级花名册(50个同学的名字)
  • 🛒 购物清单(要买的东西)
  • 📱 微信通讯录(几百个好友)
  • 📰 新闻列表(几十条新闻)

如果让你一个个写出来,那太麻烦了!

1.2 程序中的列表

假设你要显示一个班级50个学生的信息:

笨方法(不要这样做):

Text("张三")
Text("李四")
Text("王五")
// ... 重复50次!

聪明方法

ForEach(学生列表, (学生) => {
  Text(学生.姓名)
})
// 一行代码搞定!

1.3 本课目标

今天我们要学习:

  1. ForEach 循环显示列表
  2. ListListItem 优化长列表
  3. 列表的点击和交互
  4. 实战:通讯录、商品列表、待办事项

1.4 预期成果

完成本课后,你能做出这样的应用:

通讯录:                商品列表:              待办事项:
┌─────────────┐        ┌─────────────┐        ┌─────────────┐
│  📇 通讯录   │        │  🛍️ 商品列表 │        │  📋 待办事项 │
├─────────────┤        ├─────────────┤        ├─────────────┤
│ 👤 张三     │        │ 📱 手机  ¥2999│       │ ☑️ 完成作业  │
│ 👤 李四     │        │ 💻 电脑  ¥5999│       │ ☐ 买牛奶    │
│ 👤 王五     │        │ 🎧 耳机  ¥199 │       │ ☑️ 锻炼身体  │
│ 👤 赵六     │        │ ⌚ 手表  ¥899 │       │ ☐ 洗衣服    │
│ ...         │        │ ...         │        │ ...         │
│             │        │             │        │             │
│  [添加]     │        │  [购物车]   │        │  [+ 新建]   │
└─────────────┘        └─────────────┘        └─────────────┘

二、概念讲解

2.1 ForEach——循环渲染

什么是ForEach?

ForEach 就像一个"复制机":

  • 你给它一个列表
  • 告诉它每个项目怎么显示
  • 它会自动为每个项目生成对应的界面
基本语法
ForEach(
  数据列表,                    // 要遍历的数组
  (项目: 类型, 索引: number) => {  // 对每个项目做什么
    // 界面代码
  },
  (项目: 类型) => 唯一标识     // 可选:帮助ArkTS识别每个项目
)
简单例子
@State 水果列表: string[] = ["苹果", "香蕉", "橙子", "葡萄"]

build() {
  Column() {
    ForEach(this.水果列表, (水果: string) => {
      Text(水果)
        .fontSize(20)
        .margin(10)
    })
  }
}

效果:

苹果
香蕉
橙子
葡萄
带索引的例子
ForEach(this.水果列表, (水果: string, 序号: number) => {
  Text(`${序号 + 1}. ${水果}`)
    .fontSize(20)
})

效果:

1. 苹果
2. 香蕉
3. 橙子
4. 葡萄

2.2 List和ListItem——优化长列表

为什么要用List?

当列表很长时(比如1000条数据),如果全部显示出来:

  • 占用大量内存
  • 界面卡顿
  • 加载慢

List组件会"智能加载":

  • 只加载屏幕上能看到的部分
  • 滑出屏幕的会自动回收
  • 就像传送带,循环利用
List的基本用法
List({ space: 10 }) {           // space: 列表项之间的间距
  ForEach(this.数据列表, (项目) => {
    ListItem() {                 // 每个列表项用ListItem包裹
      // 列表项的内容
      Text(项目.名称)
    }
  })
}
.width('100%')
.height('100%')
.padding(20)
List的重要属性
List() {
  // ...
}
.width('100%')
.height('100%')
.listDirection(Axis.Vertical)     // 垂直排列(默认)
.divider({                        // 分隔线
  strokeWidth: 1,                 // 线宽
  color: '#EEEEEE'                // 颜色
})
.edgeEffect(EdgeEffect.Spring)    // 边缘效果(弹簧效果)
.scrollBar(BarState.Auto)         // 滚动条(自动显示)

2.3 数据类型定义

为什么要定义类型?

当列表项比较复杂时(有多个字段),我们最好定义一个"模板":

// 定义学生信息的结构
interface 学生信息 {
  学号: string
  姓名: string
  年龄: number
  成绩: number
}

// 使用这个类型
@State 学生列表: 学生信息[] = [
  { 学号: "001", 姓名: "张三", 年龄: 15, 成绩: 95 },
  { 学号: "002", 姓名: "李四", 年龄: 16, 成绩: 88 },
  { 学号: "003", 姓名: "王五", 年龄: 15, 成绩: 92 }
]

好处

  • 代码更清晰
  • 有自动提示
  • 不容易出错

2.4 列表的交互

点击列表项
ListItem() {
  Text(学生.姓名)
}
.onClick(() => {
  console.log(`点击了${学生.姓名}`)
})
滑动操作(左滑删除)
ListItem() {
  Text(学生.姓名)
}
.swipeAction({
  end: {                        // 从右向左滑显示的操作
    Button("删除")
      .onClick(() => {
        this.删除学生(学生.学号)
      })
  }
})

三、动手实践

3.1 基础练习:班级通讯录

做一个简单的班级通讯录:

// 完整可运行代码,复制到 Index.ets 即可运行

// 定义学生信息类型
interface 学生信息 {
  学号: string
  姓名: string
  性别: string
  年龄: number
  电话: string
}

@Entry
@Component
struct Index {
  @State 学生列表: 学生信息[] = [
    { 学号: "202401", 姓名: "张三", 性别: "男", 年龄: 15, 电话: "138****1111" },
    { 学号: "202402", 姓名: "李四", 性别: "女", 年龄: 15, 电话: "139****2222" },
    { 学号: "202403", 姓名: "王五", 性别: "男", 年龄: 16, 电话: "137****3333" },
    { 学号: "202404", 姓名: "赵六", 性别: "女", 年龄: 15, 电话: "136****4444" },
    { 学号: "202405", 姓名: "孙七", 性别: "男", 年龄: 16, 电话: "135****5555" },
    { 学号: "202406", 姓名: "周八", 性别: "女", 年龄: 15, 电话: "134****6666" },
    { 学号: "202407", 姓名: "吴九", 性别: "男", 年龄: 15, 电话: "133****7777" },
    { 学号: "202408", 姓名: "郑十", 性别: "女", 年龄: 16, 电话: "132****8888" }
  ]
  
  @State 选中项: string = ""

  build() {
    Column() {
      // 标题栏
      Row() {
        Text("📇 班级通讯录")
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
        
        Text(`${this.学生列表.length}`)
          .fontSize(14)
          .fontColor("#999999")
      }
      .width('100%')
      .justifyContent(FlexAlign.SpaceBetween)
      .padding(20)
      .backgroundColor('#2196F3')
      .fontColor('#FFFFFF')
      
      // 学生列表
      List({ space: 1 }) {
        ForEach(this.学生列表, (学生: 学生信息) => {
          ListItem() {
            Row({ space: 15 }) {
              // 头像
              Text(学生.性别 == "男" ? "👦" : "👧")
                .fontSize(40)
              
              // 信息
              Column({ space: 5 }) {
                Text(学生.姓名)
                  .fontSize(18)
                  .fontWeight(FontWeight.Medium)
                
                Row({ space: 10 }) {
                  Text(`学号: ${学生.学号}`)
                    .fontSize(12)
                    .fontColor("#999999")
                  
                  Text(`${学生.年龄}`)
                    .fontSize(12)
                    .fontColor("#999999")
                }
              }
              .layoutWeight(1)
              .alignItems(HorizontalAlign.Start)
              
              // 电话
              Text(学生.电话)
                .fontSize(14)
                .fontColor("#2196F3")
            }
            .width('100%')
            .padding(15)
            .backgroundColor(this.选中项 == 学生.学号 ? '#E3F2FD' : '#FFFFFF')
          }
          .onClick(() => {
            this.选中项 = 学生.学号
            console.log(`选中了:${学生.姓名}`)
          })
        })
      }
      .width('100%')
      .layoutWeight(1)
      .divider({ strokeWidth: 1, color: '#EEEEEE' })
      
      // 底部按钮
      Row({ space: 20 }) {
        Button("添加学生", { type: ButtonType.Capsule })
          .backgroundColor('#4CAF50')
          .onClick(() => {
            this.添加学生()
          })
        
        Button("清空列表", { type: ButtonType.Capsule })
          .backgroundColor('#F44336')
          .onClick(() => {
            this.学生列表 = []
          })
      }
      .padding(20)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
  }
  
  添加学生() {
    let 新学生: 学生信息 = {
      学号: `2024${(this.学生列表.length + 9).toString().padStart(2, '0')}`,
      姓名: `新同学${this.学生列表.length + 1}`,
      性别: this.学生列表.length % 2 == 0 ? "男" : "女",
      年龄: 15,
      电话: "138****0000"
    }
    this.学生列表.push(新学生)
    // 重新赋值触发更新
    this.学生列表 = [...this.学生列表]
  }
}

3.2 进阶练习:商品列表

做一个带分类和购物车的商品列表:

// 完整可运行代码,复制到 Index.ets 即可运行

// 定义商品类型
interface 商品信息 {
  编号: string
  名称: string
  价格: number
  原价: number
  销量: number
  分类: string
  标签: string[]
}

@Entry
@Component
struct Index {
  @State 商品列表: 商品信息[] = [
    { 编号: "P001", 名称: "无线蓝牙耳机", 价格: 299, 原价: 399, 销量: 12580, 分类: "数码", 标签: ["热销", "包邮"] },
    { 编号: "P002", 名称: "智能手环 Pro", 价格: 199, 原价: 259, 销量: 8560, 分类: "数码", 标签: ["新品"] },
    { 编号: "P003", 名称: "便携充电宝", 价格: 89, 原价: 129, 销量: 23150, 分类: "数码", 标签: ["爆款"] },
    { 编号: "P004", 名称: "纯棉T恤", 价格: 59, 原价: 99, 销量: 5680, 分类: "服装", 标签: ["特价"] },
    { 编号: "P005", 名称: "运动鞋", 价格: 299, 原价: 499, 销量: 3420, 分类: "服装", 标签: ["限时"] },
    { 编号: "P006", 名称: "零食大礼包", 价格: 49, 原价: 79, 销量: 18900, 分类: "食品", 标签: ["热销", "包邮"] },
    { 编号: "P007", 名称: "坚果礼盒", 价格: 128, 原价: 168, 销量: 5670, 分类: "食品", 标签: ["新品"] },
    { 编号: "P008", 名称: "保温杯", 价格: 69, 原价: 99, 销量: 9870, 分类: "家居", 标签: [] }
  ]
  
  @State 当前分类: string = "全部"
  @State 购物车数量: number = 0
  
  // 获取分类列表
  get 分类列表(): string[] {
    let 分类集: Set<string> = new Set()
    分类集.add("全部")
    this.商品列表.forEach(商品 => 分类集.add(商品.分类))
    return Array.from(分类集)
  }
  
  // 获取过滤后的商品
  get 过滤后商品(): 商品信息[] {
    if (this.当前分类 == "全部") {
      return this.商品列表
    }
    return this.商品列表.filter(商品 => 商品.分类 == this.当前分类)
  }

  build() {
    Column() {
      // 标题栏
      Row() {
        Text("🛍️ 商品列表")
          .fontSize(22)
          .fontWeight(FontWeight.Bold)
        
        Row({ space: 5 }) {
          Text("🛒")
            .fontSize(24)
          Text(`${this.购物车数量}`)
            .fontSize(14)
            .fontColor('#FFFFFF')
            .backgroundColor('#F44336')
            .borderRadius(10)
            .padding({ left: 6, right: 6, top: 2, bottom: 2 })
        }
      }
      .width('100%')
      .justifyContent(FlexAlign.SpaceBetween)
      .padding(15)
      .backgroundColor('#FFFFFF')
      
      // 分类标签
      Scroll() {
        Row({ space: 10 }) {
          ForEach(this.分类列表, (分类: string) => {
            Text(分类)
              .fontSize(14)
              .fontColor(this.当前分类 == 分类 ? '#FFFFFF' : '#666666')
              .padding({ left: 15, right: 15, top: 6, bottom: 6 })
              .backgroundColor(this.当前分类 == 分类 ? '#2196F3' : '#F5F5F5')
              .borderRadius(15)
              .onClick(() => {
                this.当前分类 = 分类
              })
          })
        }
        .padding(15)
      }
      .scrollBar(BarState.Off)
      .scrollable(ScrollDirection.Horizontal)
      
      // 商品列表
      List({ space: 10 }) {
        ForEach(this.过滤后商品, (商品: 商品信息) => {
          ListItem() {
            this.商品卡片(商品)
          }
        })
      }
      .padding(15)
      .layoutWeight(1)
      .backgroundColor('#F5F5F5')
    }
    .width('100%')
    .height('100%')
  }
  
  // 商品卡片
  @Builder
  商品卡片(商品: 商品信息) {
    Row({ space: 12 }) {
      // 图片区域
      Column() {
        Text("📦")
          .fontSize(50)
      }
      .width(100)
      .height(100)
      .backgroundColor('#E3F2FD')
      .borderRadius(8)
      .justifyContent(FlexAlign.Center)
      
      // 信息区域
      Column({ space: 8 }) {
        // 名称和标签
        Row({ space: 5 }) {
          Text(商品.名称)
            .fontSize(16)
            .fontWeight(FontWeight.Medium)
            .layoutWeight(1)
          
          ForEach(商品.标签, (标签: string) => {
            Text(标签)
              .fontSize(10)
              .fontColor('#FFFFFF')
              .padding({ left: 4, right: 4, top: 1, bottom: 1 })
              .backgroundColor('#FF5722')
              .borderRadius(3)
          })
        }
        
        // 价格
        Row({ space: 8 }) {
          Text(`¥${商品.价格}`)
            .fontSize(20)
            .fontColor('#F44336')
            .fontWeight(FontWeight.Bold)
          
          Text(`¥${商品.原价}`)
            .fontSize(12)
            .fontColor('#999999')
            .decoration({ type: TextDecorationType.LineThrough })
          
          Text(`${((1 - 商品.价格 / 商品.原价) * 100).toFixed(0)}`)
            .fontSize(11)
            .fontColor('#FFFFFF')
            .padding({ left: 4, right: 4 })
            .backgroundColor('#FF5722')
            .borderRadius(3)
        }
        
        // 销量和按钮
        Row() {
          Text(`已售${商品.销量}`)
            .fontSize(12)
            .fontColor('#999999')
          
          Blank()
          
          Button("加入购物车", { type: ButtonType.Capsule })
            .fontSize(12)
            .height(28)
            .backgroundColor('#2196F3')
            .onClick(() => {
              this.购物车数量++
            })
        }
        .width('100%')
      }
      .layoutWeight(1)
      .alignItems(HorizontalAlign.Start)
    }
    .width('100%')
    .padding(12)
    .backgroundColor('#FFFFFF')
    .borderRadius(12)
  }
}

3.3 挑战练习:待办事项列表

做一个功能完整的待办事项应用:

// 完整可运行代码,复制到 Index.ets 即可运行

// 定义待办事项类型
interface 待办事项 {
  编号: string
  内容: string
  是否完成: boolean
  创建时间: string
}

@Entry
@Component
struct Index {
  @State 待办列表: 待办事项[] = [
    { 编号: "1", 内容: "完成数学作业", 是否完成: false, 创建时间: "2024-01-15" },
    { 编号: "2", 内容: "去超市买牛奶", 是否完成: true, 创建时间: "2024-01-15" },
    { 编号: "3", 内容: "锻炼身体30分钟", 是否完成: false, 创建时间: "2024-01-14" },
    { 编号: "4", 内容: "阅读课外书", 是否完成: false, 创建时间: "2024-01-14" }
  ]
  @State 新事项内容: string = ""
  @State 过滤条件: string = "全部"  // 全部、未完成、已完成
  
  // 计算属性
  get 未完成数量(): number {
    return this.待办列表.filter(事项 => !事项.是否完成).length
  }
  
  get 已完成数量(): number {
    return this.待办列表.filter(事项 => 事项.是否完成).length
  }
  
  get 过滤后列表(): 待办事项[] {
    switch (this.过滤条件) {
      case "未完成":
        return this.待办列表.filter(事项 => !事项.是否完成)
      case "已完成":
        return this.待办列表.filter(事项 => 事项.是否完成)
      default:
        return this.待办列表
    }
  }

  build() {
    Column() {
      // 标题栏
      Row() {
        Column({ space: 5 }) {
          Text("📋 待办事项")
            .fontSize(24)
            .fontWeight(FontWeight.Bold)
          
          Text(`未完成 ${this.未完成数量} 项 · 已完成 ${this.已完成数量}`)
            .fontSize(12)
            .fontColor('#999999')
        }
        .alignItems(HorizontalAlign.Start)
        
        Button("清空已完成")
          .fontSize(12)
          .height(32)
          .backgroundColor('#F44336')
          .onClick(() => {
            this.待办列表 = this.待办列表.filter(事项 => !事项.是否完成)
          })
      }
      .width('100%')
      .justifyContent(FlexAlign.SpaceBetween)
      .padding(20)
      .backgroundColor('#FFFFFF')
      
      // 输入区域
      Row({ space: 10 }) {
        TextInput({ placeholder: "添加新事项...", text: this.新事项内容 })
          .placeholderColor('#999999')
          .height(45)
          .backgroundColor('#F5F5F5')
          .borderRadius(8)
          .layoutWeight(1)
          .onChange((: string) => {
            this.新事项内容 =})
        
        Button("+", { type: ButtonType.Circle })
          .width(45)
          .height(45)
          .fontSize(24)
          .backgroundColor('#2196F3')
          .enabled(this.新事项内容.length > 0)
          .onClick(() => {
            this.添加事项()
          })
      }
      .padding(15)
      .backgroundColor('#FFFFFF')
      
      // 过滤标签
      Row({ space: 15 }) {
        ForEach(["全部", "未完成", "已完成"], (条件: string) => {
          Text(条件)
            .fontSize(14)
            .fontColor(this.过滤条件 == 条件 ? '#2196F3' : '#666666')
            .fontWeight(this.过滤条件 == 条件 ? FontWeight.Bold : FontWeight.Normal)
            .padding({ bottom: 8 })
            .border({ 
              width: { bottom: this.过滤条件 == 条件 ? 2 : 0 }, 
              color: '#2196F3' 
            })
            .onClick(() => {
              this.过滤条件 = 条件
            })
        })
      }
      .width('100%')
      .padding({ left: 20, right: 20 })
      .backgroundColor('#FFFFFF')
      
      // 待办列表
      List({ space: 1 }) {
        ForEach(this.过滤后列表, (事项: 待办事项) => {
          ListItem() {
            this.待办项(事项)
          }
          .swipeAction({
            end: {
              Button("删除")
                .width(80)
                .height('100%')
                .backgroundColor('#F44336')
                .fontColor('#FFFFFF')
                .onClick(() => {
                  this.删除事项(事项.编号)
                })
            }
          })
        })
      }
      .width('100%')
      .layoutWeight(1)
      .divider({ strokeWidth: 1, color: '#EEEEEE' })
      .backgroundColor('#FFFFFF')
      
      // 空状态提示
      if (this.过滤后列表.length == 0) {
        Column({ space: 10 }) {
          Text("📝")
            .fontSize(60)
          Text("暂无事项")
            .fontSize(16)
            .fontColor('#999999')
        }
        .position({ x: '50%', y: '60%' })
        .markAnchor({ x: '50%', y: '50%' })
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
  }
  
  // 待办项组件
  @Builder
  待办项(事项: 待办事项) {
    Row({ space: 12 }) {
      // 复选框
      Text(事项.是否完成 ? "☑️" : "⬜")
        .fontSize(24)
        .onClick(() => {
          this.切换完成状态(事项.编号)
        })
      
      // 内容
      Column({ space: 4 }) {
        Text(事项.内容)
          .fontSize(16)
          .fontColor(事项.是否完成 ? '#999999' : '#333333')
          .decoration({ 
            type: 事项.是否完成 ? TextDecorationType.LineThrough : TextDecorationType.None 
          })
        
        Text(事项.创建时间)
          .fontSize(11)
          .fontColor('#CCCCCC')
      }
      .layoutWeight(1)
      .alignItems(HorizontalAlign.Start)
    }
    .width('100%')
    .padding(15)
    .backgroundColor('#FFFFFF')
  }
  
  // 添加事项
  添加事项() {
    if (this.新事项内容.trim() == "") return
    
    let 新事项: 待办事项 = {
      编号: Date.now().toString(),
      内容: this.新事项内容.trim(),
      是否完成: false,
      创建时间: new Date().toLocaleDateString()
    }
    
    this.待办列表.unshift(新事项)  // 添加到开头
    this.待办列表 = [...this.待办列表]
    this.新事项内容 = ""
  }
  
  // 切换完成状态
  切换完成状态(编号: string) {
    let 索引 = this.待办列表.findIndex(事项 => 事项.编号 == 编号)
    if (索引 != -1) {
      this.待办列表[索引].是否完成 = !this.待办列表[索引].是否完成
      this.待办列表 = [...this.待办列表]
    }
  }
  
  // 删除事项
  删除事项(编号: string) {
    this.待办列表 = this.待办列表.filter(事项 => 事项.编号 != 编号)
  }
}

四、知识总结

4.1 核心概念回顾

  1. ForEach:循环渲染列表数据
  2. List/ListItem:优化长列表显示
  3. Interface:定义数据结构
  4. 计算属性:动态计算列表数据

4.2 关键代码速查

// 定义数据类型
interface 数据类型 {
  字段1: string
  字段2: number
}

// 定义状态数组
@State 列表: 数据类型[] = []

// ForEach循环渲染
ForEach(this.列表, (项目: 数据类型, 索引: number) => {
  // 渲染每个项目
})

// List优化长列表
List({ space: 10 }) {
  ForEach(this.列表, (项目) => {
    ListItem() {
      // 列表项内容
    }
  })
}

// 列表项点击
ListItem() {
  // ...
}
.onClick(() => {
  // 点击处理
})

// 左滑删除
ListItem() {
  // ...
}
.swipeAction({
  end: {
    Button("删除")
  }
})

4.3 数组操作速查

// 添加元素
this.列表 = [...this.列表, 新元素]      // 添加到末尾
this.列表 = [新元素, ...this.列表]      // 添加到开头

// 删除元素
this.列表 = this.列表.filter(item => item.id != 要删的id)

// 修改元素
this.列表 = this.列表.map(item => 
  item.id == 要改的id ? { ...item, 字段: 新值 } : item
)

// 查找元素
let 找到了 = this.列表.find(item => item.id == 要找的id)
let 索引 = this.列表.findIndex(item => item.id == 要找的id)

// 过滤数组
let 过滤后 = this.列表.filter(item => item.条件 == true)

4.4 常见错误提醒

错误现象 原因 解决方法
列表不更新 直接修改数组元素 重新赋值整个数组
ForEach报错 数据类型不匹配 检查interface定义
列表项不显示 没加ListItem 用ListItem包裹
滑动卡顿 没用List组件 改用List代替Column
删除错乱 没有唯一标识 给ForEach加第三个参数

五、课后作业

5.1 巩固练习(必做)

练习1:音乐播放列表

做一个音乐列表:

  • 显示歌曲名、歌手、时长
  • 点击播放(改变样式)
  • 可以删除歌曲

练习2:新闻列表

做一个新闻列表:

  • 显示标题、摘要、发布时间
  • 分类筛选(国内、国际、体育、娱乐)
  • 下拉刷新(模拟)

练习3:聊天记录

做一个聊天界面:

  • 左右消息气泡
  • 显示发送时间
  • 可以删除消息

5.2 创意编程(选做)

创意1:相册应用

  • 显示照片网格
  • 点击查看大图
  • 可以删除照片

创意2:课程表

  • 显示周一到周日的课程
  • 点击课程显示详情
  • 可以添加新课程

创意3:记账本

  • 记录收入和支出
  • 按月份筛选
  • 显示收支统计

5.3 下篇预习

下一篇,我们将学习条件渲染,根据条件显示不同内容。预习问题:

  1. 怎么实现登录/未登录显示不同界面?
  2. 怎么显示/隐藏某些内容?
  3. 怎么根据状态显示不同颜色?

附录:更多列表技巧

技巧1:分组列表

List() {
  // 第一组
  ListItemGroup({ header: this.分组标题("水果") }) {
    ForEach(this.水果列表, (水果) => {
      ListItem() { /* ... */ }
    })
  }
  
  // 第二组
  ListItemGroup({ header: this.分组标题("蔬菜") }) {
    ForEach(this.蔬菜列表, (蔬菜) => {
      ListItem() { /* ... */ }
    })
  }
}

技巧2:索引列表(字母索引)

List({ space: 0, initialIndex: 0 }) {
  // ...
}
.sticky(StickyStyle.Header)    // 分组标题吸顶

技巧3:网格列表

Grid() {
  ForEach(this.图片列表, (图片) => {
    GridItem() {
      Image(图片)
    }
  })
}
.columnsTemplate('1fr 1fr 1fr')  // 3列
.rowsGap(10)
.columnsGap(10)

恭喜你完成了第5篇的学习! 🎉

现在你已经掌握了列表渲染的核心技能,可以处理大量数据的展示了。记住:用List优化长列表,用ForEach循环渲染,用interface规范数据

下节课,我们将学习如何根据条件显示不同内容!

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐