HarmonyOS第十课——创建列表(List)

675 阅读11分钟

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

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

布局与约束

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

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

图1 List、ListItemGroup和ListItem组件关系

image.png

备注:List的子组件必须是ListItemGroup或ListItem,ListItem和ListItemGroup必须配合List来使用。

布局

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

利用垂直布局能力可以构建单列或者多列垂直滚动列表,如下图所示。 图2 垂直滚动列表(左:单列;右:多列)

image.png

利用水平布局能力可以是构建单行或多行水平滚动列表,如下图所示。 图3 水平滚动列表(左:单行;右:多行)

image.png

约束

列表的主轴方向是指子组件列的排列方向,也是列表的滚动方向。垂直于主轴的轴称为交叉轴,其方向与主轴方向相互垂直。

如下图所示,垂直列表的主轴是垂直方向,交叉轴是水平方向。水平列表的主轴是水平方向,交叉轴是垂直方向。

图4 列表的主轴与交叉轴

image.png

如果List组件主轴或交叉轴方向设置了尺寸,则其对应方向上的尺寸为设置值。

如果List组件主轴方向没有设置尺寸,当List子组件主轴方向总尺寸小于List的父组件尺寸时,List主轴方向尺寸自动适应子组件的总尺寸。

如下图所示,一个垂直列表B没有设置高度时,其父组件A高度为200vp,若其所有子组件C的高度总和为150vp,则此时列表B的高度为150vp。

图5 列表主轴高度约束示例1(A: List的父组件; B: List组件; C: List的所有子组件)

image.png

如果子组件主轴方向总尺寸超过List父组件尺寸时,List主轴方向尺寸适应List的父组件尺寸。

如下图所示,同样是没有设置高度的垂直列表B,其父组件A高度为200vp,若其所有子组件C的高度总和为300vp,则此时列表B的高度为200vp。

图6 列表主轴高度约束示例2(A: List的父组件; B: List组件; C: List的所有子组件)

image.png

List组件交叉轴方向在没有设置尺寸时,其尺寸默认自适应父组件尺寸。

开发布局

设置主轴方向

List组件主轴默认是垂直方向,即默认情况下不需要手动设置List方向,就可以构建一个垂直滚动列表。

若是水平滚动列表场景,将List的listDirection属性设置为Axis.Horizontal即可实现。listDirection默认为Axis.Vertical,即主轴默认是垂直方向。

List() {
  ...
}
.listDirection(Axis.Horizontal)

设置交叉轴布局**

List组件的交叉轴布局可以通过lanes和alignListItem属性进行设置,lanes属性用于确定交叉轴排列的列表项数量,alignListItem用于设置子组件在交叉轴方向的对齐方式。

List组件的lanes属性通常用于在不同尺寸的设备自适应构建不同行数或列数的列表。lanes属性的取值类型是"number | LengthConstrain",即整数或者LengthConstrain类型。以垂直列表为例,如果将lanes属性设为2,表示构建的是一个两列的垂直列表,如图2中右图所示。lanes的默认值为1,即默认情况下,垂直列表的列数是1。

List() {
  ...
}
.lanes(2)

当其取值为LengthConstrain类型时,表示会根据LengthConstrain与List组件的尺寸自适应决定行或列数。

List() {
  ...
}
.lanes({ minLength: 200, maxLength: 300 })

例如,假设在垂直列表中设置了lanes的值为{ minLength: 200, maxLength: 300 }。此时,

  • 当List组件宽度为300vp时,由于minLength为200vp,此时列表为一列。
  • 当List组件宽度变化至400vp时,符合两倍的minLength,则此时列表自适应为两列。

说明当lanes为LengthConstrain类型时,仅用于计算当前列表的行或列数,不影响列表项本身的尺寸。

同样以垂直列表为例,当alignListItem属性设置为ListItemAlign.Center表示列表项在水平方向上居中对齐。alignListItem的默认值是ListItemAlign.Start,即列表项在列表交叉轴方向上默认按首部对齐。

List() {
  ...
}
.alignListItem(ListItemAlign.Center)

在列表中显示数据

列表视图垂直或水平显示项目集合,在行或列超出屏幕时提供滚动功能,使其适合显示大型数据集合。在最简单的列表形式中,List静态地创建其列表项ListItem的内容。

由于在ListItem中只能有一个根节点组件,不支持以平铺形式使用多个组件。因此,若列表项是由多个组件元素组成的,则需要将这多个元素组合到一个容器组件内或组成一个自定义组件。

build() {
  Column(){
    List(){
      ListItem(){
        Text('红').fontSize(24)
      }
      ListItem(){
        Text('黑').fontSize(24)
      }
      ListItem(){
        Text('白').fontSize(24)
      }
      ListItem(){
        Text('黄').fontSize(24)
      }
      ListItem(){
        Row(){
          Image($r('app.media.icon'))
            .width(40)
            .height(40)
            .margin({top:10})
          Text('哈哈')
            .fontSize(20)
            .padding({left:10})
        }
      }
      ListItem(){
        Row(){
          Image($r('app.media.icon'))
            .width(40)
            .height(40)
            .margin({top:10})
          Text('呵呵')
            .fontSize(20)
            .padding({left:10})
        }
      }
    }.width('100%')
    .height('100%')
    .alignListItem(ListItemAlign.Center)
  }
  .width('100%')
  .height('100%')
}
image.png

迭代列表内容

通常更常见的是,应用通过数据集合动态地创建列表。使用循环渲染可从数据源中迭代获取数据,并在每次迭代过程中创建相应的组件,降低代码复杂度。

import util from  '@ohos.util'
class Contact{
  key: string = util.generateRandomUUID(true);
  name: string;
  icon: Resource;
  constructor(name: string,icon: Resource){
    this.name = name;
    this.icon = icon;
  }
}
@Entry
@Component
struct Index {
  @State message: string = 'Hello World'
  private contacts = [new Contact('哈哈',$r('app.media.icon')),
    new Contact('呵呵',$r('app.media.app_icon'))
  ]

  build() {
    Column(){
      List(){
        ForEach(this.contacts,(item: Contact) => {
          ListItem(){
            Row(){
              Image(item.icon)
                .width(40)
                .height(40)
                .margin(10)
              Text(item.name).fontSize(20)
            }.width('100%')
              .justifyContent(FlexAlign.Start)
          }
        },
          item => JSON.stringify(item))

      }.width('100%')
      .height('100%')
      .alignListItem(ListItemAlign.Center)
    }
    .width('100%')
    .height('100%')
  }
}

在List组件中,ForEach除了可以用来循环渲染ListItem,也可以用来循环渲染ListItemGroup。ListItemGroup的循环渲染详细使用请参见支持分组列表

自定义列表样式

设置内容间距

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

List({ space: 10 }) {
  ...
}

添加分隔线

List提供了divider属性用于给列表项之间添加分隔线。在设置divider属性时,可以通过strokeWidth和color属性设置分隔线的粗细和颜色。

List() {
  ...
}
.divider({
  strokeWidth: 1,
  startMargin: 60,
  endMargin: 10,
  color: '#ffe9f0f0'
})

从距离列表侧边起始端60vp开始到距离结束端10vp的位置,画一条粗细为1vp的分割线,可以实现图8设置列表分隔线的样式。 备注

  1. 分隔线的宽度会使ListItem之间存在一定间隔,当List设置的内容间距小于分隔线宽度时,ListItem之间的间隔会使用分隔线的宽度。

  2. 当List存在多列时,分割线的startMargin和endMargin作用于每一列上。

  3. List组件的分隔线画在两个ListItem之间,第一个ListItem上方和最后一个ListItem下方不会绘制分隔线。

添加滚动条

当列表项高度(宽度)超出屏幕高度(宽度)时,列表可以沿垂直(水平)方向滚动。在页面内容很多时,若用户需快速定位,可拖拽滚动条, 在使用List组件时,可通过scrollBar属性控制列表滚动条的显示。scrollBar的取值类型为BarState,当取值为BarState.Auto表示按需显示滚动条。此时,当触摸到滚动条区域时显示控件,可上下拖拽滚动条快速浏览内容,拖拽时会变粗。若不进行任何操作,2秒后滚动条自动消失。

List() {
  ...
}
.scrollBar(BarState.Auto)

支持分组列表

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

在List组件中可以直接使用一个或者多个ListItemGroup组件,ListItemGroup的宽度默认充满List组件。在初始化ListItemGroup时,可通过header参数设置列表分组的头部组件。

import util from  '@ohos.util'
class Contact{
  key: string = util.generateRandomUUID(true);
  name: string;
  icon: Resource;
  constructor(name: string,icon: Resource){
    this.name = name;
    this.icon = icon;
  }
}
@Entry
@Component
struct Index {
  @State message: string = 'Hello World'
 
  private  contactsGroups: object[] = [
    {
    title:"A",
    contacts:[
      new Contact('A哈哈',$r('app.media.app_icon'))
     ]
    },
    {
      title:"B",
      contacts:[
        new Contact('B哈哈',$r('app.media.app_icon'))
      ]
    },
    {
    title:"C",
    contacts:[
  new Contact('A哈哈',$r('app.media.app_icon'))
  ]
  },
  {
    title:"C",
    contacts:[
    new Contact('A哈哈',$r('app.media.app_icon'))
    ]
  }
  ]

  @Builder itemHead(text: string){
    
    Text(text)
      .fontSize(20)
      .backgroundColor('#ff1f3f5')
      .width('100%')
      .padding(5)
  }

  build() {
    Column(){
      List(){
        ForEach(this.contactsGroups,item =>{
          ListItemGroup({
            header: this.itemHead(item.title)
          })
          ForEach(item.contacts,contact =>{
            ListItem(){
              Row(){
                Image(contact.icon)
                  .width(40)
                  .height(40)
                Text(contact.name)
                  .fontSize(20)
                  .padding(10)
              }
              .margin({left:15})
              .width('100%')
            }

          }, contact => JSON.stringify(Contact))
          
        },item => JSON.stringify(item))
        
        
      }.width('100%')
      .height('100%')
      .alignListItem(ListItemAlign.Center)
      .divider({
        strokeWidth:1,
        startMargin:60,
        endMargin:10,
        color:'#ffe9f0f0'
      })
      .scrollBar(BarState.Auto)
    }
    .width('100%')
    .height('100%')
  }
}

添加粘性标题

粘性标题是一种常见的标题模式,常用于定位字母列表的头部元素。如下图所示,在联系人列表中滚动A部分时,B部分开始的头部元素始终处于A的下方。而在开始滚动B部分时,B的头部会固定在屏幕顶部,直到所有B的项均完成滚动后,才被后面的头部替代。

粘性标题不仅有助于阐明列表中数据的表示形式和用途,还可以帮助用户在大量信息中进行数据定位,从而避免用户在标题所在的表的顶部与感兴趣区域之间反复滚动。 List组件的sticky属性配合ListItemGroup组件使用,用于设置ListItemGroup中的头部组件是否呈现吸顶效果或者尾部组件是否呈现吸底效果。

通过给List组件设置sticky属性为StickyStyle.Header,即可实现列表的粘性标题效果。如果需要支持吸底效果,可以通过footer参数初始化ListItemGroup的底部组件,并将sticky属性设置为StickyStyle.Footer。

build() {
  Row() {
    Column() {
      List(){
        ListItemGroup({header:this.itemHeader("标题一")}){
          ListItem(){
          }
        }
      }.sticky(StickyStyle.Header)
    }
    .width('100%')
  }
  .height('100%')
}

控制滚动位置

控制滚动位置在实际应用中十分常见,例如当新闻页列表项数量庞大,用户滚动列表到一定位置时,希望快速滚动到列表底部或返回列表顶部。此时,可以通过控制滚动位置来实现列表的快速定位

List组件初始化时,可以通过scroller参数绑定一个Scroller对象,进行列表的滚动控制。例如,用户在新闻应用中,点击新闻页面底部的返回顶部按钮时,就可以通过Scroller对象的scrollToIndex方法使列表滚动到指定的列表项索引位置。

首先,需要创建一个Scroller的对象listScroller。

private listScroller: Scroller = new Scroller();

通过将listScroller用于初始化List组件的scroller参数,完成listScroller与列表的绑定。在需要跳转的位置指定scrollToIndex的参数为0,表示返回列表顶部。

@Entry
@Component
struct Index {
private  listScroller: Scroller = new Scroller();
  private  datas  = ['1','3','4','5','6','7','8','5','4','3','36','7','3','4','5','6','7',]
  build() {

    Column(){

      Stack({alignContent:Alignment.BottomEnd}){

        List({space: 20,scroller: this.listScroller}){
          ForEach(this.datas,item => {
            ListItem(){
              Row(){
                Text(item)
                  .fontSize(50)
                  .width('100')
                  .height('100%')
                  .textAlign(TextAlign.Center)
              }
              .width('100%')
              .height(100)
              .padding(10)
              .backgroundColor(Color.Red)
            }

          },item => JSON.stringify(item))
        }.width('100%')
        .height('100%')

        Button('回到顶部')
          .backgroundColor(Color.Orange)
          .onClick( () =>{
            //点击按钮时,指定跳转位置,返回列表顶部
            this.listScroller.scrollToIndex(0)
          })
          .margin({bottom:30,right:40})
          .width(100)
          .height(60)
      }

    }
    .width('100%')
    .height('100%')
  }
}