首页 > 编程笔记 > 通用技能 阅读:10

ArkUI List列表布局详解(附带实例)

列表是一种复杂的容器,当列表项达到一定数量、内容超过屏幕大小时,列表会自动提供滚动功能。

列表适用于呈现相同数据类型或数据类型集合,如图片和文本等。在列表中显示数据集合是许多应用程序中的常见需求,如通讯录、音乐列表、购物清单等。

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

List 组件支持使用条件渲染、循环渲染、懒加载等渲染控制方式生成子组件。

List布局与约束

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


图 1 List、ListItemGroup 和 ListItem 组件的关系

如上图所示,在垂直列表中,List 按垂直方向自动排列 ListItemGroup 或 ListItem。ListItemGroup 用于列表数据的分组展示,其子组件也是 ListItem。ListItem 表示单个列表项,可以包含单个子组件。

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

List布局

List 除了提供垂直和水平布局能力、超出屏幕时自动提供自适应的延伸能力之外,还提供了自适应交叉轴方向上并行排列多个元素的布局能力。利用垂直布局能力,可以构建单列或者多列垂直滚动列表,如下图所示:


图 2 垂直滚动列表(左:单列;右:多列)

List 的水平布局能力可用于构建单行或多行水平滚动列表,如下图所示:


图 3 水平滚动列表(左:单行;右:多行)

Grid 和 WaterFlow 也可以实现单列、多列布局,但如果需要每列等宽,且不需要跨行跨列布局,相比 Gird 和 WaterFlow,更推荐使用 List。

列表的主轴方向是指子组件列的排列方向,也是列表的滚动方向。垂直于主轴的轴称为交叉轴。如下图所示,垂直列表的主轴是垂直方向,交叉轴是水平方向;水平列表的主轴是水平方向,交叉轴是垂直方向。


图 4 列表的主轴与交叉轴

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

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

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


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

如果子组件主轴方向的总尺寸超过 List 父组件的尺寸,则 List 主轴方向的尺寸适应List父组件的尺寸。如下图所示,同样是没有设置高度的垂直列表 B,其父组件 A 的高度为 200vp,若其所有子组件 C 的高度总和为 300vp,则此时列表 B 的高度为 200vp。


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

如果 List 组件的交叉轴方向没有设置尺寸,则其尺寸默认自适应父组件尺寸。

List设置主轴方向

List 组件的主轴默认是垂直方向,即不需要手动设置 List 的方向,就可以构建一个垂直滚动列表。若要实现水平滚动列表场景,则将 List 的 listDirection 属性设置为 Axis.Horizontal 即可。listDirection 的默认值为 Axis.Vertical。

主轴方向设置示例代码如下:
@Entry
@Component
struct ListSample {
  build() {
    List() {
      ListItem() {
        Text().height('100%').width('30%').backgroundColor(Color.Blue)
      }
      ListItem() {
        Text().height('100%').width('30%').backgroundColor(Color.Brown)
      }// ...
    }
    // 设置主轴方向为水平方向
    .listDirection(Axis.Horizontal)
    .height('100%').width('100%')
  }
}
显示效果如下图所示,此时得到的是可以水平滚动的列表。


图 7 水平滚动列表显示效果

List设置交叉轴布局

List 组件的交叉轴布局可以通过 lanes 和 alignListItem 属性进行设置:
List 组件的 lanes 属性通常用于在不同尺寸的设备中自适应构建不同行数或列数的列表,即一次开发、多端部署,例如歌单列表。

lanes 属性的取值类型是 number | LengthConstrain,即整数或者 LengthConstrain 类型。以垂直列表为例,如果将 lanes 属性值设置为 2,则表示构建的是一个两列的垂直列表。lanes 的默认值为 1,即默认情况下,垂直列表的列数是 1。

交叉轴布局设置的示例代码如下:
@Entry
@Component
struct ListSample {
  build() {
    List() {
      ListItem() {
        Text().height('40%').width('100%').backgroundColor(Color.Blue)
      }
      ListItem() {
        Text().height('40%').width('100%').backgroundColor(Color.Brown)
      }// ......
    }.height('100%').width('100%')
    // 设置列表分两列显示
    .lanes(2)
  }
}
显示效果如下图所示:


图 8 设置交叉轴布局后的显示效果

在列表中显示数据

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

在列表中显示数据的示例代码如下:
@Entry
@Component
struct CityList {
  build() {
    // 列表组件
    List() {
      // 列表项组件
      ListItem() {
        // 文本显示组件
        Text('北京').fontSize(24)
      }

      // 列表项组件
      ListItem() {
        // 文本显示组件
        Text('杭州').fontSize(24)
      }

      // 列表项组件
      ListItem() {
        // 文本显示组件
        Text('上海').fontSize(24)
      }
    }
    // 设置宽度
    .width('100%')
    // 设置高度
    .height(85)
    // 设置背景色
    .backgroundColor('#FFF1F3F5')
    // 设置列表项排列方式为交叉轴方向居中对齐
    .alignListItem(ListItemAlign.Center)
  }
}
显示效果如下图所示:


图 9 列表中数据的显示效果

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


图 10 联系人列表项效果

如上图所示,在联系人列表的列表项中,每个联系人都有头像和名称。此时,需要将 Image 和 Text 封装到一个 Row 容器内。示例代码如下:
@Entry
@Component
struct Demo03 {
  build() {
    // 列表组件
    List() {
      // 列表项组件
      ListItem() {
        // 通过行容器组件,在一个列表项中显示多个组件
        Row() {
          Image($r('app.media.contact_male'))
            .width(40)
            .height(40)
            .margin(10)
          Text('小明')
            .fontSize(20)
        }
      }

      // 列表项组件
      ListItem() {
        // 通过行容器组件,在一个列表项中显示多个组件
        Row() {
          Image($r('app.media.contact_female'))
            .width(40)
            .height(40)
            .margin(10)
          Text('小红')
            .fontSize(20)
        }
      }
    }
    .height('100%')
    .width('100%')
  }
}

List迭代列表内容

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

ArkTS 通过 ForEach 提供了组件的循环渲染能力。以简单的联系人列表为例,将联系人名称和头像数据以 Contact 类结构存储到 contacts 数组中,使用在 ForEach 中嵌套 ListItem 的形式来代替多个平铺的、内容相似的 ListItem,从而减少重复代码。

示例代码如下:
import { util } from '@kit.ArkTS';

// 声明类,用于封装通讯录单条信息
class Contact {
  // key
  key: string = util.generateRandomUUID(true);
  // 名称
  name: string;
  // 头像
  icon: Resource;

  // 构造函数
  constructor(name: string, icon: Resource) {
    // 赋值名称
    this.name = name;
    // 赋值头像
    this.icon = icon;
  }
}

@Entry
@Component
struct Demo04 {
  // 存储联系人信息的数组,通过遍历该数组循环渲染 UI
  private contacts: Array<Contact> = [
    new Contact('小明', $r('app.media.contact_male')),
    new Contact('小红', $r('app.media.contact_female'))
  ];

  build() {
    // 列表组件
    List() {
      // ForEach 循环渲染
      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: Contact) => JSON.stringify(item))
    }
    .width('100%')
    .height('100%')
  }
}
在 List 组件中,ForEach 除了可以用来循环渲染 ListItem 之外,还可以用来循环渲染 ListItemGroup。

List自定义列表样式

1) 设置内容间距

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

2) 添加分隔线

List 提供了 divider 属性用于在列表项之间添加分隔线。分隔线用来将界面元素隔开,使单个元素更加容易识别。

在设置 divider 属性时,可以通过 strokeWidth 和 color 属性分别设置分隔线的粗细和颜色。通过 startMargin 和 endMargin 属性分别设置分隔线距离列表起始端的距离和距离列表结束端的距离。

示例代码如下:
// 导入工具类
import { util } from '@kit.ArkTS';

// 声明类,用于封装分割线信息
class DividerTmp {
  // 分割线宽度
  strokeWidth: Length = 1;
  // 分割线起始外边距
  startMargin: Length = 60;
  // 分割线结束外边距
  endMargin: Length = 10;
  // 分割线颜色
  color: ResourceColor = '#FFE9F0F0';

  // 构造函数
  constructor(
    strokeWidth: Length,
    startMargin: Length,
    endMargin: Length,
    color: ResourceColor
  ) {
    // 设置分割线线宽
    this.strokeWidth = strokeWidth;
    // 设置分割线起始外边距
    this.startMargin = startMargin;
    // 设置分割线结束外边距
    this.endMargin = endMargin;
    // 设置分割线的颜色
    this.color = color;
  }
}

// 声明类,用于封装通讯录单条信息
class Contact {
  // key
  key: string = util.generateRandomUUID(true);
  // 名称
  name: string;
  // 头像
  icon: Resource;

  // 构造函数
  constructor(name: string, icon: Resource) {
    // 赋值名称
    this.name = name;
    // 赋值头像
    this.icon = icon;
  }
}

@Entry
@Component
struct EgDivider {
  // 声明状态变量,初始化为分割线实例
  // 其中,设置分割线的线宽为 1vp,分割线起始外边距为 36vp,结束外边距为 5vp,同时设置颜色
  @State egDivider: DividerTmp = new DividerTmp(1, 36, 5, '#EEEEEE');

  build() {
    Column() {
      Text('设置')
        .fontSize(24)
        .width('90%')
        .margin({ top: 20, bottom: 12 });

      List({ space: 5 }) {
        // 列表项:WLAN
        ListItem() {
          Row() {
            Image($r('app.media.wifi'))
              .height(32)
              .width(32)
              .margin({ right: 5 });
            Text('WLAN')
              .fontWeight(FontWeight.Bold)
              .fontSize(15);
            Blank();
            Image($r('app.media.rightarrow'))
              .height(15)
              .width(15);
          }
          .width('100%')
          .offset({ left: 1 })
          .height(40)
          .alignItems(VerticalAlign.Center);
        }

        // 列表项:蓝牙
        ListItem() {
          Row() {
            Image($r('app.media.bluetooth'))
              .height(35)
              .width(35)
              .margin({ right: 5 });
            Text('蓝牙')
              .fontWeight(FontWeight.Bold)
              .fontSize(15);
            Blank();
            Image($r('app.media.rightarrow'))
              .height(15)
              .width(15);
          }
          .width('100%')
          .height(40)
          .alignItems(VerticalAlign.Center);
        }

        // 列表项:移动网络
        ListItem() {
          Row() {
            Image($r('app.media.network'))
              .height(40)
              .width(40)
              .margin({ right: 5 });
            Text('移动网络')
              .fontWeight(FontWeight.Bold)
              .fontSize(15);
            Blank();
            Image($r('app.media.rightarrow'))
              .height(15)
              .width(15);
          }
          .width('100%')
          .offset({ left: 2 })
          .height(40)
          .alignItems(VerticalAlign.Center);
        }
      }
      .width('90%')
      .padding(5)
      .backgroundColor(Color.White)
      .borderRadius(10)
      // 设置分割线的具体样式
      .divider(this.egDivider);
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5');
  }
}
显示效果如下图所示:


图 11 设置列表分割线样式

相关文章