当前位置:   article > 正文

2.7、创建列表(List)

2.7、创建列表(List)

概述

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

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

我开发的 Demo 展示

在这里插入图片描述

以下代码均经过我 demo 的实战验证,确保代码和效果对应

布局与约束

列表作为一种容器,会自动按其滚动方向排列子组件,向列表中添加组件或从列表中移除组件会重新排列子组件。

如下图所示,在垂直列表中,List按垂直方向自动排列ListItemGroup或ListItem。

ListItemGroup用于列表数据的分组展示,其子组件也是ListItem。ListItem表示单个列表项,可以包含单个子组件。

布局

List除了提供垂直和水平布局能力、超出屏幕时可以滚动的自适应延伸能力之外,还提供了自适应交叉轴方向上排列个数的布局能力。

利用垂直布局能力可以构建单列或者多列垂直滚动列表,如下图所示。

垂直滚动列表

  • 单列
    在这里插入图片描述
    对应代码
@Entry
@Component
struct ListVerticalPage {

  @State listItems:Array<String> = []

  aboutToAppear() {
    for (var i =0;i< 50;i++) {
      this.listItems.push("")
    }
  }

  build() {
    Navigation() {
      List({space: 5}) {
        ForEach(this.listItems, ()=> {
          ListItem() {
             Stack()
               .width('100%')
               .height(100)
               .backgroundColor('#9dc3e6')
          }
          .padding({left:15, right:15})
        })
      }
    }
    .title('垂直滚动列表')
    .titleMode(NavigationTitleMode.Mini)
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 多列
    在这里插入图片描述
@Entry
@Component
struct ListMultiVerticalPage {

  @State listItems:Array<String> = []

  aboutToAppear() {
    for (var i =0;i< 50;i++) {
      this.listItems.push("")
    }
  }

  build() {
    Navigation() {
      List({space: 5}) {
        ForEach(this.listItems, ()=> {
          ListItem() {
             Stack()
               .width('100%')
               .height(100)
               .backgroundColor('#9dc3e6')
          }.padding({left:2,right:2})
        })
      }
      .lanes(2)
    }
    .title('垂直滚动多列')
    .titleMode(NavigationTitleMode.Mini)
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

水平滚动列表

  • 单列
    在这里插入图片描述
    对应代码
@Entry
@Component
struct ListHorizontalPage {

  @State listItems:Array<String> = []

  aboutToAppear() {
    for (var i =0;i< 50;i++) {
      this.listItems.push(`${i+1}`)
    }
  }

  build() {
    Navigation() {
      List({space: 5}) {
        ForEach(this.listItems, (item:string)=> {
          ListItem() {
             Text(item)
               .textAlign(TextAlign.Center)
               .width(50)
               .height(300)
               .backgroundColor('#9dc3e6')
          }
        })
      }.listDirection(Axis.Horizontal)
    }
    .title('水平滚动列表')
    .titleMode(NavigationTitleMode.Mini)
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 多列
    在这里插入图片描述
    对应代码
@Entry
@Component
struct ListMultiHorizontalPage {

  @State listItems:Array<String> = []

  aboutToAppear() {
    for (var i =0;i< 50;i++) {
      this.listItems.push(`${i+1}`)
    }
  }

  build() {
    Navigation() {
      List({space: 5}) {
        ForEach(this.listItems, (item:string)=> {
          ListItem() {
             Text(item)
               .textAlign(TextAlign.Center)
               .width(50)
               .height(300)
               .backgroundColor('#9dc3e6')
          }
        })
      }
      .lanes(2)
      .listDirection(Axis.Horizontal)
    }
    .title('水平滚动多列')
    .titleMode(NavigationTitleMode.Mini)
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

自定义列表样式

设置内容间距

在初始化列表时,如需在列表项之间添加间距,可以使用 space 参数。例如,在每个列表项之间沿主轴方向添加 55vp 的间距:

在这里插入图片描述

对应局部代码

List({space: 55}) {
  ForEach(this.listItems, ()=> {
      ListItem() {
        Stack()
          .width('100%')
          .height(100)
          .backgroundColor('#9dc3e6')
      }
    })
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

添加分隔线

效果图
在这里插入图片描述
对应代码

List() {
 ForEach(this.listItems, ()=> {
    ListItem() {
      Stack()
        .width('100%')
        .height(100)
        .backgroundColor('#9dc3e6')
    }
  })
}
.divider({
  strokeWidth: 1,
  startMargin: 60,
  endMargin: 10,
  color: '#ff0000'
})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

添加滚动条

效果图
在这里插入图片描述
对应代码

List() {
  ForEach(this.listItems, ()=> {
    ListItem() {
      Stack()
        .width('100%')
        .height(100)
        .backgroundColor('#9dc3e6')
    }
  })
}
.scrollBar(BarState.Auto)
.divider({
  strokeWidth: 1,
  color: '#ff0000'
})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

支持分组列表

在列表中支持数据的分组展示,可以使列表显示结构清晰,查找方便,从而提高使用效率。分组列表在实际应用中十分常见,如下图所示联系人列表。

在这里插入图片描述
对应代码

import router from '@ohos.router'
import { CodeView } from '../../../widget/CodeView'

interface ContactGroup {
    title:string
    contacts:Array<String>
}

@Entry
@Component
struct GroupListPage {

  contactsGroups: ContactGroup[] = [
    {
      title: 'A',
      contacts: [
        "安以轩",
        "安悦溪",        
      ],
    },
    {
      title: 'B',
      contacts: [
        "白敬亭",
        "白宇",       
      ],
    },
    ...
    }
  ]

  @Builder itemHead(text: string) {
    // 列表分组的头部组件,对应联系人分组A、B等位置的组件
    Text(text)
      .fontSize(20)
      .width('100%')
      .padding(10)
      .backgroundColor('#ffffff')
      .fontWeight(FontWeight.Bold)
  }

  @Builder itemContent(text: string) {
    // 列表分组的头部组件,对应联系人分组A、B等位置的组件
    Text(text)
      .padding({ left: 10, bottom: 10, top: 10 })
  }

  build() {
    Navigation() {
      List() {
        ForEach(this.contactsGroups, (item:ContactGroup)=>{
          ListItemGroup({ header: this.itemHead(item.title) }) {
            ForEach(item.contacts, (name:string)=> {
              ListItem() {
                this.itemContent(name)
              }
            })
          }
        })
      }
    }
    .title('支持分组列表')
    .titleMode(NavigationTitleMode.Mini)
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65

添加粘性标题

运行效果
在这里插入图片描述
对应代码

List() {
       ...
}
.sticky(StickyStyle.Header)  // 设置吸顶,实现粘性标题效果
  • 1
  • 2
  • 3
  • 4

控制滚动位置

在这里插入图片描述

对应代码

@Entry
@Component
struct ListScrollToPage {

  @State listItems:Array<String> = []

  private listScroller: Scroller = new Scroller();

  aboutToAppear() {
    for (var i =0;i< 50;i++) {
      this.listItems.push(`新闻${i+1}`)
    }
  }

  build() {
    Navigation() {
      Stack({ alignContent: Alignment.BottomEnd }) {
        List({ space: 5, scroller: this.listScroller }) {
          ForEach(this.listItems, (text: string) => {
            ListItem() {
              Text(text)
                .textAlign(TextAlign.Center)
                .width('100%')
                .height(250)
                .backgroundColor('#9dc3e6')
            }
          })
        }
        Image('image/scroll_to_top.svg')
          .width(50)
          .height(50)
          .margin({right: 10,bottom: 10})
          .onClick(()=> {
              this.listScroller.scrollToIndex(0)
          })
      }
    }
    .title('控制滚动位置')
    .titleMode(NavigationTitleMode.Mini)
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

响应滚动位置

在这里插入图片描述
对应代码

Stack() {
  List() {
    ...
  }
  .onScrollIndex((start, end)=> {
    this.firstIndex = start
  })
  Text(`当前第一个index:${this.firstIndex}`)
    ...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

响应列表项侧滑

在这里插入图片描述
对应代码

@Entry
@Component
struct SwipeListPage {

  @State listItems:Array<String> = []

  aboutToAppear() {
    for (var i =0;i< 50;i++) {
      this.listItems.push(`选项${i+1}`)
    }
  }

  @Builder itemEnd(index: number) {
    // 侧滑后尾端出现的组件
    Image("image/delete.svg")
      .width(20)
      .height(20)
      .onClick(() => {
        this.listItems.splice(index, 1);
      })
  }

  build() {
    Navigation() {
      List({space: 5}) {
        ForEach(this.listItems, (item, index)=> {
          ListItem() {
            Text(item)
              .textAlign(TextAlign.Center)
              .width('100%')
              .height(50)
              .backgroundColor('#9dc3e6')
          }
          .swipeAction({ end: this.itemEnd.bind(this,index) })
        })
      }
    }
    .title('左滑删除列表')
    .titleMode(NavigationTitleMode.Mini)
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

给列表项添加标记

添加标记是一种无干扰性且直观的方法,用于显示通知或将注意力集中到应用内的某个区域。例如,当消息列表接收到新消息时,通常对应的联系人头像的右上方会出现标记,提示有若干条未读消息,如下图所示。
在这里插入图片描述

@Entry
@Component
struct BadgeListPage {

  @State listItems:Array<String> = []

  aboutToAppear() {
    for (var i =0;i< 50;i++) {
      this.listItems.push(`Item${i+1}`)
    }
  }

  build() {
    Navigation() {
      List({space: 5}) {
        ForEach(this.listItems, (item:string)=> {
          ListItem() {
            Row() {
              // 展示未读数
              Badge({
                count: 1,
                position: BadgePosition.RightTop,
                style: { badgeSize: 16, badgeColor: '#FA2A2D' }
              }) {
                // 未读数的头像
                Button()
                  .width(80)
                  .height(80)
                  .border({radius:90})
              }.margin({left:15})
              // 右侧文字
              Text(item)
                .fontColor(Color.White)
                .width('100%')
                .height(100)
                .padding({left:20})
            }
          }
        })
      }.backgroundColor(Color.Gray)
    }
    .title('列表项添加标记')
    .titleMode(NavigationTitleMode.Mini)
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

长列表的处理

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

当使用懒加载方式渲染列表时,为了更好的列表滚动体验,减少列表滑动时出现白块,List组件提供了cachedCount参数用于设置列表项缓存数,只在懒加载LazyForEach中生效。
在这里插入图片描述

@Entry
@Component
struct LazyForEachPage {

  dataSource:MyDataSource = new MyDataSource();

  aboutToAppear() {
    for (var i =0;i< 50;i++) {
      this.dataSource.pushData(`Item${i+1}`)
    }
  }

  build() {
    Navigation() {
      List({space: 5}) {
        LazyForEach(this.dataSource, (item:string)=> {
          ListItem() {
            Text(item)
              .width('100%')
              .height(100)
              .backgroundColor('#9dc3e6')
          }
        })
      }
    }
    .title('LazyForEach 列表')
    .titleMode(NavigationTitleMode.Mini)
  }
}


// Basic implementation of IDataSource to handle data listener
class BasicDataSource implements IDataSource {

  private listeners: DataChangeListener[] = [];

  getData(index: number) {
    return ""
  }
  totalCount(): number {
    return 0
  }

  // 该方法为框架侧调用,为LazyForEach组件向其数据源处添加listener监听
  registerDataChangeListener(listener: DataChangeListener): void {
    if (this.listeners.indexOf(listener) < 0) {
      console.info('add listener');
      this.listeners.push(listener);
    }
  }

  // 该方法为框架侧调用,为对应的LazyForEach组件在数据源处去除listener监听
  unregisterDataChangeListener(listener: DataChangeListener): void {
    const pos = this.listeners.indexOf(listener);
    if (pos >= 0) {
      console.info('remove listener');
      this.listeners.splice(pos, 1);
    }
  }

  // 通知LazyForEach组件需要重载所有子组件
  notifyDataReload(): void {
    this.listeners.forEach(listener => {
      listener.onDataReloaded();
    })
  }

  // 通知LazyForEach组件需要在index对应索引处添加子组件
  notifyDataAdd(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataAdd(index);
    })
  }

  // 通知LazyForEach组件在index对应索引处数据有变化,需要重建该子组件
  notifyDataChange(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataChange(index);
    })
  }

  // 通知LazyForEach组件需要在index对应索引处删除该子组件
  notifyDataDelete(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataDelete(index);
    })
  }
}

class MyDataSource extends BasicDataSource {
  private dataArray: string[] = [];

  totalCount(): number {
    return this.dataArray.length;
  }

  getData(index: number) {
    return this.dataArray[index];
  }

  public addData(index: number, data: string): void {
    this.dataArray.splice(index, 0, data);
    this.notifyDataAdd(index);
  }

  public pushData(data: string): void {
    this.dataArray.push(data);
    this.notifyDataAdd(this.dataArray.length - 1);
  }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111

上一篇 2.6、媒体查询(mediaquery)
下一篇 2.8、下拉刷新与上拉加载

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/316076
推荐阅读
相关标签
  

闽ICP备14008679号