代码如诗,列表如流。当指尖在屏幕滑动,信息如溪水般流淌,这大概就是数字时代最美的垂钓体验。


List组件介绍

首先我们先来看看官网对List组件的介绍

列表是一种复杂的容器,当列表项达到一定数量,内容超过屏幕大小时,可以自动提供滚动功能。它适合用于呈现同类数据类型或数据类型集,例如图片和文本。在列表中显示数据集合是许多应用程序中的常见要求(如通讯录、音乐列表、购物清单等)。

使用列表可以轻松高效地显示结构化、可滚动的信息。通过在List组件中按垂直或者水平方向线性排列子组件ListItemGroupListItem,为列表中的行或列提供单个视图,或使用循环渲染迭代一组行或列,或混合任意数量的单个视图和ForEach结构,构建一个列表。List组件支持使用条件渲染、循环渲染、懒加载等渲染控制方式生成子组件。

官网对List组件给出了篇幅很大的介绍,里面有非常详细的使用介绍,当然列表也是我们开发中最常用的组件之一。

官网文档:创建列表

根据官网文档,简单的整理了下常用的知识点:

分类 核心概念 说明 代码示例
基础概念 List容器 垂直排列的滚动容器 List() { ListItem() { } }
ListItem 列表项,必须放在List内 ListItem() { Content() }
列表方向 默认垂直,可设置水平 .listDirection(Axis.Vertical)
数据渲染 ForEach 遍历数组渲染列表项 ForEach(arr, item => ListItem() { })
键值生成器 优化渲染性能的关键 (item) => item.id
LazyForEach 懒加载大数据集 LazyForEach(dataSource)
布局控制 间距设置 列表项之间的间距 List({ space: 10 })
边距设置 列表内容与边界的距离 .padding(20)
对齐方式 列表项在容器中的对齐 .alignListItem(ListItemAlign.Center)
滚动控制 滚动条 显示/隐藏滚动条 .scrollBar(BarState.On)
边缘效果 滚动到边缘的效果 .edgeEffect(EdgeEffect.Spring)
滚动位置 控制滚动到指定位置 .scroller(controller)
性能优化 缓存数量 预渲染不可见项的数量 .cachedCount(5)
链式动画 开启/关闭链式动画 .chainAnimation(true)
懒加载 延迟创建复杂组件 LazyForEach + 简单子组件
交互功能 点击事件 列表项点击处理 .onClick(() => {})
滑动删除 支持侧滑删除操作 .deleteTransition
拖拽排序 支持拖拽重新排序 .dragStart相关事件
高级特性 粘性头部 头部悬停效果 ListItem().sticky(StickyStyle.Header)
分组列表 带分组的列表 结合Section组件使用
多类型项 不同样式的列表项 在ForEach中返回不同的ListItem
响应式布局 适应不同屏幕尺寸 使用百分比或响应式单位

项目实战

接下来基于我们【钓鱼云助手】首页的列表调整为例,一起看下怎么使用List组件吧

1. 模拟数据

调整一下我们的数据结构,增加点赞相关元素。

interface FishingSpot {
  id: string
  name: string;
  distance: string;
  status: string;
  imageUrl: string;
  isHot: boolean;
  likeCount: number;
}

因为我们暂时还没有调用后端接口,所以直接生成一批模拟数据。

const locations = ['东湖', '西山', '滨海', '翠竹', '青龙'];
const suffixes = ['垂钓中心', '生态渔场', '野钓基地', '休闲钓点'];
const statusOptions = [
  '最近一小时有人上鱼',
  '环境优美,设施完善',
  '今日鱼获量超过50斤',
  '新开放优质钓位',
  '周末举办钓鱼比赛',
  '提供渔具租赁服务'
];

  @State frequentlyPlaces: FishingSpot[] = (() => {
    const result: FishingSpot[] = [];
    for (let index = 0; index < 50; index++) {
      result.push({
        id: `${index + 1}`,
        name: `${locations[index % 5]}${suffixes[index % 4]}`,
        distance: `${(Math.random() * 15 + 1).toFixed(1)}km`,
        status: statusOptions[Math.floor(Math.random() * 6)],
        imageUrl: 'https://image.xiaoxiaofeng.site/blog/image/686eb37643134371e5cca347c4c87edb.jpg?xiaoxiaofeng',
        isHot: Math.random() > 0.6,
        likeCount: Math.floor(Math.random() * 100)
      });
    }
    return result;
  })();

2. 调整列表轮廓

添加新增的字段,增加列表点击后跳转到详情navigateToSpotDetail的事件。这里使用伪代码,模拟跳转详情页的实现了。

这里使用List组件,针对每一个ListItem属性,我们额外定义了一个FishingSpotItem,去实现内部的展示效果。

// 钓点列表
@Component
struct FishingSpotList {
  @Prop frequentlyPlaces: FishingSpot[];
  @State listHeight: number = 0;

  // 显示更多钓点
  showMoreSpots(): void {
    console.log('显示更多钓点');
  }

  // 导航到钓点详情
  navigateToSpotDetail(spotId: string): void {
    console.log('导航到钓点详情:', spotId);
  }

  build() {
    Column() {
      // 列表标题
      Row() {
        Text('钓点列表')
          .fontSize(18)
          .fontWeight(FontWeight.Bold)

        Blank()

        Text('更多')
          .fontSize(14)
          .fontColor('#007DFF')
          .onClick(() => {
            this.showMoreSpots();
          })
      }
      .width('100%')
      .padding({
        top: 20,
        bottom: 10,
        left: 20,
        right: 20
      })

      List({ space: 12 }) {
        ForEach(this.frequentlyPlaces, (item: FishingSpot) => {
          ListItem() {
            FishingSpotItem({
              name: item.name,
              distance: item.distance,
              status: item.status,
              imageUrl: item.imageUrl,
              isHot: item.isHot, // 新增热门标识
              likeCount: item.likeCount // 新增点赞数
            })
              .onClick(() => {
                this.navigateToSpotDetail(item.id);
              })
          }
        }, (item: FishingSpot) => item.id)
      }
      .width('100%')
    }
  }
}

3. 增强列表项交互体验

3.1 页面布局

这里增强每一行列表的展示方式。采用多层级布局组合。

image-20251014173352933

  • 采用 Row → [Stack, Column → [Row, Text, Row]] 的复合布局结构:

  • 外层 Row 实现水平整体布局

  • Stack 处理图片和标签的层叠效果

  • 内层 Column 垂直排列文本信息

  • 最内层 Row 实现底部按钮的水平分布

实现单个列表的布局,效果如下:

image-20251014173507325

3.2 样式与视觉效果

条件样式渲染
根据状态动态改变样式:

  • 按压状态改变背景色:this.isPressed ? '#F5F5F5' : '#FFFFFF'
  • 点赞状态改变文字颜色:this.isLiked ? '#FF6B6B' : '#999999'
  • 热门标签的条件渲染:if (this.isHot) 控制显示/隐藏

高级样式特性

  • 阴影效果:.shadow({ radius: 4, color: '#1A000000', offsetX: 0, offsetY: 2 })
  • 图片填充模式:ImageFit.Cover 保持比例并填满容器
  • 复杂圆角设置:borderRadius({ topLeft: 8, bottomRight: 8 })
  • 文本溢出处理:textOverflow 配合 maxLines 防止文字溢出

具体代码如下:

@Component
struct FishingSpotItem {
  private id: string = '';
  private name: string = '';
  private distance: string = '';
  private status: string = '';
  private imageUrl: string = '';
  private isHot: boolean = false;
  private likeCount: number = 0;
  @State isLiked: boolean = false;
  @State isPressed: boolean = false;

  build() {
    Row() {
      // 图片区域
      Stack() {
        Image(this.imageUrl)
          .width(120)
          .height(80)
          .borderRadius(8)
          .objectFit(ImageFit.Cover) // 保证图片比例
        
        // 热门标签
        if (this.isHot) {
          Row() {
            Text('热门')
              .fontSize(10)
              .fontColor('#FFFFFF')
          }
          .position({ x: 0, y: 0 })
          .backgroundColor('#FF6B6B')
          .padding({ left: 6, right: 6, top: 2, bottom: 2 })
          .borderRadius({ topLeft: 8, bottomRight: 8 })
        }
      }

      // 信息区域
      Column() {
        // 第一行:名称和距离
        Row() {
          Text(this.name)
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
            .layoutWeight(1)
            .textOverflow({ overflow: TextOverflow.Ellipsis })
            .maxLines(1)
          
          Text(this.distance)
            .fontSize(12)
            .fontColor('#666666')
        }
        .width('100%')

        // 第二行:状态信息
        Text(this.status)
          .fontSize(12)
          .fontColor('#999999')
          .width('100%')
          .textAlign(TextAlign.Start)
          .margin({ top: 4 })

        // 第三行:互动区域
        Row() {
          // 点赞按钮
          Row() {
            Image(this.isLiked ? $r('app.media.liked') : $r('app.media.like'))
              .width(16)
              .height(16)
            Text(this.likeCount.toString())
              .fontSize(12)
              .fontColor(this.isLiked ? '#FF6B6B' : '#999999')
              .margin({ left: 4 })
          }
          .onClick(() => {
            this.handleLike();
          })

          Blank()

          // 导航按钮
          Button('导航', { type: ButtonType.Normal })
            .width(60)
            .height(24)
            .fontSize(12)
            .backgroundColor('#007DFF')
        }
        .width('100%')
        .margin({ top: 8 })
      }
      .margin({ left: 12 })
      .layoutWeight(1)
      .alignItems(HorizontalAlign.Start)
    }
    .width('100%')
    .padding(12)
    .backgroundColor(this.isPressed ? '#F5F5F5' : '#FFFFFF')
    .borderRadius(12)
    .shadow({ radius: 4, color: '#1A000000', offsetX: 0, offsetY: 2 })
    .onTouch((event: TouchEvent) => {
      if (event.type === TouchType.Down) {
        this.isPressed = true;
      } else if (event.type === TouchType.Up || event.type === TouchType.Cancel) {
        this.isPressed = false;
      }
    })
  }

  // 点赞处理
  private handleLike(): void {
    this.isLiked = !this.isLiked;
    this.likeCount += this.isLiked ? 1 : -1;
    
    console.log(`钓点 ${this.id} 点赞状态: ${this.isLiked}`);
  }
}

4. 优化性能与用户体验

长列表的处理

循环渲染适用于短列表,当构建具有大量列表项的长列表时,如果直接采用循环渲染方式,会一次性加载所有的列表元素,会导致页面启动时间过长,影响用户体验。因此,推荐使用数据懒加载(LazyForEach)方式实现按需迭代加载数据,从而提升列表性能。

当使用懒加载方式渲染列表时,为了更好的列表滚动体验,减少列表滑动时出现白块,List组件提供了cachedCount参数用于设置列表项缓存数,懒加载方式只会预加载List显示区域外cachedCount的内容,而非懒加载会全部加载。无论懒加载还是非懒加载都只布局List显示区域+List显示区域外cachedCount的内容。

List() {  // ...}.cachedCount(3)

以垂直列表为例:

  • List设置cachedCount后,显示区域外上下各会预加载并布局cachedCount行ListItem。计算ListItem行数时,会计算ListItemGroup内部的ListItem行数。如果ListItemGroup内没有ListItem,则整个ListItemGroup算一行。
  • List下嵌套使用LazyForEach,并且LazyForEach下嵌套使用ListItemGroup时,LazyForEach会在List显示区域外上下各会创建cachedCount个ListItemGroup。

说明

  1. cachedCount的增加会增大UI的CPU、内存开销。使用时需要根据实际情况,综合性能和用户体验进行调整。
  2. 列表使用数据懒加载时,除了显示区域的列表项和前后缓存的列表项,其他列表项会被销毁。
// 高性能列表配置
List({ space: 12 }) {
  ForEach(this.frequentlyPlaces, (item: FishingSpot) => {
    ListItem() {
      LazyFishingSpotItem({ // 使用懒加载组件
        spotData: item
      })
    }
  }, (item: FishingSpot) => item.id)
}
.cachedCount(5) // 缓存可见区域外的5个项
.chainAnimation(false) // 关闭链式动画提升性能

成果展示

进入页面后,展示效果:

image-20251014175511270

往下滑动,展示效果:

image-20251014175443471

滑动到底部,展示效果:

image-20251014175443471

Logo

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

更多推荐