概述
列表是一种复杂的容器,当列表项达到一定数量,内容超过屏幕大小时,可以自动提供滚动功能。它适合用于呈现同类数据类型或数据类型集,例如图片和文本。在列表中显示数据集合是许多应用程序中的常见要求(如通讯录、音乐列表、购物清单等)。
使用列表可以轻松高效地显示结构化、可滚动的信息。通过在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)
}
}
- 多列
@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)
}
}
水平滚动列表
- 单列
对应代码
@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)
}
}
- 多列
对应代码
@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)
}
}
自定义列表样式
设置内容间距
在初始化列表时,如需在列表项之间添加间距,可以使用 space 参数。例如,在每个列表项之间沿主轴方向添加 55vp 的间距:
对应局部代码
List({space: 55}) {
ForEach(this.listItems, ()=> {
ListItem() {
Stack()
.width('100%')
.height(100)
.backgroundColor('#9dc3e6')
}
})
}
添加分隔线
效果图
对应代码
List() {
ForEach(this.listItems, ()=> {
ListItem() {
Stack()
.width('100%')
.height(100)
.backgroundColor('#9dc3e6')
}
})
}
.divider({
strokeWidth: 1,
startMargin: 60,
endMargin: 10,
color: '#ff0000'
})
添加滚动条
效果图
对应代码
List() {
ForEach(this.listItems, ()=> {
ListItem() {
Stack()
.width('100%')
.height(100)
.backgroundColor('#9dc3e6')
}
})
}
.scrollBar(BarState.Auto)
.divider({
strokeWidth: 1,
color: '#ff0000'
})
支持分组列表
在列表中支持数据的分组展示,可以使列表显示结构清晰,查找方便,从而提高使用效率。分组列表在实际应用中十分常见,如下图所示联系人列表。
对应代码
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)
}
}
添加粘性标题
运行效果
对应代码
List() {
...
}
.sticky(StickyStyle.Header) // 设置吸顶,实现粘性标题效果
控制滚动位置
对应代码
@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)
}
}
响应滚动位置
对应代码
Stack() {
List() {
...
}
.onScrollIndex((start, end)=> {
this.firstIndex = start
})
Text(`当前第一个index:${this.firstIndex}`)
...
}
响应列表项侧滑
对应代码
@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)
}
}
给列表项添加标记
添加标记是一种无干扰性且直观的方法,用于显示通知或将注意力集中到应用内的某个区域。例如,当消息列表接收到新消息时,通常对应的联系人头像的右上方会出现标记,提示有若干条未读消息,如下图所示。
@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)
}
}
长列表的处理
循环渲染适用于短列表,当构建具有大量列表项的长列表时,如果直接采用循环渲染方式,会一次性加载所有的列表元素,会导致页面启动时间过长,影响用户体验。因此,推荐使用数据懒加载(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);
}
}
上一篇 2.6、媒体查询(mediaquery)
下一篇 2.8、下拉刷新与上拉加载