Android Compose UI 清單 (Lazy List)

App 中最常見的介面就是清單。在 Compose 中,我們不使用 RecyclerView,而是使用更簡單強大的 Lazy List

「Lazy」的意思是懶加載:它只會繪製螢幕上可見的項目。這對於成千上萬筆資料的清單來說,效能至關重要。

垂直清單 (LazyColumn)

LazyColumn(
    modifier = Modifier.fillMaxSize(),
    contentPadding = PaddingValues(16.dp), // 清單四周的留白
    verticalArrangement = Arrangement.spacedBy(8.dp) // 項目之間的間距
) {
    // 1. 單個項目
    item {
        Text("Header", style = MaterialTheme.typography.titleLarge)
    }

    // 2. 多個項目 (固定數量)
    items(5) { index ->
        Text("Item $index")
    }

    // 3. 多個項目 (來自 List 資料)
    val names = listOf("Alice", "Bob", "Charlie")
    items(names) { name ->
        Text("Hello $name")
    }
}

水平清單 (LazyRow)

用法與 LazyColumn 幾乎一模一樣,只是方向不同。

LazyRow(
    horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
    items(10) {
        Card { ... }
    }
}

Key 的重要性

當清單資料會發生變動(新增、刪除、排序)時,提供唯一的 key 非常重要。這能幫助 Compose 識別該項目,避免不必要的重組,並保持捲動位置。

items(
    items = userList,
    key = { user -> user.id } // 使用唯一 ID 作為 Key
) { user ->
    UserRow(user)
}

捲動控制 (LazyListState)

透過 rememberLazyListState,我們可以控制或監聽清單的捲動位置。

val listState = rememberLazyListState()
val scope = rememberCoroutineScope()

// 顯示 "回到頂端" 按鈕
val showButton = listState.firstVisibleItemIndex > 0

if (showButton) {
    FloatingActionButton(
        onClick = {
            scope.launch {
                listState.animateScrollToItem(0) // 平滑捲動到頂端
            }
        }
    ) {
        Icon(Icons.Filled.KeyboardArrowUp, contentDescription = null)
    }
}

LazyColumn(state = listState) { ... }

Sticky Headers (黏性標題)

在聯絡人清單中,常見滑動時 "A", "B", "C" 的分組標題會吸附在頂端。LazyColumn 內建支援這個功能。

val grouped = contacts.groupBy { it.name.first() }

LazyColumn {
    grouped.forEach { (initial, contactsForInitial) ->
        stickyHeader {
            Text(
                text = initial.toString(),
                modifier = Modifier
                    .fillMaxWidth()
                    .background(Color.LightGray)
                    .padding(8.dp)
            )
        }

        items(contactsForInitial) { contact ->
            ContactItem(contact)
        }
    }
}

小結

LazyColumnRecyclerView 的完美替代品。它不需要 Adapter,不需要 ViewHolder,程式碼量減少了 90% 以上,寫起來非常愉悅。