創(chuàng)建列表(List)

2024-01-25 13:12 更新

概述

列表是一種復(fù)雜的容器,當(dāng)列表項(xiàng)達(dá)到一定數(shù)量,內(nèi)容超過屏幕大小時(shí),可以自動(dòng)提供滾動(dòng)功能。它適合用于呈現(xiàn)同類數(shù)據(jù)類型或數(shù)據(jù)類型集,例如圖片和文本。在列表中顯示數(shù)據(jù)集合是許多應(yīng)用程序中的常見要求(如通訊錄、音樂列表、購物清單等)。

使用列表可以輕松高效地顯示結(jié)構(gòu)化、可滾動(dòng)的信息。通過在List組件中按垂直或者水平方向線性排列子組件ListItemGroupListItem,為列表中的行或列提供單個(gè)視圖,或使用ForEach迭代一組行或列,或混合任意數(shù)量的單個(gè)視圖和ForEach結(jié)構(gòu),構(gòu)建一個(gè)列表。List組件支持使用條件渲染、循環(huán)渲染、懶加載等渲染控制方式生成子組件。

布局與約束

列表作為一種容器,會(huì)自動(dòng)按其滾動(dòng)方向排列子組件,向列表中添加組件或從列表中移除組件會(huì)重新排列子組件。

如下圖所示,在垂直列表中,List按垂直方向自動(dòng)排列ListItemGroup或ListItem。

ListItemGroup用于列表數(shù)據(jù)的分組展示,其子組件也是ListItem。ListItem表示單個(gè)列表項(xiàng),可以包含單個(gè)子組件。

圖1 List、ListItemGroup和ListItem組件關(guān)系
說明

List的子組件必須是ListItemGroup或ListItem,ListItem和ListItemGroup必須配合List來使用。

布局

List除了提供垂直和水平布局能力、超出屏幕時(shí)可以滾動(dòng)的自適應(yīng)延伸能力之外,還提供了自適應(yīng)交叉軸方向上排列個(gè)數(shù)的布局能力。

利用垂直布局能力可以構(gòu)建單列或者多列垂直滾動(dòng)列表,如下圖所示。

圖2 垂直滾動(dòng)列表(左:單列;右:多列)

利用水平布局能力可以是構(gòu)建單行或多行水平滾動(dòng)列表,如下圖所示。

圖3 水平滾動(dòng)列表(左:單行;右:多行)

約束

列表的主軸方向是指子組件列的排列方向,也是列表的滾動(dòng)方向。垂直于主軸的軸稱為交叉軸,其方向與主軸方向相互垂直。

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

圖4 列表的主軸與交叉軸

如果List組件主軸或交叉軸方向設(shè)置了尺寸,則其對應(yīng)方向上的尺寸為設(shè)置值。

如果List組件主軸方向沒有設(shè)置尺寸,當(dāng)List子組件主軸方向總尺寸小于List的父組件尺寸時(shí),List主軸方向尺寸自動(dòng)適應(yīng)子組件的總尺寸。

如下圖所示,一個(gè)垂直列表B沒有設(shè)置高度時(shí),其父組件A高度為200vp,若其所有子組件C的高度總和為150vp,則此時(shí)列表B的高度為150vp。

圖5 列表主軸高度約束示例1(A: List的父組件; B: List組件; C: List的所有子組件)

如果子組件主軸方向總尺寸超過List父組件尺寸時(shí),List主軸方向尺寸適應(yīng)List的父組件尺寸。

如下圖所示,同樣是沒有設(shè)置高度的垂直列表B,其父組件A高度為200vp,若其所有子組件C的高度總和為300vp,則此時(shí)列表B的高度為200vp。

圖6 列表主軸高度約束示例2(A: List的父組件; B: List組件; C: List的所有子組件)

List組件交叉軸方向在沒有設(shè)置尺寸時(shí),其尺寸默認(rèn)自適應(yīng)父組件尺寸。

開發(fā)布局

設(shè)置主軸方向

List組件主軸默認(rèn)是垂直方向,即默認(rèn)情況下不需要手動(dòng)設(shè)置List方向,就可以構(gòu)建一個(gè)垂直滾動(dòng)列表。

若是水平滾動(dòng)列表場景,將List的listDirection屬性設(shè)置為Axis.Horizontal即可實(shí)現(xiàn)。listDirection默認(rèn)為Axis.Vertical,即主軸默認(rèn)是垂直方向。

  1. List() {
  2. ...
  3. }
  4. .listDirection(Axis.Horizontal)

設(shè)置交叉軸布局

List組件的交叉軸布局可以通過lanes和alignListItem屬性進(jìn)行設(shè)置,lanes屬性用于確定交叉軸排列的列表項(xiàng)數(shù)量,alignListItem用于設(shè)置子組件在交叉軸方向的對齊方式。

List組件的lanes屬性通常用于在不同尺寸的設(shè)備自適應(yīng)構(gòu)建不同行數(shù)或列數(shù)的列表。lanes屬性的取值類型是"number | LengthConstrain",即整數(shù)或者LengthConstrain類型。以垂直列表為例,如果將lanes屬性設(shè)為2,表示構(gòu)建的是一個(gè)兩列的垂直列表,如圖2中右圖所示。lanes的默認(rèn)值為1,即默認(rèn)情況下,垂直列表的列數(shù)是1。

  1. List() {
  2. ...
  3. }
  4. .lanes(2)

當(dāng)其取值為LengthConstrain類型時(shí),表示會(huì)根據(jù)LengthConstrain與List組件的尺寸自適應(yīng)決定行或列數(shù)。

  1. List() {
  2. ...
  3. }
  4. .lanes({ minLength: 200, maxLength: 300 })

例如,假設(shè)在垂直列表中設(shè)置了lanes的值為{ minLength: 200, maxLength: 300 }。此時(shí),

  • 當(dāng)List組件寬度為300vp時(shí),由于minLength為200vp,此時(shí)列表為一列。
  • 當(dāng)List組件寬度變化至400vp時(shí),符合兩倍的minLength,則此時(shí)列表自適應(yīng)為兩列。

同樣以垂直列表為例,當(dāng)alignListItem屬性設(shè)置為ListItemAlign.Center表示列表項(xiàng)在水平方向上居中對齊。alignListItem的默認(rèn)值是ListItemAlign.Start,即列表項(xiàng)在列表交叉軸方向上默認(rèn)按首部對齊。

  1. List() {
  2. ...
  3. }
  4. .alignListItem(ListItemAlign.Center)

在列表中顯示數(shù)據(jù)

列表視圖垂直或水平顯示項(xiàng)目集合,在行或列超出屏幕時(shí)提供滾動(dòng)功能,使其適合顯示大型數(shù)據(jù)集合。在最簡單的列表形式中,List靜態(tài)地創(chuàng)建其列表項(xiàng)ListItem的內(nèi)容。

圖7 城市列表
  1. @Component
  2. struct CityList {
  3. build() {
  4. List() {
  5. ListItem() {
  6. Text('北京').fontSize(24)
  7. }
  8. ListItem() {
  9. Text('杭州').fontSize(24)
  10. }
  11. ListItem() {
  12. Text('上海').fontSize(24)
  13. }
  14. }
  15. .backgroundColor('#FFF1F3F5')
  16. .alignListItem(ListItemAlign.Center)
  17. }
  18. }

由于在ListItem中只能有一個(gè)根節(jié)點(diǎn)組件,不支持以平鋪形式使用多個(gè)組件。因此,若列表項(xiàng)是由多個(gè)組件元素組成的,則需要將這多個(gè)元素組合到一個(gè)容器組件內(nèi)或組成一個(gè)自定義組件。

圖8 聯(lián)系人列表項(xiàng)示例

如上圖所示,聯(lián)系人列表的列表項(xiàng)中,每個(gè)聯(lián)系人都有頭像和名稱。此時(shí),需要將Image和Text封裝到一個(gè)Row容器內(nèi)。

  1. List() {
  2. ListItem() {
  3. Row() {
  4. Image($r('app.media.iconE'))
  5. .width(40)
  6. .height(40)
  7. .margin(10)
  8. Text('小明')
  9. .fontSize(20)
  10. }
  11. }
  12. ListItem() {
  13. Row() {
  14. Image($r('app.media.iconF'))
  15. .width(40)
  16. .height(40)
  17. .margin(10)
  18. Text('小紅')
  19. .fontSize(20)
  20. }
  21. }
  22. }

迭代列表內(nèi)容

通常更常見的是,應(yīng)用通過數(shù)據(jù)集合動(dòng)態(tài)地創(chuàng)建列表。使用循環(huán)渲染可從數(shù)據(jù)源中迭代獲取數(shù)據(jù),并在每次迭代過程中創(chuàng)建相應(yīng)的組件,降低代碼復(fù)雜度。

ArkTS通過ForEach提供了組件的循環(huán)渲染能力。以簡單形式的聯(lián)系人列表為例,將聯(lián)系人名稱和頭像數(shù)據(jù)以Contact類結(jié)構(gòu)存儲(chǔ)到contacts數(shù)組,使用ForEach中嵌套ListItem的形式來代替多個(gè)平鋪的、內(nèi)容相似的ListItem,從而減少重復(fù)代碼。

  1. import util from '@ohos.util';
  2. class Contact {
  3. key: string = util.generateRandomUUID(true);
  4. name: string;
  5. icon: Resource;
  6. constructor(name: string, icon: Resource) {
  7. this.name = name;
  8. this.icon = icon;
  9. }
  10. }
  11. @Entry
  12. @Component
  13. struct SimpleContacts {
  14. private contacts = [
  15. new Contact('小明', $r("app.media.iconA")),
  16. new Contact('小紅', $r("app.media.iconB")),
  17. ...
  18. ]
  19. build() {
  20. List() {
  21. ForEach(this.contacts, (item: Contact) => {
  22. ListItem() {
  23. Row() {
  24. Image(item.icon)
  25. .width(40)
  26. .height(40)
  27. .margin(10)
  28. Text(item.name).fontSize(20)
  29. }
  30. .width('100%')
  31. .justifyContent(FlexAlign.Start)
  32. }
  33. }, item => item.key)
  34. }
  35. .width('100%')
  36. }
  37. }

在List組件中,F(xiàn)orEach除了可以用來循環(huán)渲染ListItem,也可以用來循環(huán)渲染ListItemGroup。ListItemGroup的循環(huán)渲染詳細(xì)使用請參見支持分組列表。

自定義列表樣式

設(shè)置內(nèi)容間距

在初始化列表時(shí),如需在列表項(xiàng)之間添加間距,可以使用space參數(shù)。例如,在每個(gè)列表項(xiàng)之間沿主軸方向添加10vp的間距:

  1. List({ space: 10 }) {
  2. ...
  3. }

添加分隔線

分隔線用來將界面元素隔開,使單個(gè)元素更加容易識(shí)別。如下圖所示,當(dāng)列表項(xiàng)左邊有圖標(biāo)(如藍(lán)牙圖標(biāo)),由于圖標(biāo)本身就能很好的區(qū)分,此時(shí)分隔線從圖標(biāo)之后開始顯示即可。

圖9 設(shè)置列表分隔線樣式

List提供了divider屬性用于給列表項(xiàng)之間添加分隔線。在設(shè)置divider屬性時(shí),可以通過strokeWidth和color屬性設(shè)置分隔線的粗細(xì)和顏色。

startMargin和endMargin屬性分別用于設(shè)置分隔線距離列表側(cè)邊起始端的距離和距離列表側(cè)邊結(jié)束端的距離。

  1. List() {
  2. ...
  3. }
  4. .divider({
  5. strokeWidth: 1,
  6. startMargin: 60,
  7. endMargin: 10,
  8. color: '#ffe9f0f0'
  9. })

此示例表示從距離列表側(cè)邊起始端60vp開始到距離結(jié)束端10vp的位置,畫一條粗細(xì)為1vp的分割線,可以實(shí)現(xiàn)圖8設(shè)置列表分隔線的樣式。

說明

1. 分隔線的寬度會(huì)使ListItem之間存在一定間隔,當(dāng)List設(shè)置的內(nèi)容間距小于分隔線寬度時(shí),ListItem之間的間隔會(huì)使用分隔線的寬度。

2. 當(dāng)List存在多列時(shí),分割線的startMargin和endMargin作用于每一列上。

3. List組件的分隔線畫在兩個(gè)ListItem之間,第一個(gè)ListItem上方和最后一個(gè)ListItem下方不會(huì)繪制分隔線。

添加滾動(dòng)條

當(dāng)列表項(xiàng)高度(寬度)超出屏幕高度(寬度)時(shí),列表可以沿垂直(水平)方向滾動(dòng)。在頁面內(nèi)容很多時(shí),若用戶需快速定位,可拖拽滾動(dòng)條,如下圖所示。

圖10 列表的滾動(dòng)條

在使用List組件時(shí),可通過scrollBar屬性控制列表滾動(dòng)條的顯示。scrollBar的取值類型為BarState,當(dāng)取值為BarState.Auto表示按需顯示滾動(dòng)條。此時(shí),當(dāng)觸摸到滾動(dòng)條區(qū)域時(shí)顯示控件,可上下拖拽滾動(dòng)條快速瀏覽內(nèi)容,拖拽時(shí)會(huì)變粗。若不進(jìn)行任何操作,2秒后滾動(dòng)條自動(dòng)消失。

  1. List() {
  2. ...
  3. }
  4. .scrollBar(BarState.Auto)

支持分組列表

在列表中支持?jǐn)?shù)據(jù)的分組展示,可以使列表顯示結(jié)構(gòu)清晰,查找方便,從而提高使用效率。分組列表在實(shí)際應(yīng)用中十分常見,如下圖所示聯(lián)系人列表。

圖11 聯(lián)系人分組列表

在List組件中使用ListItemGroup對項(xiàng)目進(jìn)行分組,可以構(gòu)建二維列表。

在List組件中可以直接使用一個(gè)或者多個(gè)ListItemGroup組件,ListItemGroup的寬度默認(rèn)充滿List組件。在初始化ListItemGroup時(shí),可通過header參數(shù)設(shè)置列表分組的頭部組件。

  1. @Component
  2. struct ContactsList {
  3. ...
  4. @Builder itemHead(text: string) {
  5. // 列表分組的頭部組件,對應(yīng)聯(lián)系人分組A、B等位置的組件
  6. Text(text)
  7. .fontSize(20)
  8. .backgroundColor('#fff1f3f5')
  9. .width('100%')
  10. .padding(5)
  11. }
  12. build() {
  13. List() {
  14. ListItemGroup({ header: this.itemHead('A') }) {
  15. // 循環(huán)渲染分組A的ListItem
  16. ...
  17. }
  18. ...
  19. ListItemGroup({ header: this.itemHead('B') }) {
  20. // 循環(huán)渲染分組B的ListItem
  21. ...
  22. }
  23. ...
  24. }
  25. }
  26. }

如果多個(gè)ListItemGroup結(jié)構(gòu)類似,可以將多個(gè)分組的數(shù)據(jù)組成數(shù)組,然后使用ForEach對多個(gè)分組進(jìn)行循環(huán)渲染。例如在聯(lián)系人列表中,將每個(gè)分組的聯(lián)系人數(shù)據(jù)contacts(可參考迭代列表內(nèi)容章節(jié))和對應(yīng)分組的標(biāo)題title數(shù)據(jù)進(jìn)行組合,定義為數(shù)組contactsGroups。

  1. contactsGroups: object[] = [
  2. {
  3. title: 'A',
  4. contacts: [
  5. new Contact('艾佳', $r('app.media.iconA')),
  6. new Contact('安安', $r('app.media.iconB')),
  7. new Contact('Angela', $r('app.media.iconC')),
  8. ],
  9. },
  10. {
  11. title: 'B',
  12. contacts: [
  13. new Contact('白葉', $r('app.media.iconD')),
  14. new Contact('伯明', $r('app.media.iconE')),
  15. ],
  16. },
  17. ...
  18. ]

然后在ForEach中對contactsGroups進(jìn)行循環(huán)渲染,即可實(shí)現(xiàn)多個(gè)分組的聯(lián)系人列表。

  1. List() {
  2. // 循環(huán)渲染ListItemGroup,contactsGroups為多個(gè)分組聯(lián)系人contacts和標(biāo)題title的數(shù)據(jù)集合
  3. ForEach(this.contactsGroups, item => {
  4. ListItemGroup({ header: this.itemHead(item.title) }) {
  5. // 循環(huán)渲染ListItem
  6. ForEach(item.contacts, contact => {
  7. ListItem() {
  8. ...
  9. }
  10. }, item => item.key)
  11. }
  12. ...
  13. })
  14. }

添加粘性標(biāo)題

粘性標(biāo)題是一種常見的標(biāo)題模式,常用于定位字母列表的頭部元素。如下圖所示,在聯(lián)系人列表中滾動(dòng)A部分時(shí),B部分開始的頭部元素始終處于A的下方。而在開始滾動(dòng)B部分時(shí),B的頭部會(huì)固定在屏幕頂部,直到所有B的項(xiàng)均完成滾動(dòng)后,才被后面的頭部替代。

粘性標(biāo)題不僅有助于闡明列表中數(shù)據(jù)的表示形式和用途,還可以幫助用戶在大量信息中進(jìn)行數(shù)據(jù)定位,從而避免用戶在標(biāo)題所在的表的頂部與感興趣區(qū)域之間反復(fù)滾動(dòng)。

圖12 粘性標(biāo)題

List組件的sticky屬性配合ListItemGroup組件使用,用于設(shè)置ListItemGroup中的頭部組件是否呈現(xiàn)吸頂效果或者尾部組件是否呈現(xiàn)吸底效果。

通過給List組件設(shè)置sticky屬性為StickyStyle.Header,即可實(shí)現(xiàn)列表的粘性標(biāo)題效果。如果需要支持吸底效果,可以通過footer參數(shù)初始化ListItemGroup的底部組件,并將sticky屬性設(shè)置為StickyStyle.Footer。

  1. @Component
  2. struct ContactsList {
  3. // 定義分組聯(lián)系人數(shù)據(jù)集合contactsGroups數(shù)組
  4. ...
  5. @Builder itemHead(text: string) {
  6. // 列表分組的頭部組件,對應(yīng)聯(lián)系人分組A、B等位置的組件
  7. Text(text)
  8. .fontSize(20)
  9. .backgroundColor('#fff1f3f5')
  10. .width('100%')
  11. .padding(5)
  12. }
  13. build() {
  14. List() {
  15. // 循環(huán)渲染ListItemGroup,contactsGroups為多個(gè)分組聯(lián)系人contacts和標(biāo)題title的數(shù)據(jù)集合
  16. ForEach(this.contactsGroups, item => {
  17. ListItemGroup({ header: this.itemHead(item.title) }) {
  18. // 循環(huán)渲染ListItem
  19. ForEach(item.contacts, contact => {
  20. ListItem() {
  21. ...
  22. }
  23. }, item => item.key)
  24. }
  25. ...
  26. })
  27. }
  28. .sticky(StickyStyle.Header) // 設(shè)置吸頂,實(shí)現(xiàn)粘性標(biāo)題效果
  29. }
  30. }

控制滾動(dòng)位置

控制滾動(dòng)位置在實(shí)際應(yīng)用中十分常見,例如當(dāng)新聞頁列表項(xiàng)數(shù)量龐大,用戶滾動(dòng)列表到一定位置時(shí),希望快速滾動(dòng)到列表底部或返回列表頂部。此時(shí),可以通過控制滾動(dòng)位置來實(shí)現(xiàn)列表的快速定位,如下圖所示。

圖13 返回列表頂部

List組件初始化時(shí),可以通過scroller參數(shù)綁定一個(gè)Scroller對象,進(jìn)行列表的滾動(dòng)控制。例如,用戶在新聞應(yīng)用中,點(diǎn)擊新聞頁面底部的返回頂部按鈕時(shí),就可以通過Scroller對象的scrollToIndex方法使列表滾動(dòng)到指定的列表項(xiàng)索引位置。

首先,需要?jiǎng)?chuàng)建一個(gè)Scroller的對象listScroller。

  1. private listScroller: Scroller = new Scroller();

然后,通過將listScroller用于初始化List組件的scroller參數(shù),完成listScroller與列表的綁定。在需要跳轉(zhuǎn)的位置指定scrollToIndex的參數(shù)為0,表示返回列表頂部。

  1. Stack({ alignContent: Alignment.BottomEnd }) {
  2. // 將listScroller用于初始化List組件的scroller參數(shù),完成listScroller與列表的綁定。
  3. List({ space: 20, scroller: this.listScroller }) {
  4. ...
  5. }
  6. ...
  7. Button() {
  8. ...
  9. }
  10. .onClick(() => {
  11. // 點(diǎn)擊按鈕時(shí),指定跳轉(zhuǎn)位置,返回列表頂部
  12. this.listScroller.scrollToIndex(0)
  13. })
  14. ...
  15. }

響應(yīng)滾動(dòng)位置

許多應(yīng)用需要監(jiān)聽列表的滾動(dòng)位置變化并作出響應(yīng)。例如,在聯(lián)系人列表滾動(dòng)時(shí),如果跨越了不同字母開頭的分組,則側(cè)邊字母索引欄也需要更新到對應(yīng)的字母位置。

除了字母索引之外,滾動(dòng)列表結(jié)合多級分類索引在應(yīng)用開發(fā)過程中也很常見,例如購物應(yīng)用的商品分類頁面,多級分類也需要監(jiān)聽列表的滾動(dòng)位置。

圖14 字母索引響應(yīng)聯(lián)系人列表滾動(dòng)

如上圖所示,當(dāng)聯(lián)系人列表從A滾動(dòng)到B時(shí),右側(cè)索引欄也需要同步從選中A狀態(tài)變成選中B狀態(tài)。此場景可以通過監(jiān)聽List組件的onScrollIndex事件來實(shí)現(xiàn),右側(cè)索引欄需要使用字母表索引組件AlphabetIndexer。

在列表滾動(dòng)時(shí),根據(jù)列表此時(shí)所在的索引值位置firstIndex,重新計(jì)算字母索引欄對應(yīng)字母的位置selectedIndex。由于AlphabetIndexer組件通過selected屬性設(shè)置了選中項(xiàng)索引值,當(dāng)selectedIndex變化時(shí)會(huì)觸發(fā)AlphabetIndexer組件重新渲染,從而顯示為選中對應(yīng)字母的狀態(tài)。

  1. ...
  2. const alphabets = ['#', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
  3. 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
  4. @Entry
  5. @Component
  6. struct ContactsList {
  7. @State selectedIndex: number = 0;
  8. private listScroller: Scroller = new Scroller();
  9. ...
  10. build() {
  11. Stack({ alignContent: Alignment.End }) {
  12. List({ scroller: this.listScroller }) {
  13. ...
  14. }
  15. .onScrollIndex((firstIndex: number) => {
  16. this.selectedIndex = firstIndex
  17. // 根據(jù)列表滾動(dòng)到的索引值,重新計(jì)算對應(yīng)聯(lián)系人索引欄的位置this.selectedIndex
  18. ...
  19. })
  20. ...
  21. // 字母表索引組件
  22. AlphabetIndexer({ arrayValue: alphabets, selected: 0 })
  23. .selected(this.selectedIndex)
  24. ...
  25. }
  26. }
  27. }
說明

計(jì)算索引值時(shí),ListItemGroup作為一個(gè)整體占一個(gè)索引值,不計(jì)算ListItemGroup內(nèi)部ListItem的索引值。

響應(yīng)列表項(xiàng)側(cè)滑

側(cè)滑菜單在許多應(yīng)用中都很常見。例如,通訊類應(yīng)用通常會(huì)給消息列表提供側(cè)滑刪除功能,即用戶可以通過向左側(cè)滑列表的某一項(xiàng),再點(diǎn)擊刪除按鈕刪除消息,如下圖所示。

圖15 側(cè)滑刪除列表項(xiàng)

ListItem的swipeAction屬性可用于實(shí)現(xiàn)列表項(xiàng)的左右滑動(dòng)功能。swipeAction屬性方法初始化時(shí)有必填參數(shù)SwipeActionOptions,其中,start參數(shù)表示設(shè)置列表項(xiàng)右滑時(shí)起始端滑出的組件,end參數(shù)表示設(shè)置列表項(xiàng)左滑時(shí)尾端滑出的組件。

在消息列表中,end參數(shù)表示設(shè)置ListItem左滑時(shí)尾端劃出自定義組件,即刪除按鈕。在初始化end方法時(shí),將滑動(dòng)列表項(xiàng)的索引傳入刪除按鈕組件,當(dāng)用戶點(diǎn)擊刪除按鈕時(shí),可以根據(jù)索引值來刪除列表項(xiàng)對應(yīng)的數(shù)據(jù),從而實(shí)現(xiàn)側(cè)滑刪除功能。

  1. @Entry
  2. @Component
  3. struct MessageList {
  4. @State messages: object[] = [
  5. // 初始化消息列表數(shù)據(jù)
  6. ...
  7. ];
  8. @Builder itemEnd(index: number) {
  9. // 側(cè)滑后尾端出現(xiàn)的組件
  10. Button({ type: ButtonType.Circle }) {
  11. Image($r('app.media.ic_public_delete_filled'))
  12. .width(20)
  13. .height(20)
  14. }
  15. .onClick(() => {
  16. this.messages.splice(index, 1);
  17. })
  18. ...
  19. }
  20. build() {
  21. ...
  22. List() {
  23. ForEach(this.messages, (item, index) => {
  24. ListItem() {
  25. ...
  26. }
  27. .swipeAction({ end: this.itemEnd.bind(this, index) }) // 設(shè)置側(cè)滑屬性
  28. }, item => item.id.toString())
  29. }
  30. ...
  31. }
  32. }

給列表項(xiàng)添加標(biāo)記

添加標(biāo)記是一種無干擾性且直觀的方法,用于顯示通知或?qū)⒆⒁饬械綉?yīng)用內(nèi)的某個(gè)區(qū)域。例如,當(dāng)消息列表接收到新消息時(shí),通常對應(yīng)的聯(lián)系人頭像的右上方會(huì)出現(xiàn)標(biāo)記,提示有若干條未讀消息,如下圖所示。

圖16 給列表項(xiàng)添加標(biāo)記

在ListItem中使用Badge組件可實(shí)現(xiàn)給列表項(xiàng)添加標(biāo)記功能。Badge是可以附加在單個(gè)組件上用于信息標(biāo)記的容器組件。

在消息列表中,若希望在聯(lián)系人頭像右上角添加標(biāo)記,可在實(shí)現(xiàn)消息列表項(xiàng)ListItem的聯(lián)系人頭像時(shí),將頭像Image組件作為Badge的子組件。

在Badge組件中,count和position參數(shù)用于設(shè)置需要展示的消息數(shù)量和提示點(diǎn)顯示位置,還可以通過style參數(shù)靈活設(shè)置標(biāo)記的樣式。

  1. Badge({
  2. count: 1,
  3. position: BadgePosition.RightTop,
  4. style: { badgeSize: 16, badgeColor: '#FA2A2D' }
  5. }) {
  6. // Image組件實(shí)現(xiàn)消息聯(lián)系人頭像
  7. ...
  8. }
  9. ...

下拉刷新與上拉加載

頁面的下拉刷新與上拉加載功能在移動(dòng)應(yīng)用中十分常見,例如,新聞頁面的內(nèi)容刷新和加載。這兩種操作的原理都是通過響應(yīng)用戶的觸摸事件,在頂部或者底部顯示一個(gè)刷新或加載視圖,完成后再將此視圖隱藏。

以下拉刷新為例,其實(shí)現(xiàn)主要分成三步:

  1. 監(jiān)聽手指按下事件,記錄其初始位置的值。
  2. 監(jiān)聽手指按壓移動(dòng)事件,記錄并計(jì)算當(dāng)前移動(dòng)的位置與初始值的差值,大于0表示向下移動(dòng),同時(shí)設(shè)置一個(gè)允許移動(dòng)的最大值。
  3. 監(jiān)聽手指抬起事件,若此時(shí)移動(dòng)達(dá)到最大值,則觸發(fā)數(shù)據(jù)加載并顯示刷新視圖,加載完成后將此視圖隱藏。

下拉刷新與上拉加載的具體實(shí)現(xiàn)可參考相關(guān)實(shí)例中新聞數(shù)據(jù)加載。若開發(fā)者希望快速實(shí)現(xiàn)此功能,也可使用三方組件PullToRefresh。

編輯列表

列表的編輯模式用途十分廣泛,常見于待辦事項(xiàng)管理、文件管理、備忘錄的記錄管理等應(yīng)用場景。在列表的編輯模式下,新增和刪除列表項(xiàng)是最基礎(chǔ)的功能,其核心是對列表項(xiàng)對應(yīng)的數(shù)據(jù)集合進(jìn)行數(shù)據(jù)添加和刪除。

下面以待辦事項(xiàng)管理為例,介紹如何快速實(shí)現(xiàn)新增和刪除列表項(xiàng)功能。

新增列表項(xiàng)

如下圖所示,當(dāng)用戶點(diǎn)擊添加按鈕時(shí),提供用戶新增列表項(xiàng)內(nèi)容選擇或填寫的交互界面,用戶點(diǎn)擊確定后,列表中新增對應(yīng)的項(xiàng)目。

圖17 新增待辦

添加列表項(xiàng)功能實(shí)現(xiàn)主要流程如下:

  1. 定義列表項(xiàng)數(shù)據(jù)結(jié)構(gòu)和初始化列表數(shù)據(jù),構(gòu)建列表整體布局和列表項(xiàng)。

    以待辦事項(xiàng)管理為例,首先定義待辦數(shù)據(jù)結(jié)構(gòu):
    1. import util from '@ohos.util';
    2. export class ToDo {
    3. key: string = util.generateRandomUUID(true);
    4. name: string;
    5. constructor(name: string) {
    6. this.name = name;
    7. }
    8. }
    然后,初始化待辦列表數(shù)據(jù)和可選事項(xiàng):
    1. @State toDoData: ToDo[] = [];
    2. private availableThings: string[] = ['讀書', '運(yùn)動(dòng)', '旅游', '聽音樂', '看電影', '唱歌'];

    最后,構(gòu)建列表布局和列表項(xiàng):

    1. List({ space: 10 }) {
    2. ForEach(this.toDoData, (toDoItem) => {
    3. ListItem() {
    4. ...
    5. }
    6. }, toDoItem => toDoItem.key)
    7. }

  2. 提供新增列表項(xiàng)入口,即給新增按鈕添加點(diǎn)擊事件。
  3. 響應(yīng)用戶確定新增事件,更新列表數(shù)據(jù)。

    待辦事項(xiàng)管理示例的步驟2和步驟3功能實(shí)現(xiàn)如下:
    1. Text('+')
    2. .onClick(() => {
    3. TextPickerDialog.show({
    4. range: this.availableThings,
    5. onAccept: (value: TextPickerResult) => {
    6. this.toDoData.push(new ToDo(this.availableThings[value.index])); // 新增列表項(xiàng)數(shù)據(jù)toDoData
    7. },
    8. })
    9. })

刪除列表項(xiàng)

如下圖所示,當(dāng)用戶長按列表項(xiàng)進(jìn)入刪除模式時(shí),提供用戶刪除列表項(xiàng)選擇的交互界面,用戶勾選完成后點(diǎn)擊刪除按鈕,列表中刪除對應(yīng)的項(xiàng)目。

圖18 長按刪除待辦事項(xiàng)

刪除列表項(xiàng)功能實(shí)現(xiàn)主要流程如下:

  1. 列表的刪除功能一般進(jìn)入編輯模式后才可使用,所以需要提供編輯模式的入口。

    以待辦列表為例,通過監(jiān)聽列表項(xiàng)的長按事件,當(dāng)用戶長按列表項(xiàng)時(shí),進(jìn)入編輯模式。
    1. // ToDoListItem.ets
    2. Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) {
    3. ...
    4. }
    5. .gesture(
    6. GestureGroup(GestureMode.Exclusive,
    7. LongPressGesture()
    8. .onAction(() => {
    9. if (!this.isEditMode) {
    10. this.isEditMode = true; //進(jìn)入編輯模式
    11. this.selectedItems.push(this.toDoItem); // 記錄長按時(shí)選中的列表項(xiàng)
    12. }
    13. })
    14. )
    15. )

  2. 需要響應(yīng)用戶的選擇交互,記錄要?jiǎng)h除的列表項(xiàng)數(shù)據(jù)。

    在待辦列表中,通過勾選框的勾選或取消勾選,響應(yīng)用戶勾選列表項(xiàng)變化,記錄所有選擇的列表項(xiàng)。
    1. // ToDoListItem.ets
    2. if (this.isEditMode) {
    3. Checkbox()
    4. .onChange((isSelected) => {
    5. if (isSelected) {
    6. this.selectedItems.push(this.toDoItem) // 勾選時(shí),記錄選中的列表項(xiàng)
    7. } else {
    8. let index = this.selectedItems.indexOf(this.toDoItem)
    9. if (index !== -1) {
    10. this.selectedItems.splice(index, 1) // 取消勾選時(shí),則將此項(xiàng)從selectedItems中刪除
    11. }
    12. }
    13. })
    14. ...
    15. }

  3. 需要響應(yīng)用戶點(diǎn)擊刪除按鈕事件,刪除列表中對應(yīng)的選項(xiàng)。

    1. // ToDoList.ets
    2. Button('刪除')
    3. .onClick(() => {
    4. // 刪除選中的列表項(xiàng)對應(yīng)的toDoData數(shù)據(jù)
    5. let leftData = this.toDoData.filter((item) => {
    6. return this.selectedItems.find((selectedItem) => selectedItem !== item);
    7. })
    8. this.toDoData = leftData;
    9. this.isEditMode = false;
    10. })
    11. ...

長列表的處理

循環(huán)渲染適用于短列表,當(dāng)構(gòu)建具有大量列表項(xiàng)的長列表時(shí),如果直接采用循環(huán)渲染方式,會(huì)一次性加載所有的列表元素,會(huì)導(dǎo)致頁面啟動(dòng)時(shí)間過長,影響用戶體驗(yàn)。因此,推薦使用數(shù)據(jù)懶加載(LazyForEach)方式實(shí)現(xiàn)按需迭代加載數(shù)據(jù),從而提升列表性能。

關(guān)于長列表按需加載優(yōu)化的具體實(shí)現(xiàn)可參考數(shù)據(jù)懶加載章節(jié)中的示例。

當(dāng)使用懶加載方式渲染列表時(shí),為了更好的列表滾動(dòng)體驗(yàn),減少列表滑動(dòng)時(shí)出現(xiàn)白塊,List組件提供了cachedCount參數(shù)用于設(shè)置列表項(xiàng)緩存數(shù),只在懶加載LazyForEach中生效。

  1. List() {
  2. LazyForEach(this.dataSource, item => {
  3. ListItem() {
  4. ...
  5. }
  6. })
  7. }.cachedCount(3)

以垂直列表為例:

  • 若懶加載是用于ListItem,當(dāng)列表為單列模式時(shí),會(huì)在List顯示的ListItem前后各緩存cachedCount個(gè)ListItem;若是多列模式下,會(huì)在List顯示的ListItem前后各緩存cachedCount*列數(shù)個(gè)ListItem。
  • 若懶加載是用于ListItemGroup,無論單列模式還是多列模式,都是在List顯示的ListItem前后各緩存cachedCount個(gè)ListItemGroup。
說明
  1. cachedCount的增加會(huì)增大UI的CPU、內(nèi)存開銷。使用時(shí)需要根據(jù)實(shí)際情況,綜合性能和用戶體驗(yàn)進(jìn)行調(diào)整。
  2. 列表使用數(shù)據(jù)懶加載時(shí),除了顯示區(qū)域的列表項(xiàng)和前后緩存的列表項(xiàng),其他列表項(xiàng)會(huì)被銷毀。

相關(guān)實(shí)例

如需詳細(xì)了解ArkUI中列表的創(chuàng)建與使用,請參考以下示例:

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號