鸿蒙Harmony-列表组件(List)详解
不要和别人比生活,每个人阶段不同,追求不同,活法自然也不同。只要今天的你能比昨天的你快乐一点点,那你就是自己人生赢家。
目录
一,定义
二,布局与约束
2.1 布局
2.2 约束
三,开发布局
3.1 设置主轴方向
3.2设置交叉轴布局
四,迭代列表内容
五,自定义列表样式
5.1 设置内容间距
5.2 添加分隔线
5.3 添加滚动条
5.4 支持分组列表
5.5 添加粘性标题
5.6 控制滚动位置
5.7 响应列表项侧滑
5.8 给列表项添加标记
一,定义
类似于Android的recyclerview,鸿蒙的列表List是一种复杂的容器,当列表项达到一定数量,内容超过屏幕大小时,可以自动提供滚动功能。它适合用于呈现同类数据类型或数据类型集,例如图片和文本。在列表中显示数据集合是许多应用程序中的常见要求(如通讯录、音乐列表、购物清单等)。
使用列表可以轻松高效地显示结构化、可滚动的信息。通过在List组件中按垂直或者水平方向线性排列子组件ListItemGroup或ListItem,为列表中的行或列提供单个视图,或使用循环渲染迭代一组行或列,或混合任意数量的单个视图和ForEach结构,构建一个列表。List组件支持使用条件渲染、循环渲染、懒加载等渲染控制方式生成子组件。
二,布局与约束
列表作为一种容器,会自动按其滚动方向排列子组件,向列表中添加组件或从列表中移除组件会重新排列子组件。
在垂直列表中,List按垂直方向自动排列ListItemGroup或ListItem。
ListItemGroup用于列表数据的分组展示,其子组件也是ListItem。ListItem表示单个列表项,可以包含单个子组件。
注意:List的子组件必须是ListItemGroup或ListItem,ListItem和ListItemGroup必须配合List来使用。
2.1 布局
List除了提供垂直和水平布局能力、超出屏幕时可以滚动的自适应延伸能力之外,还提供了自适应交叉轴方向上排列个数的布局能力。
利用垂直布局能力可以构建单列或者多列垂直滚动列表
利用水平布局能力可以是构建单行或多行水平滚动列表
2.2 约束
列表的主轴方向是指子组件列的排列方向,也是列表的滚动方向。垂直于主轴的轴称为交叉轴,其方向与主轴方向相互垂直。
垂直列表的主轴是垂直方向,交叉轴是水平方向;水平列表的主轴是水平方向,交叉轴是垂直方向。
如果List组件主轴或交叉轴方向设置了尺寸,则其对应方向上的尺寸为设置值。
如果List组件主轴方向没有设置尺寸,当List子组件主轴方向总尺寸小于List的父组件尺寸时,List主轴方向尺寸自动适应子组件的总尺寸。
类似于Android的wrap_content
三,开发布局
3.1 设置主轴方向
List组件主轴默认是垂直方向,即默认情况下不需要手动设置List方向,就可以构建一个垂直滚动列表。
若是水平滚动列表场景,将List的listDirection属性设置为Axis.Horizontal即可实现。listDirection默认为Axis.Vertical,即主轴默认是垂直方向。
垂直列表:
@Entry @Component struct Index { build() { List(){ ListItem(){ Text("袁震1").fontSize(24) } ListItem(){ Text("袁震2").fontSize(24) } ListItem(){ Text("袁震3").fontSize(24) } }.listDirection(Axis.Vertical) } }
水平列表:
@Entry @Component struct Index { build() { List(){ ListItem(){ Text("袁震1").fontSize(24) } ListItem(){ Text("袁震2").fontSize(24) } ListItem(){ Text("袁震3").fontSize(24) } }.listDirection(Axis.Horizontal) } }
3.2设置交叉轴布局
List组件的交叉轴布局可以通过lanes和alignListItem属性进行设置,lanes属性用于确定交叉轴排列的列表项数量,alignListItem用于设置子组件在交叉轴方向的对齐方式。
List组件的lanes属性通常用于在不同尺寸的设备自适应构建不同行数或列数的列表,即一次开发、多端部署的场景,例如歌单列表。lanes属性的取值类型是"number | LengthConstrain",即整数或者LengthConstrain类型。以垂直列表为例,如果将lanes属性设为2,表示构建的是一个两列的垂直列表。lanes的默认值为1,即默认情况下,垂直列表的列数是1。
交叉轴方向列表项是2,对齐方式为居中对齐:
@Entry @Component struct Index { build() { List(){ ListItem(){ Text("袁震1").fontSize(24) } ListItem(){ Text("袁震2").fontSize(24) } ListItem(){ Text("袁震3").fontSize(24) } ListItem(){ Text("袁震4").fontSize(24) } } .listDirection(Axis.Vertical) .lanes(2) .alignListItem(ListItemAlign.Center) } }
当其取值为LengthConstrain类型时,表示会根据LengthConstrain与List组件的尺寸自适应决定行或列数。
例如,假设在垂直列表中设置了lanes的值为{ minLength: 200, maxLength: 300 }。此时,
当List组件宽度为300vp时,由于minLength为200vp,此时列表为一列:
@Entry @Component struct Index { @State egLanes: LengthConstrain = { minLength: 200, maxLength: 300 } build() { List(){ ListItem(){ Text("袁震1").fontSize(24) } ListItem(){ Text("袁震2").fontSize(24) } ListItem(){ Text("袁震3").fontSize(24) } ListItem(){ Text("袁震4").fontSize(24) } } .width(300) .lanes(this.egLanes) .listDirection(Axis.Vertical) .alignListItem(ListItemAlign.Center) } }
当List组件宽度变化至400vp时,符合两倍的minLength,则此时列表自适应为两列:
@Entry @Component struct Index { @State egLanes: LengthConstrain = { minLength: 200, maxLength: 200 } build() { List(){ ListItem(){ Text("袁震1").fontSize(24) } ListItem(){ Text("袁震2").fontSize(24) } ListItem(){ Text("袁震3").fontSize(24) } ListItem(){ Text("袁震4").fontSize(24) } } .width(400) .lanes(this.egLanes) .listDirection(Axis.Vertical) .alignListItem(ListItemAlign.Center) } }
四,迭代列表内容
通常,应用通过数据集合动态地创建列表。使用循环渲染可从数据源中迭代获取数据,并在每次迭代过程中创建相应的组件,降低代码复杂度。
ArkTS通过ForEach提供了组件的循环渲染能力。
新建数据类:
export default class YuanZhen { public name: string = 'YuanZhen'; public age: number = 18; constructor(name: string, age: number) { this.name = name this.age = age } }
组件:
import YuanZhen from './bean/YuanZhen' @Entry @Component struct Index { @State list:Array=new Array aboutToAppear(){ this.list.push(new YuanZhen("袁震1",18)) this.list.push(new YuanZhen("袁震2",19)) this.list.push(new YuanZhen("袁震3",20)) this.list.push(new YuanZhen("袁震4",21)) this.list.push(new YuanZhen("袁震5",22)) this.list.push(new YuanZhen("袁震6",23)) } build() { List(){ ForEach(this.list,(item:YuanZhen)=>{ ListItem(){ Row() { Image($r('app.media.startIcon')) .width(40) .height(40) .margin(10) Text(item.name).fontSize(20) Text(" 年龄:"+item.age).fontSize(20) } } }) } .width(400) .listDirection(Axis.Vertical) } }
五,自定义列表样式
5.1 设置内容间距
在初始化列表时,如需在列表项之间添加间距,可以使用space参数。例如,在每个列表项之间沿主轴方向添加10vp的间距
import YuanZhen from './bean/YuanZhen' @Entry @Component struct Index { @State list:Array=new Array aboutToAppear(){ this.list.push(new YuanZhen("袁震1",18)) this.list.push(new YuanZhen("袁震2",19)) this.list.push(new YuanZhen("袁震3",20)) this.list.push(new YuanZhen("袁震4",21)) this.list.push(new YuanZhen("袁震5",22)) this.list.push(new YuanZhen("袁震6",23)) } build() { List({space:10}){ ForEach(this.list,(item:YuanZhen)=>{ ListItem(){ Row() { Image($r('app.media.startIcon')) .width(40) .height(40) .margin(10) Text(item.name).fontSize(20) Text(" 年龄:"+item.age).fontSize(20) } } }) } .width(400) .listDirection(Axis.Vertical) } }
5.2 添加分隔线
分隔线用来将界面元素隔开,使单个元素更加容易识别。
List提供了divider属性用于给列表项之间添加分隔线。在设置divider属性时,可以通过strokeWidth和color属性设置分隔线的粗细和颜色。
startMargin和endMargin属性分别用于设置分隔线距离列表侧边起始端的距离和距离列表侧边结束端的距离。
import YuanZhen from './bean/YuanZhen' @Entry @Component struct Index { @State list:Array=new Array aboutToAppear(){ this.list.push(new YuanZhen("袁震1",18)) this.list.push(new YuanZhen("袁震2",19)) this.list.push(new YuanZhen("袁震3",20)) this.list.push(new YuanZhen("袁震4",21)) this.list.push(new YuanZhen("袁震5",22)) this.list.push(new YuanZhen("袁震6",23)) } build() { List({space:10}){ ForEach(this.list,(item:YuanZhen)=>{ ListItem(){ Row() { Image($r('app.media.startIcon')) .width(40) .height(40) .margin(10) Text(item.name).fontSize(20) Text(" 年龄:"+item.age).fontSize(20) } } }) } .divider({ strokeWidth:1, startMargin:60, endMargin:10, color: '#ffe9f0f0' }) .width(400) .listDirection(Axis.Vertical) } }
5.3 添加滚动条
当列表项高度(宽度)超出屏幕高度(宽度)时,列表可以沿垂直(水平)方向滚动。在页面内容很多时,若用户需快速定位,可拖拽滚动条,
在使用List组件时,可通过scrollBar属性控制列表滚动条的显示。scrollBar的取值类型为BarState,当取值为BarState.Auto表示按需显示滚动条。此时,当触摸到滚动条区域时显示控件,可上下拖拽滚动条快速浏览内容,拖拽时会变粗。若不进行任何操作,2秒后滚动条自动消失。
从API version 10版本开始默认值为BarState.Auto
import YuanZhen from './bean/YuanZhen' @Entry @Component struct Index { @State list:Array=new Array aboutToAppear(){ this.list.push(new YuanZhen("袁震1",18)) this.list.push(new YuanZhen("袁震2",19)) this.list.push(new YuanZhen("袁震3",20)) this.list.push(new YuanZhen("袁震4",21)) this.list.push(new YuanZhen("袁震5",22)) this.list.push(new YuanZhen("袁震6",23)) this.list.push(new YuanZhen("袁震6",23)) this.list.push(new YuanZhen("袁震6",23)) this.list.push(new YuanZhen("袁震6",23)) this.list.push(new YuanZhen("袁震6",23)) this.list.push(new YuanZhen("袁震6",23)) this.list.push(new YuanZhen("袁震6",23)) this.list.push(new YuanZhen("袁震6",23)) this.list.push(new YuanZhen("袁震6",23)) this.list.push(new YuanZhen("袁震6",23)) this.list.push(new YuanZhen("袁震6",23)) this.list.push(new YuanZhen("袁震8",28)) } build() { List({space:10}){ ForEach(this.list,(item:YuanZhen)=>{ ListItem(){ Row() { Image($r('app.media.startIcon')) .width(40) .height(40) .margin(10) Text(item.name).fontSize(20) Text(" 年龄:"+item.age).fontSize(20) } } }) }.scrollBar(BarState.On) .divider({ strokeWidth:1, startMargin:60, endMargin:10, color: '#ffe9f0f0' }) .width(400) .listDirection(Axis.Vertical) } }
5.4 支持分组列表
在列表中支持数据的分组展示,可以使列表显示结构清晰,查找方便,从而提高使用效率。
在List组件中使用ListItemGroup对项目进行分组,可以构建二维列表。
在List组件中可以直接使用一个或者多个ListItemGroup组件,ListItemGroup的宽度默认充满List组件。在初始化ListItemGroup时,可通过header参数设置列表分组的头部组件。
如果多个ListItemGroup结构类似,可以将多个分组的数据组成数组,然后使用ForEach对多个分组进行循环渲染。
import YuanZhen from './bean/YuanZhen' @Entry @Component struct Index { @State list:Array=new Array @Builder itemHead(text: string) { Text(text) .fontSize(20) .backgroundColor('#345') .width('100%') .padding(5) } aboutToAppear(){ this.list.push(new YuanZhen("袁震1",18)) this.list.push(new YuanZhen("袁震2",19)) this.list.push(new YuanZhen("袁震3",20)) this.list.push(new YuanZhen("袁震4",21)) this.list.push(new YuanZhen("袁震5",22)) } build() { List({space:10}){ ListItemGroup({ header: this.itemHead('A') }) { // 循环渲染分组A的ListItem ForEach(this.list,(item:YuanZhen)=>{ ListItem(){ Row() { Image($r('app.media.startIcon')) .width(40) .height(40) .margin(10) Text(item.name).fontSize(20) Text(" 年龄:"+item.age).fontSize(20) } } }) } ListItemGroup({ header: this.itemHead('B') }) { // 循环渲染分组A的ListItem ForEach(this.list,(item:YuanZhen)=>{ ListItem(){ Row() { Image($r('app.media.startIcon')) .width(40) .height(40) .margin(10) Text(item.name).fontSize(20) Text(" 年龄:"+item.age).fontSize(20) } } }) } }.scrollBar(BarState.On) .divider({ strokeWidth:1, startMargin:60, endMargin:10, color: '#ffe9f0f0' }) .width(400) .listDirection(Axis.Vertical) } }
5.5 添加粘性标题
粘性标题是一种常见的标题模式,常用于定位字母列表的头部元素。
粘性标题不仅有助于阐明列表中数据的表示形式和用途,还可以帮助用户在大量信息中进行数据定位,从而避免用户在标题所在的表的顶部与感兴趣区域之间反复滚动。
List组件的sticky属性配合ListItemGroup组件使用,用于设置ListItemGroup中的头部组件是否呈现吸顶效果或者尾部组件是否呈现吸底效果。
通过给List组件设置sticky属性为StickyStyle.Header,即可实现列表的粘性标题效果。如果需要支持吸底效果,可以通过footer参数初始化ListItemGroup的底部组件,并将sticky属性设置为StickyStyle.Footer。
5.6 控制滚动位置
控制滚动位置在实际应用中十分常见,例如当新闻页列表项数量庞大,用户滚动列表到一定位置时,希望快速滚动到列表底部或返回列表顶部。此时,可以通过控制滚动位置来实现列表的快速定位
List组件初始化时,可以通过scroller参数绑定一个Scroller对象,进行列表的滚动控制。例如,用户在新闻应用中,点击新闻页面底部的返回顶部按钮时,就可以通过Scroller对象的scrollToIndex方法使列表滚动到指定的列表项索引位置。
import YuanZhen from './bean/YuanZhen' @Entry @Component struct Index { @State list:Array=new Array listScroller: Scroller = new Scroller(); @Builder itemHead(text: string) { Text(text) .fontSize(20) .backgroundColor('#345') .width('100%') .padding(5) } aboutToAppear(){ this.list.push(new YuanZhen("袁震1",18)) this.list.push(new YuanZhen("袁震2",19)) this.list.push(new YuanZhen("袁震3",20)) this.list.push(new YuanZhen("袁震4",21)) this.list.push(new YuanZhen("袁震5",22)) } build() { Column(){ Text("回到顶部").width(50).height(50).onClick(()=>{ this.listScroller.scrollToIndex(0) }) List({space:10, scroller: this.listScroller}){ ListItemGroup({ header: this.itemHead('A') }) { // 循环渲染分组A的ListItem ForEach(this.list,(item:YuanZhen)=>{ ListItem(){ Row() { Image($r('app.media.startIcon')) .width(40) .height(40) .margin(10) Text(item.name).fontSize(20) Text(" 年龄:"+item.age).fontSize(20) } } }) } ListItemGroup({ header: this.itemHead('B') }) { // 循环渲染分组A的ListItem ForEach(this.list,(item:YuanZhen)=>{ ListItem(){ Row() { Image($r('app.media.startIcon')) .width(40) .height(40) .margin(10) Text(item.name).fontSize(20) Text(" 年龄:"+item.age).fontSize(20) } } }) } ListItemGroup({ header: this.itemHead('C') }) { // 循环渲染分组A的ListItem ForEach(this.list,(item:YuanZhen)=>{ ListItem(){ Row() { Image($r('app.media.startIcon')) .width(40) .height(40) .margin(10) Text(item.name).fontSize(20) Text(" 年龄:"+item.age).fontSize(20) } } }) } }.scrollBar(BarState.On) .divider({ strokeWidth:1, startMargin:60, endMargin:10, color: '#ffe9f0f0' }) .sticky(StickyStyle.Header) // 设置吸顶,实现粘性标题效果 .width("100%") .listDirection(Axis.Vertical) } .width("100%") .height("100%") } }
5.7 响应列表项侧滑
ListItem的swipeAction属性可用于实现列表项的左右滑动功能。swipeAction属性方法初始化时有必填参数SwipeActionOptions,其中,start参数表示设置列表项右滑时起始端滑出的组件,end参数表示设置列表项左滑时尾端滑出的组件。
在消息列表中,end参数表示设置ListItem左滑时尾端划出自定义组件,即删除按钮。在初始化end方法时,将滑动列表项的索引传入删除按钮组件,当用户点击删除按钮时,可以根据索引值来删除列表项对应的数据,从而实现侧滑删除功能。
import YuanZhen from './bean/YuanZhen' @Entry @Component struct Index { @State list:Array=new Array listScroller: Scroller = new Scroller(); @Builder itemHead(text: string) { Text(text) .fontSize(20) .backgroundColor('#345') .width('100%') .padding(5) } @Builder itemEnd(index: number) { // 构建尾端滑出组件 Button({ type: ButtonType.Circle }) { Image($r('app.media.startIcon')) .width(20) .height(20) } .onClick(() => { // this.messages为列表数据源,可根据实际场景构造。点击后从数据源删除指定数据项。 this.list.splice(index, 1); }) } aboutToAppear(){ this.list.push(new YuanZhen("袁震1",18)) this.list.push(new YuanZhen("袁震2",19)) this.list.push(new YuanZhen("袁震3",20)) this.list.push(new YuanZhen("袁震4",21)) this.list.push(new YuanZhen("袁震5",22)) } build() { Column(){ List({space:10, scroller: this.listScroller}){ ListItemGroup({ header: this.itemHead('A') }) { // 循环渲染分组A的ListItem ForEach(this.list,(item:YuanZhen,index)=>{ ListItem(){ Row() { Image($r('app.media.startIcon')) .width(40) .height(40) .margin(10) Text(item.name).fontSize(20) Text(" 年龄:"+item.age).fontSize(20) } }.swipeAction({ end: this.itemEnd(index) }) // 设置侧滑属性 }) } ListItemGroup({ header: this.itemHead('B') }) { // 循环渲染分组A的ListItem ForEach(this.list,(item:YuanZhen)=>{ ListItem(){ Row() { Image($r('app.media.startIcon')) .width(40) .height(40) .margin(10) Text(item.name).fontSize(20) Text(" 年龄:"+item.age).fontSize(20) } } }) } ListItemGroup({ header: this.itemHead('C') }) { // 循环渲染分组A的ListItem ForEach(this.list,(item:YuanZhen)=>{ ListItem(){ Row() { Image($r('app.media.startIcon')) .width(40) .height(40) .margin(10) Text(item.name).fontSize(20) Text(" 年龄:"+item.age).fontSize(20) } } }) } }.scrollBar(BarState.On) .divider({ strokeWidth:1, startMargin:60, endMargin:10, color: '#ffe9f0f0' }) .sticky(StickyStyle.Header) // 设置吸顶,实现粘性标题效果 .width("100%") .listDirection(Axis.Vertical) } .width("100%") .height("100%") } }
5.8 给列表项添加标记
添加标记是一种无干扰性且直观的方法,用于显示通知或将注意力集中到应用内的某个区域。例如,当消息列表接收到新消息时,通常对应的联系人头像的右上方会出现标记,提示有若干条未读消息
在ListItem中使用Badge组件可实现给列表项添加标记功能。Badge是可以附加在单个组件上用于信息标记的容器组件。
在消息列表中,若希望在联系人头像右上角添加标记,可在实现消息列表项ListItem的联系人头像时,将头像Image组件作为Badge的子组件。
在Badge组件中,count和position参数用于设置需要展示的消息数量和提示点显示位置,还可以通过style参数灵活设置标记的样式。