List列表組件基礎展示與數據綁定

引言

在HarmonyOS應用開發中,List組件是最常用且功能強大的數據展示控件之一。無論你是開發社交應用的消息列表、電商應用的商品列表,還是設置頁面的選項列表,List組件都能提供流暢的滾動體驗和高效的性能表現。本文將帶你從零開始掌握List組件的使用,重點講解基礎展示、數據綁定以及實際開發中的最佳實踐。

官方參考資料

  • List組件官方文檔
  • List組件api
  • ArkTS語言官方文檔

一、List組件基礎概念

1.1 什麼是List組件

List組件是一個可滾動的列表容器,用於呈現相同或相似類型的數據集合。它具有以下核心特性:

  • 垂直滾動:默認支持垂直方向的滾動
  • 高性能:採用懶加載機制,只渲染可視區域內的項
  • 多樣化佈局:支持線性佈局、網格佈局等多種排列方式
  • 豐富的交互:支持點擊、長按、滑動等手勢操作

1.2 List組件的基本結構

一個典型的List組件包含以下部分:

List() {
  // 列表項內容
  ListItem() {
    // 列表項的具體UI組件
  }
}

二、創建基礎List列表

2.1 最簡單的List示例

讓我們從一個最基本的文本列表開始:

@Entry
@Component
struct BasicListExample {
  build() {
    Column() {
      List({ space: 10 }) {
        ListItem() {
          Text('列表項1')
            .fontSize(20)
            .padding(10)
        }

        ListItem() {
          Text('列表項2')
            .fontSize(20)
            .padding(10)
        }

        ListItem() {
          Text('列表項3')
            .fontSize(20)
            .padding(10)
        }
      }
      .width('100%')
      .height('100%')
    }
  }
}

2.2 List核心屬性詳解

List組件提供了豐富的屬性來控制列表的顯示和行為:

屬性名

類型

説明

默認值

space

number

列表項之間的間距

0

initialIndex

number

初始滾動位置

0

scroller

Scroller

列表滾動控制器

-

listDirection

Axis

列表方向(垂直/水平)

Axis.Vertical

重要提示:在實際開發中,建議始終設置List的寬度和高度,否則可能無法正常顯示滾動效果。

三、數據綁定與動態列表

3.1 使用ForEach進行數據綁定

靜態定義的列表在實際開發中很少使用,大多數情況下我們需要動態綁定數據:

@Entry
@Component
struct DynamicListExample {
  // 定義數據源
  private items: string[] = ['蘋果', '香蕉', '橙子', '葡萄', '西瓜', '芒果']

  build() {
    Column() {
      List({ space: 8 }) {
        // 使用ForEach循環渲染列表項
        ForEach(this.items, (item: string, index: number) => {
          ListItem() {
            Text(`${index + 1}. ${item}`)
              .fontSize(18)
              .fontColor(Color.Black)
              .backgroundColor(Color.White)
              .padding(12)
              .width('100%')
          }
        }, (item: string) => item)
      }
      .width('100%')
      .height('100%')
      .divider({ strokeWidth: 1, color: '#F1F1F1' })
    }
  }
}

3.2 複雜數據結構的列表展示

實際應用中的數據通常更復雜,讓我們看一個對象數組的示例:

// 定義數據模型
class Product {
  id: number
  name: string
  price: number
  category: string

  constructor(id: number, name: string, price: number, category: string) {
    this.id = id
    this.name = name
    this.price = price
    this.category = category
  }
}

@Entry
@Component
struct ProductListExample {
  private products: Product[] = [
    new Product(1, '華為Mate 60', 5999, '手機'),
    new Product(2, '華為Watch 4', 2499, '智能手錶'),
    new Product(3, '華為MatePad', 3299, '平板電腦'),
    new Product(4, '華為FreeBuds', 899, '耳機')
  ]

  build() {
    Column() {
      List({ space: 12 }) {
        ForEach(this.products, (product: Product) => {
          ListItem() {
            Column({ space: 8 }) {
              Text(product.name)
                .fontSize(20)
                .fontColor('#0018A7')
                .fontWeight(FontWeight.Bold)
                .width('100%')

              Row() {
                Text(`¥${product.price}`)
                  .fontSize(16)
                  .fontColor('#E30000')

                Text(product.category)
                  .fontSize(14)
                  .fontColor('#666666')
                  .margin({ left: 12 })
                  .backgroundColor('#F5F5F5')
                  .padding({ left: 8, right: 8, top: 2, bottom: 2 })
                  .borderRadius(4)
              }
              .width('100%')
              .justifyContent(FlexAlign.Start)
            }
            .padding(16)
            .backgroundColor(Color.White)
            .borderRadius(8)
            .shadow({ radius: 4, color: '#1A000000', offsetX: 0, offsetY: 2 })
          }
        }, (product: Product) => product.id.toString())
      }
      .padding(16)
      .width('100%')
      .height('100%')
      .backgroundColor('#F8F8F8')
    }
  }
}

注意事項

  • ForEach的第三個參數是鍵值生成函數,必須為每個列表項提供唯一的鍵值
  • 對於對象數組,建議使用對象的唯一標識符作為鍵值
  • 如果沒有提供合適的鍵值,列表的更新性能會受到影響

四、列表交互與事件處理

4.1 點擊事件處理

為列表項添加點擊交互:

@Entry
@Component
struct InteractiveListExample {
  private messages: string[] = [
    '早上好,今天天氣不錯',
    '下午有個重要的會議',
    '記得完成項目報告',
    '晚上7點健身房見'
  ]

  build() {
    Column() {
      List({ space: 1 }) {
        ForEach(this.messages, (message: string, index: number) => {
          ListItem() {
            Row() {
              Text(`消息 ${index + 1}`)
                .fontSize(16)
                .fontWeight(FontWeight.Medium)
                
              Text(message)
                .fontSize(14)
                .fontColor('#666666')
                .maxLines(1)
                .textOverflow({ overflow: TextOverflow.Ellipsis })
                .layoutWeight(1)
            }
            .padding(16)
            .width('100%')
          }
          .onClick(() => {
            // 處理點擊事件
            console.log(`點擊了第${index + 1}條消息: ${message}`)
            // 在實際應用中,這裏通常會跳轉到詳情頁面或執行其他操作
          })
        }, (message: string, index: number) => index.toString())
      }
      .width('100%')
      .height('100%')
    }
  }
}

4.2 長按事件與滑動操作

@Entry
@Component
struct AdvancedInteractionList {
  @State private tasks: string[] = [
    '完成HarmonyOS學習',
    '編寫項目文檔',
    '代碼評審',
    '團隊會議',
    '產品需求分析'
  ]

  build() {
    Column() {
      List({ space: 8 }) {
        ForEach(this.tasks, (task: string, index: number) => {
          ListItem() {
            Text(task)
              .fontSize(18)
              .padding(16)
              .width('100%')
              .backgroundColor(Color.White)
              .borderRadius(8)
          }
          .onClick(() => {
            console.log(`開始任務: ${task}`)
          })
          .onLongPress(() => {
            console.log(`長按任務: ${task}`)
            // 顯示刪除確認對話框等
          })
          .swipeAction({ 
            end: this.DeleteButton(index) 
          })
        }, (task: string, index: number) => index.toString())
      }
      .width('100%')
      .height('100%')
      .padding(16)
      .backgroundColor('#F5F5F5')
    }
  }

  @Builder DeleteButton(index: number) {
    Button('刪除')
      .backgroundColor(Color.Red)
      .fontColor(Color.White)
      .width(80)
      .height('100%')
      .onClick(() => {
        // 刪除對應的任務
        this.tasks.splice(index, 1)
        this.tasks = [...this.tasks] // 觸發UI更新
      })
  }
}

五、自定義列表項佈局

5.1 複雜列表項設計

實際應用中的列表項通常包含圖片、文本等多種元素:

class Contact {
  id: string
  name: string
  phone: string
  avatar: Resource
  online: boolean

  constructor(id: string, name: string, phone: string, avatar: Resource, online: boolean) {
    this.id = id
    this.name = name
    this.phone = phone
    this.avatar = avatar
    this.online = online
  }
}

@Entry
@Component
struct ContactListExample {
  private contacts: Contact[] = [
    new Contact('1', '張三', '138****1234', $r('app.media.avatar1'), true),
    new Contact('2', '李四', '139****5678', $r('app.media.avatar2'), false),
    new Contact('3', '王五', '136****9012', $r('app.media.avatar3'), true)
  ]

  build() {
    Column() {
      List({ space: 1 }) {
        ForEach(this.contacts, (contact: Contact) => {
          ListItem() {
            Row({ space: 12 }) {
              // 頭像
              Stack() {
                Image(contact.avatar)
                  .width(50)
                  .height(50)
                  .borderRadius(25)
                
                // 在線狀態指示器
                if (contact.online) {
                  Circle({ width: 12, height: 12 })
                    .fill('#52C41A')
                    .position({ x: 38, y: 38 })
                    .border({ width: 2, color: Color.White })
                }
              }
              .width(50)
              .height(50)

              // 聯繫人信息
              Column({ space: 4 }) {
                Text(contact.name)
                  .fontSize(18)
                  .fontColor(Color.Black)
                  .fontWeight(FontWeight.Medium)
                  .width('100%')

                Text(contact.phone)
                  .fontSize(14)
                  .fontColor('#666666')
                  .width('100%')
              }
              .layoutWeight(1)

              // 操作按鈕
              Image($r('app.media.ic_call'))
                .width(24)
                .height(24)
                .onClick(() => {
                  console.log(`呼叫 ${contact.name}`)
                })
            }
            .padding(16)
            .width('100%')
          }
        }, (contact: Contact) => contact.id)
      }
      .width('100%')
      .height('100%')
    }
  }
}

六、列表性能優化

6.1 使用LazyForEach處理大數據集

當列表數據量很大時,使用LazyForEach可以顯著提升性能:

// 數據源實現接口
class MyDataSource implements IDataSource {
  private data: string[] = []
  private listeners: DataChangeListener[] = []

  constructor(data: string[]) {
    this.data = data
  }

  totalCount(): number {
    return this.data.length
  }

  getData(index: number): string {
    return this.data[index]
  }

  registerDataChangeListener(listener: DataChangeListener): void {
    this.listeners.push(listener)
  }

  unregisterDataChangeListener(listener: DataChangeListener): void {
    const index = this.listeners.indexOf(listener)
    if (index >= 0) {
      this.listeners.splice(index, 1)
    }
  }
}

@Entry
@Component
struct LargeListExample {
  private dataSource: MyDataSource = new MyDataSource(
    Array.from({ length: 1000 }, (_, i) => `列表項 ${i + 1}`)
  )

  build() {
    Column() {
      List({ space: 1 }) {
        LazyForEach(this.dataSource, (item: string) => {
          ListItem() {
            Text(item)
              .fontSize(16)
              .padding(12)
              .width('100%')
          }
        }, (item: string) => item)
      }
      .width('100%')
      .height('100%')
    }
  }
}

6.2 列表性能優化技巧

  • 使用合適的鍵值:為每個列表項提供穩定且唯一的鍵值
  • 避免複雜佈局:簡化列表項的佈局層次
  • 圖片優化:對列表中的圖片進行適當壓縮和緩存
  • 分頁加載:大數據集採用分頁加載策略
  • 虛擬化渲染:List組件默認支持,無需額外配置

七、實際開發中的注意事項

7.1 常見問題與解決方案

問題1:列表滾動卡頓

  • 原因:列表項佈局過於複雜或數據綁定效率低
  • 解決方案:
  • 使用LazyForEach替代ForEach處理大數據集
  • 簡化列表項佈局結構
  • 避免在列表項中使用過於複雜的計算

問題2:列表項狀態異常

  • 原因:鍵值不唯一或狀態管理不當
  • 解決方案:
  • 確保每個列表項有唯一的鍵值
  • 合理使用@State、@Prop等裝飾器管理狀態

問題3:內存泄漏

  • 原因:事件監聽器未正確移除
  • 解決方案:
  • 及時移除不需要的事件監聽
  • 使用弱引用或適當的作用域管理

7.2 版本兼容性説明

  • List組件從API version 7開始支持
  • swipeAction屬性從API version 9開始支持
  • LazyForEach從API version 8開始支持
  • 建議在開發時關注目標設備的HarmonyOS版本

八、完整實戰案例:任務管理應用

讓我們結合所學知識,創建一個完整的任務管理應用:

class Task {
  id: string
  title: string
  completed: boolean
  priority: number // 1-高, 2-中, 3-低
  createTime: number

  constructor(id: string, title: string, priority: number) {
    this.id = id
    this.title = title
    this.completed = false
    this.priority = priority
    this.createTime = new Date().getTime()
  }
}

@Entry
@Component
struct TaskManagerApp {
  @State private tasks: Task[] = [
    new Task('1', '學習HarmonyOS開發', 1),
    new Task('2', '完成項目文檔', 2),
    new Task('3', '購買生活用品', 3)
  ]

  @State private newTaskTitle: string = ''

  build() {
    Column() {
      // 標題
      Text('任務管理')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .margin({ top: 20, bottom: 20 })

      // 添加新任務
      Row({ space: 8 }) {
        TextInput({ placeholder: '輸入新任務...', text: this.newTaskTitle })
          .layoutWeight(1)
          .onChange((value: string) => {
            this.newTaskTitle = value
          })

        Button('添加')
          .backgroundColor('#1890FF')
          .fontColor(Color.White)
          .onClick(() => {
            if (this.newTaskTitle.trim()) {
              const newTask = new Task(
                Date.now().toString(),
                this.newTaskTitle,
                2
              )
              this.tasks = [newTask, ...this.tasks]
              this.newTaskTitle = ''
            }
          })
      }
      .padding(16)
      .width('100%')

      // 任務列表
      List({ space: 8 }) {
        ForEach(this.tasks, (task: Task) => {
          ListItem() {
            this.TaskItem(task)
          }
          .swipeAction({ 
            end: this.DeleteTaskButton(task.id) 
          })
        }, (task: Task) => task.id)
      }
      .layoutWeight(1)
      .width('100%')
      .padding(16)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F8F9FA')
  }

  @Builder
  TaskItem(task: Task) {
    Row({ space: 12 }) {
      // 完成狀態複選框
      Image(task.completed ? $r('app.media.ic_checked') : $r('app.media.ic_unchecked'))
        .width(24)
        .height(24)
        .onClick(() => {
          task.completed = !task.completed
          this.tasks = [...this.tasks]
        })

      // 任務內容
      Column({ space: 4 }) {
        Text(task.title)
          .fontSize(16)
          .fontColor(task.completed ? '#999999' : Color.Black)
          .decoration({ type: task.completed ? TextDecorationType.LineThrough : TextDecorationType.None })

        // 優先級標籤
        Row() {
          Text(this.getPriorityText(task.priority))
            .fontSize(12)
            .fontColor(this.getPriorityColor(task.priority))
        }
        .padding({ left: 4, right: 4, top: 2, bottom: 2 })
        .backgroundColor(this.getPriorityBgColor(task.priority))
        .borderRadius(4)
      }
      .layoutWeight(1)
    }
    .padding(16)
    .width('100%')
    .backgroundColor(Color.White)
    .borderRadius(8)
    .shadow({ radius: 2, color: '#1A000000', offsetX: 0, offsetY: 1 })
  }

  @Builder
  DeleteTaskButton(taskId: string) {
    Button('刪除')
      .backgroundColor(Color.Red)
      .fontColor(Color.White)
      .width(80)
      .height('100%')
      .onClick(() => {
        this.tasks = this.tasks.filter(task => task.id !== taskId)
      })
  }

  private getPriorityText(priority: number): string {
    switch (priority) {
      case 1: return '高優先級'
      case 2: return '中優先級'
      case 3: return '低優先級'
      default: return '普通'
    }
  }

  private getPriorityColor(priority: number): string {
    switch (priority) {
      case 1: return '#CF1322'
      case 2: return '#FA8C16'
      case 3: return '#52C41A'
      default: return '#666666'
    }
  }

  private getPriorityBgColor(priority: number): string {
    switch (priority) {
      case 1: return '#FFF1F0'
      case 2: return '#FFF7E6'
      case 3: return '#F6FFED'
      default: return '#F5F5F5'
    }
  }
}

總結

通過本文的學習,你應該已經掌握了:

  • List組件的基礎用法和核心屬性
  • 使用ForEach和LazyForEach進行數據綁定
  • 列表交互事件的處理方法
  • 複雜列表項的自定義佈局設計
  • 列表性能優化的實用技巧
  • 實際開發中的注意事項和最佳實踐

List組件是HarmonyOS應用開發中不可或缺的重要組件,熟練掌握它的使用將極大提升你的開發效率和應用的性能表現。建議在實際項目中多加練習,不斷探索List組件的更多高級特性。

下一步學習建議

  • 學習Grid網格佈局組件
  • 探索List與Navigation的組合使用
  • 瞭解自定義組件在列表中的應用
  • 研究列表的動畫和過渡效果

Happy Coding!