HarmonyOS 通用列表流程使用指南

參考文檔

  1. 華為開發者聯盟 - 常見列表流開發實踐

概述

列表流是採用以"行"為單位進行內容排列的佈局形式,每"行"列表項通過文本、圖片等不同形式的組合,高效地顯示結構化的信息,當列表項內容超過屏幕大小時,可以提供滾動功能。

列表流具有以下特點:

  • 排版整齊
  • 重點突出
  • 對比方便
  • 瀏覽速度快

常見使用場景包括:應用首頁、通訊錄、音樂列表、購物清單等。

在 HarmonyOS 中,列表流主要使用 List 組件,按垂直方向線性排列子組件 ListItemGroup 或 ListItem,混合渲染任意數量的圖文視圖,從而構建列表內容。

基礎組件

List 組件

List 是最基礎的列表容器組件,用於創建可滾動的列表。

import { Column, List, ListItem, Text } from '@ohos/components';

@Entry
@Component
struct BasicListExample {
  private listData: string[] = ['列表項1', '列表項2', '列表項3', '列表項4', '列表項5'];

  build() {
    Column() {
      List({
        scroller: null,
        space: 20
      })
        .width('100%')
        .height('100%')
        .onScrollIndex((start: number, end: number) => {
          // 滾動事件處理
        })
        .onReachStart(() => {
          // 滾動到頂部
        })
        .onReachEnd(() => {
          // 滾動到底部,可用於加載更多
        })
        .onScrollStop(() => {
          // 滾動停止
        })
        {
          ForEach(this.listData, (item) => {
            ListItem() {
              Text(item)
                .width('100%')
                .height(60)
                .textAlign(TextAlign.Center)
                .fontSize(16)
            }
            .backgroundColor('#f0f0f0')
            .margin({ left: 16, right: 16 })
          }, item => item)
        }
    }
    .padding(16)
  }
}

常見列表流場景實現

1. 多類型列表項場景

場景描述

List 組件作為整個首頁長列表的容器,通過 ListItem 對不同模塊進行視圖界面定製,常用於門户首頁、商城首頁等多類型視圖展示的列表信息流場景。

實現原理

根據列表內部各部分視圖對應數據類型的區別,渲染不同的 ListItem 子組件。

import { Column, List, ListItem, Text, Image, Grid, GridItem } from '@ohos/components';

@Entry
@Component
struct MultiTypeListExample {
  // 模擬數據
  private bannerData = {
    images: ['banner1.png', 'banner2.png'],
    type: 'banner'
  };

  private recommendData = [
    { id: 1, title: '推薦內容1', image: 'rec1.png' },
    { id: 2, title: '推薦內容2', image: 'rec2.png' },
    { id: 3, title: '推薦內容3', image: 'rec3.png' },
    { id: 4, title: '推薦內容4', image: 'rec4.png' },
  ];

  private articleData = [
    { id: 1, title: '文章標題1', desc: '文章描述1', type: 'article' },
    { id: 2, title: '文章標題2', desc: '文章描述2', type: 'article' },
  ];

  build() {
    Column() {
      List() {
        // Banner類型列表項
        ListItem() {
          Column() {
            Text('Banner區域')
              .fontSize(18)
              .fontWeight(FontWeight.Bold)
            // 這裏可以放置輪播圖組件
            Image(this.bannerData.images[0])
              .width('100%')
              .height(200)
              .borderRadius(12)
          }
          .padding(16)
        }

        // 推薦內容類型列表項
        ListItem() {
          Column() {
            Text('推薦內容')
              .fontSize(18)
              .fontWeight(FontWeight.Bold)
              .margin({ bottom: 16 })

            Grid() {
              ForEach(this.recommendData, (item) => {
                GridItem() {
                  Column() {
                    Image(item.image)
                      .width('100%')
                      .height(120)
                      .aspectRatio(1)
                    Text(item.title)
                      .fontSize(14)
                      .margin({ top: 8 })
                  }
                }
              }, item => item.id)
            }
            .columnsTemplate('1fr 1fr')
            .columnsGap(16)
            .rowsGap(16)
          }
          .padding(16)
        }

        // 文章列表類型列表項
        ListItem() {
          Column() {
            Text('最新文章')
              .fontSize(18)
              .fontWeight(FontWeight.Bold)
              .margin({ bottom: 16 })

            ForEach(this.articleData, (item) => {
              Column() {
                Text(item.title)
                  .fontSize(16)
                  .fontWeight(FontWeight.Medium)
                Text(item.desc)
                  .fontSize(14)
                  .fontColor('#666666')
                  .margin({ top: 8 })
              }
              .padding(16)
              .backgroundColor('#f5f5f5')
              .borderRadius(8)
              .margin({ bottom: 12 })
            }, item => item.id)
          }
          .padding(16)
        }
      }
    }
  }
}

2. Tabs 吸頂場景

場景描述

當頁面包含 Tabs 組件時,實現 Tabs 在滾動到頂部後保持吸頂狀態,常用於分類瀏覽、數據篩選等場景。

實現原理

使用 List 組件的 sticky 屬性實現元素吸頂效果。

import { Column, List, ListItem, Tabs, TabContent, Text } from '@ohos/components';

@Entry
@Component
struct StickyTabsExample {
  private tabList: string[] = ['推薦', '關注', '熱門', '最新'];

  build() {
    Column() {
      // 頁面頂部內容
      Column() {
        Text('頁面標題')
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
          .padding(16)
      }

      // 可吸頂的Tabs組件
      Tabs() {
        ForEach(this.tabList, (tab) => {
          TabContent() {
            List() {
              ListItem() {
                Text(`${tab}內容列表`)
                  .fontSize(16)
                  .padding(20)
              }
              // 生成更多列表項
              ForEach(Array.from({ length: 20 }), (_, index) => {
                ListItem() {
                  Text(`${tab}內容 ${index + 1}`)
                    .fontSize(16)
                    .padding(20)
                    .backgroundColor('#f5f5f5')
                    .margin({ left: 16, right: 16, top: 12 })
                    .borderRadius(8)
                }
              })
            }
          }
          .tabBar(tab)
        })
      }
      .width('100%')
      .height('80%')
      .barMode(BarMode.Scrollable)
      .scrollable(true)
    }
  }
}

3. 分組吸頂場景

場景描述

列表內容按分組展示,每個分組的標題在滾動到頂部時保持吸頂狀態,常用於聯繫人列表、城市選擇等場景。

實現原理

使用 ListItemGroup 組件創建分組,並設置 sticky 屬性實現分組標題吸頂。

import { Column, List, ListItemGroup, ListItem, Text } from '@ohos/components';

@Entry
@Component
struct GroupStickyExample {
  // 模擬分組數據
  private groupData = [
    {
      title: 'A',
      items: ['Alice', 'Amy', 'Anna']
    },
    {
      title: 'B',
      items: ['Bob', 'Bill']
    },
    {
      title: 'C',
      items: ['Charlie', 'Cathy', 'Carol']
    }
  ];

  build() {
    Column() {
      Text('聯繫人列表')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .padding(16)

      List() {
        ForEach(this.groupData, (group) => {
          ListItemGroup({
            header: () => {
              // 分組標題
              return (
                Text(group.title)
                  .fontSize(18)
                  .fontWeight(FontWeight.Bold)
                  .backgroundColor('#e0e0e0')
                  .padding(12)
                  .width('100%')
              )
            },
            footer: () => {
              // 分組底部
              return null;
            },
            sticky: ListItemStickyStyle.Header
          })
          {
            // 分組內的列表項
            ForEach(group.items, (item) => {
              ListItem() {
                Text(item)
                  .fontSize(16)
                  .padding(16)
                  .width('100%')
              }
              .borderBottom({ width: 1, color: '#f0f0f0' })
            })
          }
        })
      }
      .width('100%')
      .height('80%')
    }
  }
}

4. 二級聯動場景

場景描述

左側為分類列表,右側為對應分類下的內容列表,滾動右側內容時左側分類同步高亮顯示,點擊左側分類時右側內容滾動到對應位置,常用於商品分類、點餐應用等場景。

實現原理

通過監聽左右兩側列表的滾動和點擊事件,實現聯動效果。

import { Column, Row, List, ListItem, Text, Scroll, ScrollController } from '@ohos/components';

@Entry
@Component
struct TwoLevelLinkageExample {
  private categories = ['分類1', '分類2', '分類3', '分類4', '分類5'];
  private selectedIndex: number = 0;
  private scrollController: ScrollController = new ScrollController();

  // 模擬分類內容數據
  private categoryData = {
    '分類1': ['商品1-1', '商品1-2', '商品1-3', '商品1-4'],
    '分類2': ['商品2-1', '商品2-2'],
    '分類3': ['商品3-1', '商品3-2', '商品3-3'],
    '分類4': ['商品4-1', '商品4-2', '商品4-3', '商品4-4', '商品4-5'],
    '分類5': ['商品5-1', '商品5-2']
  };

  // 點擊分類處理函數
  onCategoryClick(index: number) {
    this.selectedIndex = index;
    // 滾動到對應位置
    this.scrollController.scrollTo({ xOffset: 0, yOffset: 0 });
  }

  build() {
    Column() {
      Row() {
        // 左側分類列表
        List() {
          ForEach(this.categories, (category, index) => {
            ListItem() {
              Text(category)
                .fontSize(16)
                .padding(16)
                .width('100%')
                .backgroundColor(this.selectedIndex === index ? '#e6f7ff' : '#ffffff')
                .fontColor(this.selectedIndex === index ? '#1890ff' : '#000000')
                .onClick(() => this.onCategoryClick(index))
            }
            .borderBottom({ width: 1, color: '#f0f0f0' })
          })
        }
        .width('30%')
        .height('80%')

        // 右側內容列表
        Scroll(this.scrollController) {
          Column() {
            ForEach(this.categoryData[this.categories[this.selectedIndex]], (item) => {
              Text(item)
                .fontSize(16)
                .padding(16)
                .width('100%')
                .backgroundColor('#f5f5f5')
                .margin({ bottom: 12 })
                .borderRadius(8)
            })
          }
          .padding(16)
        }
        .width('70%')
        .height('80%')
      }
    }
  }
}

性能優化

1. 虛擬化渲染

HarmonyOS 的 List 組件默認開啓虛擬化渲染,只會渲染可視區域內的列表項,以及少量緩衝區的列表項,可以有效減少內存佔用和提高渲染性能。

2. 懶加載

對於列表中的圖片等資源,可以實現懶加載,只在圖片即將進入可視區域時再加載。

import { Image } from '@ohos/components';

// 自定義懶加載圖片組件
@Component
struct LazyLoadImage {
  @Prop src: string;
  private isLoaded: boolean = false;

  // 當組件可見時加載圖片
  onVisibleChanged(visible: boolean) {
    if (visible && !this.isLoaded) {
      this.isLoaded = true;
    }
  }

  build() {
    Image(this.isLoaded ? this.src : '')
      .onVisible(this.onVisibleChanged)
      .width('100%')
      .height(200)
  }
}

3. 避免不必要的重渲染

使用memo@Watch等機制,避免組件不必要的重渲染。

// 使用memo優化子組件
const MemoizedListItem = memo((props: { data: any }) => {
  return (
    ListItem() {
      Text(props.data.title)
        .fontSize(16)
        .padding(16)
    }
  );
});

注意事項與最佳實踐

1. 列表項高度設置

為了提高渲染性能,建議為列表項設置固定高度或最大高度,避免動態高度導致的佈局重計算。

2. 大數據量處理

當列表數據量較大時:

  • 實現分頁加載
  • 使用虛擬列表
  • 避免在滾動過程中執行復雜計算

3. 事件處理優化

  • 使用事件委託減少事件監聽器數量
  • 避免在onScroll等頻繁觸發的事件中執行耗時操作
List()
  .onScroll(() => {
    // 避免在這裏執行復雜計算
  })
  // 可以使用節流處理
  .onScrollThrottle(300, () => {
    // 每300ms最多執行一次
  });

4. 列表緩存

對於頻繁訪問的列表數據,可以實現緩存機制,減少重複請求。

常見問題解答

Q: List 組件和 Scroll 組件有什麼區別?

A: List 組件專為列表數據展示優化,支持虛擬化渲染、分組、粘性頭部等功能,性能更好;Scroll 組件是通用的滾動容器,功能更靈活但性能相對較差。

Q: 如何實現列表的下拉刷新和上拉加載更多?

A: 可以使用 Refresh 組件實現下拉刷新,結合 List 的 onReachEnd 事件實現上拉加載更多。

Refresh({
  refreshing: this.isRefreshing,
  onRefresh: () => {
    // 下拉刷新邏輯
    this.loadData();
  },
});
{
  List().onReachEnd(() => {
    // 上拉加載更多邏輯
    this.loadMoreData();
  });
}

Q: 如何優化長列表的性能?

A:

  1. 使用固定高度的列表項
  2. 開啓虛擬化渲染
  3. 實現懶加載
  4. 避免在滾動過程中執行復雜操作
  5. 使用 memo 優化組件渲染

總結

HarmonyOS 的列表流開發主要使用 List 組件,可以實現多種複雜的列表場景。通過本文介紹的多類型列表項、Tabs 吸頂、分組吸頂和二級聯動等場景的實現方法,開發者可以快速構建出功能豐富、性能優良的列表頁面。在實際開發中,還需要注意性能優化和用户體驗,確保列表滾動流暢,響應迅速。