LazyColumn — มันคืออะไร, รายการที่มีประสิทธิภาพใน Compose

ผู้แต่ง: IT Sectr เผยแพร่เมื่อ: 2026-02-24 เวลาอ่าน: 10 นาที

LazyColumn — คอมโพเนนต์ Jetpack Compose สำหรับแสดงรายการที่เลื่อนได้พร้อมการโหลดแบบขี้เกียจ แตกต่างจาก RecyclerView ตรงที่ LazyColumn ไม่สร้าง ViewHolders และไม่ใช้ XML — องค์ประกอบทั้งหมดถูกอธิบายแบบประกาศผ่านฟังก์ชัน composable เราแสดงให้เห็นว่า LazyColumn คืออะไร LazyListState และ items API ทำงานอย่างไร และวิธีทำให้ได้ประสิทธิภาพ 60 FPS ที่ IT Sectr เราใช้ LazyColumn ในโปรเจกต์ Compose ใหม่ทั้งหมด สำหรับการเปรียบเทียบกับแนวทางคลาสสิก อ่านบทความเกี่ยวกับ RecyclerView

ประเด็นสำคัญ

  • LazyColumn — คอมโพเนนต์ Jetpack Compose สำหรับรายการแนวตั้งแบบโหลดขี้เกียจ ซึ่งเป็นส่วนหนึ่งของ Material Design 3
  • LazyListState — สถานะของรายการ: ตำแหน่งการเลื่อน องค์ประกอบที่มองเห็น การเลื่อนแบบโปรแกรม
  • items API — ฟังก์ชัน items(), itemsIndexed(), items() พร้อมคีย์สำหรับการผูกข้อมูล
  • key — พารามิเตอร์สำหรับการระบุองค์ประกอบที่เสถียร ทำให้ Compose สามารถนำคอมโพซิชันกลับมาใช้ใหม่ได้
  • LazyVerticalGrid — เวอร์ชันของ LazyColumn สำหรับกริดที่มีจำนวนคอลัมน์คงที่

LazyColumn ใน Jetpack Compose คืออะไร?

LazyColumn คือฟังก์ชัน composable จากไลบรารี Foundation Compose ที่แสดงรายการแนวตั้งขององค์ประกอบแบบโหลดขี้เกียจ: เฉพาะองค์ประกอบที่มองเห็นบนหน้าจอหรือใกล้เคียง (โซน prefetch) เท่านั้นที่ถูกสร้างและคอมโพส LazyColumn ปรากฏใน Compose 1.0 (2021) พร้อมกับ LazyRow (รายการแนวนอน) และ LazyVerticalGrid (กริด)

การโหลดขี้เกียจ ทำงานผ่านกลไก SubcomposeLayout: LazyColumn วัดพื้นที่ว่าง ขอช่วงที่มองเห็นจาก LayoutInfo และคอมโพสเฉพาะองค์ประกอบในช่วงนั้น องค์ประกอบที่เลื่อนออกจากหน้าจอจะออกจากคอมโพซิชัน (ยกเว้นบัฟเฟอร์ prefetch) ตามข้อมูลของ Google (Android Performance, 2026) LazyColumn รักษา 60 FPS เมื่อเลื่อนผ่านรายการ 10000+ องค์ประกอบบนอุปกรณ์ระดับกลาง

ความแตกต่างจาก Column + ScrollState

Column พร้อมการเลื่อนแนวตั้ง — คือการจัดเรียงปกติขององค์ประกอบทั้งหมดเรียงกันโดยใช้มอดิไฟเออร์ verticalScroll Column ไม่ขี้เกียจ: องค์ประกอบย่อยทั้งหมดถูกคอมโพสทันที แม้ว่าจะไม่มองเห็นก็ตาม สำหรับรายการที่มี 200+ องค์ประกอบ Column ทำให้เกิดความล่าช้าอย่างมากเมื่อเริ่มต้น LazyColumn คอมโพสเฉพาะองค์ประกอบที่มองเห็น + บัฟเฟอร์ (ค่าเริ่มต้น 1 หน้าจอไปข้างหน้า/ข้างหลัง) ทำให้จำเป็นสำหรับรายการยาว

items API: การผูกข้อมูลกับรายการ

items API คือชุดของฟังก์ชันส่วนขยายสำหรับ LazyColumn ที่รับรายการข้อมูลและตัวอธิบาย DSL สำหรับแต่ละองค์ประกอบ ฟังก์ชันหลักได้แก่: items(count, key, itemContent) สำหรับจำนวนคงที่, items(list, key, itemContent) สำหรับรายการ, itemsIndexed(list, key, itemContent) พร้อมดัชนี พารามิเตอร์ key จำเป็นสำหรับประสิทธิภาพ — มันให้ตัวระบุองค์ประกอบที่เสถียรแก่ Compose

kotlin
// LazyColumn พื้นฐานกับ itemContent
@Composable
fun ArticleList(articles: List<Article>) {
    LazyColumn(
        modifier = Modifier.fillMaxSize(),
        contentPadding = PaddingValues(16.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp)
    ) {
        // Header — ไม่ขี้เกียจ, อยู่ที่จุดเริ่มต้นเสมอ
        item {
            Text("บทความล่าสุด", style = MaterialTheme.typography.headlineMedium)
        }

        // รายการบทความพร้อมคีย์ตาม id
        items(articles, key = { it.id }) { article ->
            ArticleCard(
                title = article.title,
                summary = article.summary,
                onClick = { onArticleClick(article.id) }
            )
        }

        // Footer พร้อมตัวบ่งชี้การโหลด
        item {
            CircularProgressIndicator(
                modifier = Modifier.fillMaxWidth().padding(16.dp)
            )
        }
    }
}

// องค์ประกอบรายการที่กำหนดเอง
@Composable
fun ArticleCard(title: String, summary: String, onClick: () -> Unit) {
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .clickable(onClick = onClick)
    ) {
        Column(modifier = Modifier.padding(12.dp)) {
            Text(text = title, style = MaterialTheme.typography.titleMedium)
            Spacer(modifier = Modifier.height(4.dp))
            Text(text = summary, style = MaterialTheme.typography.bodyMedium)
        }
    }
}

item {} เทียบกับ items(): ใช้ item {} สำหรับองค์ประกอบเดี่ยวที่อยู่ในรายการเสมอ (หัวเรื่อง, ส่วนท้าย, ตัวคั่น) ใช้ items() สำหรับรายการข้อมูลแบบไดนามิก items() รับ Iterable หรือ Int และบล็อก DSL ที่ถูกเรียกสำหรับแต่ละองค์ประกอบ รวม item และ items ใน LazyColumn เดียวกัน: ลำดับการเรียกกำหนดลำดับการแสดงผล

LazyListState: การจัดการตำแหน่งการเลื่อน

LazyListState คือออบเจกต์ที่เก็บสถานะของรายการ: ตำแหน่งการเลื่อนปัจจุบัน (firstVisibleItemIndex, firstVisibleItemScrollOffset), องค์ประกอบที่มองเห็น (layoutInfo), และวิธีการเลื่อนแบบโปรแกรม (scrollToItem, animateScrollToItem) LazyListState ถูกสร้างผ่าน rememberLazyListState() และส่งต่อไปยัง LazyColumn ผ่านพารามิเตอร์ state

kotlin
// LazyColumn พร้อมรักษาตำแหน่งการเลื่อน
@Composable
fun ScrollingList(items: List<Item>) {
    val listState = rememberLazyListState()

    Box(modifier = Modifier.fillMaxSize()) {
        LazyColumn(state = listState) {
            items(items, key = { it.id }) { item ->
                ListItemView(item = item)
            }
        }

        // ปุ่ม "ขึ้น" ปรากฏหลังจากองค์ประกอบที่ 10
        val showButton by remember {
            derivedStateOf { listState.firstVisibleItemIndex > 10 }
        }

        AnimatedVisibility(visible = showButton) {
            FloatingActionButton(
                modifier = Modifier
                    .align(Alignment.BottomEnd)
                    .padding(16.dp),
                onClick = {
                    coroutineScope.launch {
                        listState.animateScrollToItem(0)
                    }
                }
            ) {
                Icon(Icons.Default.KeyboardArrowUp, "กลับไปด้านบน")
            }
        }
    }
}

// การรับข้อมูลองค์ประกอบที่มองเห็น
@Composable
fun ListDebugInfo(listState: LazyListState) {
    val visibleItems = listState.layoutInfo.visibleItemsInfo
    val totalItems = listState.layoutInfo.totalItemsCount
    val firstItem = visibleItems.firstOrNull()?.index
    val lastItem = visibleItems.lastOrNull()?.index

    Text("กำลังแสดง $firstItem–$lastItem จาก $totalItems")
}

การเลื่อนแบบโปรแกรม ดำเนินการผ่านฟังก์ชัน suspend ของ listState: scrollToItem(index, scrollOffset) — เคลื่อนที่ทันที (ไม่มีแอนิเมชัน), animateScrollToItem(index) — พร้อมแอนิเมชัน สำหรับการเลื่อนจาก coroutine ให้ใช้ LaunchedEffect หรือ coroutineScope.launch สำคัญ: ห้ามเรียกฟังก์ชัน suspend จากคอมโพซิชัน (ภายใน @Composable) — ให้ใช้แลมบ์ดา (onClick, LaunchedEffect)

LazyColumn เทียบกับ RecyclerView: การเปรียบเทียบแนวทาง

LazyColumn และ RecyclerView แก้ปัญหาเดียวกัน — การแสดงรายการยาวอย่างมีประสิทธิภาพ ความแตกต่างอยู่ในสถาปัตยกรรม: RecyclerView ใช้ ViewHolder และ Adapter (Android View แบบคลาสสิก), LazyColumn ใช้ฟังก์ชัน composable แบบประกาศโดยไม่ใช้ XML หรือ ViewHolder การเลือกขึ้นอยู่กับสแต็กเทคโนโลยีของโปรเจกต์: Compose เทียบกับ UI แบบ View

พารามิเตอร์LazyColumn (Compose)RecyclerView (View)
เลย์เอาต์ฟังก์ชัน Composable (Kotlin DSL)ไฟล์ XML + ViewBinding
อะแดปเตอร์items() / itemsIndexed() DSLRecyclerView.Adapter + ViewHolder
การเรียง/การกรองSnapshot state + derivedStateOfDiffUtil + AsyncListDiffer
แอนิเมชันAnimatedVisibility + Modifier.animateItemDefaultItemAnimator
ประเภทองค์ประกอบที่แตกต่างitem {}/items() + when ตามประเภทgetItemViewType + หลาย ViewHolders
เลื่อนไปยังตำแหน่งlistState.animateScrollToItem()layoutManager.scrollToPosition()
Prefetchในกระบวนการ (บัฟเฟอร์ SubcomposeLayout)RecycledViewPool + GapWorker
เครือข่าย/การแบ่งหน้าcollectAsLazyPagingItems() + Paging 3PagingDataAdapter + Paging 3

คำแนะนำของ Google (Android Developers, 2026): สำหรับโปรเจกต์ใหม่บน Jetpack Compose ให้ใช้ LazyColumn สำหรับโปรเจกต์แบบ View ให้ใช้ RecyclerView LazyColumn ต้องใช้ Compose ซึ่งเพิ่มประมาณ 3–5 MB ใน APK — พิจารณาเรื่องนี้เมื่อรองรับอุปกรณ์เก่า หากโปรเจกต์ใช้ระบบ View อยู่แล้ว LazyColumn ไม่สามารถอยู่ร่วมในหน้าจอเดียวกับเลย์เอาต์ XML โดยไม่มี AndroidView — ในกรณีเช่นนี้ ควรใช้ RecyclerView ต่อไปจะง่ายกว่า

LazyVerticalGrid และ LazyHorizontalGrid: กริด

LazyVerticalGrid คือเวอร์ชันของ LazyColumn สำหรับกริดที่มีจำนวนคอลัมน์คงที่ LazyHorizontalGrid คือกริดแนวนอนที่มีแถวคงที่ ทั้งสองคอมโพเนนต์ใช้หลักการโหลดขี้เกียจเดียวกันและ items API เดียวกับ LazyColumn พารามิเตอร์ columns กำหนดจำนวนคอลัมน์ผ่าน GridCells.Fixed(N) หรือ GridCells.Adaptive(minSize)

kotlin
// กริดพร้อมคอลัมน์แบบปรับได้
@Composable
fun PhotoGrid(photos: List<Photo>) {
    LazyVerticalGrid(
        columns = GridCells.Adaptive(minSize = 128.dp),
        contentPadding = PaddingValues(8.dp),
        horizontalArrangement = Arrangement.spacedBy(8.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp)
    ) {
        items(photos, key = { it.id }) { photo ->
            AsyncImage(
                model = photo.url,
                contentDescription = photo.title,
                modifier = Modifier.aspectRatio(1f)
            )
        }
    }
}

// รายการแนวนอน (LazyRow)
@Composable
fun CategoryCarousel(categories: List<Category>) {
    LazyRow(
        contentPadding = PaddingValues(horizontal = 16.dp),
        horizontalArrangement = Arrangement.spacedBy(12.dp)
    ) {
        items(categories, key = { it.id }) { category ->
            FilterChip(
                selected = category.isSelected,
                onClick = { onCategoryClick(category.id) },
                label = { Text(category.name) }
            )
        }
    }
}

GridCells.Adaptive คำนวณจำนวนคอลัมน์โดยอัตโนมัติตามขนาดเซลล์ขั้นต่ำ ตัวอย่างเช่น GridCells.Adaptive(128.dp) บนหน้าจอกว้าง 360 dp วาง 2 คอลัมน์ บนแท็บเล็ต 600 dp — 4 คอลัมน์ GridCells.Fixed(2) แสดง 2 คอลัมน์เสมอ Adaptive เหมาะกว่าสำหรับการปรับหลายหน้าจอ — มันปรับตามความกว้างของหน้าจอโดยอัตโนมัติโดยไม่ต้องมี media query

ประสิทธิภาพ LazyColumn: key, contentType, prefetch

ประสิทธิภาพ ของ LazyColumn ขึ้นอยู่กับสามปัจจัย: ความเสถียรของคีย์ (key), ประเภทเนื้อหา (contentType), และบัฟเฟอร์ prefetch (beyondBounds) โดยค่าเริ่มต้น LazyColumn จะบัฟเฟอร์ 1 หน้าจอไปข้างหน้าและ 1 หน้าจอข้างหลัง Google (Android Developers, 2026) แนะนำให้กำหนดค่าพารามิเตอร์เหล่านี้สำหรับรายการที่มีประเภทองค์ประกอบแตกต่างกันหรือคอมโพซิชันที่ซับซ้อน

พารามิเตอร์คำอธิบายคำแนะนำ
keyตัวระบุองค์ประกอบที่เสถียรระบุ key = { it.id } เสมอ หากไม่มีคีย์ Compose จะใช้ดัชนี และการเรียงลำดับองค์ประกอบใหม่จะทำให้แอนิเมชันเสีย
contentTypeประเภทเนื้อหาสำหรับการแยกพูลระบุสำหรับรายการที่มีประเภทองค์ประกอบแตกต่างกัน (ข้อความ + รูปภาพ + โฆษณา) Compose จะนำคอมโพซิชันกลับมาใช้ใหม่เฉพาะภายใน contentType เดียวกัน
beyondBoundsจำนวนหน้าจอ prefetchเพิ่มเป็น 2–3 สำหรับรายการที่มีรูปภาพหนัก ค่าเริ่มต้นคือ 1 หน้าจอ
Modifier.animateItemแอนิเมชันการเปลี่ยนตำแหน่งใช้สำหรับการเรียงลำดับและการกรองแบบมีแอนิเมชัน (Compose 1.7+)
kotlin
// LazyColumn ที่ปรับแต่งด้วย contentType และ prefetch
@Composable
fun OptimizedFeed(items: List<FeedItem>) {
    val listState = rememberLazyListState()

    LazyColumn(
        state = listState,
        modifier = Modifier.fillMaxSize(),
        beyondBoundsPageCount = 2  // prefetch 2 หน้าจอ
    ) {
        items(
            items = items,
            key = { it.id },
            contentType = { it.type }  // การแบ่งตามประเภท
        ) { item ->
            when (item) {
                is FeedItem.Post -> PostView(item)
                is FeedItem.Ad -> AdView(item)
                is FeedItem.Suggested -> SuggestedView(item)
            }
        }
    }
}

// การวัดประสิทธิภาพผ่าน listState
@Composable
fun ScrollPerformance(listState: LazyListState) {
    val scrollInfo = listState.layoutInfo
    val visibleCount = scrollInfo.visibleItemsInfo.size
    val total = scrollInfo.totalItemsCount

    // derivedStateOf — ไม่คอมโพสใหม่หากค่าไม่เปลี่ยนแปลง
    val scrollProgress by remember {
        derivedStateOf {
            if (total > 0) visibleCount.toFloat() / total else 0f
        }
    }

    LinearProgressIndicator(
        progress = scrollProgress,
        modifier = Modifier.fillMaxWidth().height(2.dp)
    )
}

ข้อผิดพลาดทั่วไป: (1) ไม่มี key — องค์ประกอบจะถูกคอมโพสใหม่เมื่อมีการเปลี่ยนแปลงรายการใดๆ (2) ใช้ mutableStateListOf โดยไม่มี snapshot flows — การเปลี่ยนแปลงอาจไม่ถูกติดตาม (3) การเรียกฟังก์ชัน suspend ภายใน itemContent — ขัดจังหวะคอมโพซิชัน (4) ไม่มี contentType สำหรับประเภทผสม — Compose นำคอมโพซิชันของโฆษณามาใช้สำหรับโพสต์ข้อความ ทำให้เกิดสิ่งแปลกปลอมทางภาพ ที่ IT Sectr เราเพิ่ม contentType ในทุกรายการที่มีเนื้อหาสามประเภทขึ้นไป

คำถามที่พบบ่อย

LazyColumn เลื่อนช้า — ต้องทำอย่างไร?

ตรวจสอบสามพารามิเตอร์: (1) key — หากไม่มีคีย์ที่เสถียร Compose ไม่สามารถนำคอมโพซิชันกลับมาใช้ใหม่ได้ (2) contentType — สำหรับรายการที่มีประเภทองค์ประกอบแตกต่างกัน (3) beyondBoundsPageCount — เพิ่มเป็น 2 สำหรับรูปภาพ ย้ายการคำนวณหนักภายใน itemContent ไปยัง remember พร้อมคีย์ตามข้อมูล ใช้ Modifier.drawWithContent แทน Image สำหรับกราฟิกง่ายๆ

จะเพิ่มตัวคั่นระหว่างองค์ประกอบ LazyColumn ได้อย่างไร?

ใช้ LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) สำหรับระยะห่างระหว่างองค์ประกอบ สำหรับตัวคั่นภาพที่มีเส้น ให้เพิ่ม item { Divider() } ระหว่าง items ตัวคั่นอัตโนมัติ: items(items, key = { it.id }) { item -> ... } และระหว่างแต่ละองค์ประกอบ ให้แทรก item { HorizontalDivider() } สำหรับการนี้ ให้ใช้ LazyListScope.items(list, key) { /* องค์ประกอบ */ } และแยก LazyListScope.item { Divider() }

LazyColumn ภายใน Column ไม่ทำงาน — ทำไม?

LazyColumn มีความสูงไม่จำกัด (fillMaxSize) โดยค่าเริ่มต้น ภายใน Column ที่ไม่มีความสูงคงที่ LazyColumn ไม่สามารถกำหนดขนาดของมันได้ วิธีแก้ไข: (1) กำหนดความสูงคงที่ให้ LazyColumn: Modifier.height(400.dp), (2) ใช้ Modifier.weight(1f) ภายใน Column, (3) อย่าวาง LazyColumn ซ้อนภายในคอนเทนเนอร์ที่เลื่อนแนวตั้งได้ — ใช้ LazyColumn เป็นองค์ประกอบรากพร้อม item {} สำหรับหัวเรื่องและส่วนท้าย

จะอัปเดต LazyColumn เมื่อข้อมูลเปลี่ยนแปลงได้อย่างไร?

LazyColumn ตอบสนองต่อการเปลี่ยนแปลง State โดยอัตโนมัติ หากข้อมูลถูกเก็บใน mutableStateListOf หรือ mutableStateOf Compose จะคอมโพสเฉพาะองค์ประกอบที่เปลี่ยนแปลง สำหรับรายการจาก ViewModel ให้ใช้ collectAsState() กับ Flow เมื่ออัปเดตรายการด้วย key Compose จะสร้างแอนิเมชันการเปลี่ยนแปลง (การเพิ่ม, การลบ, การเรียงลำดับใหม่) โดยอัตโนมัติผ่าน Modifier.animateItemPlacement()

LazyColumn เทียบกับ LazyVerticalGrid — เลือกอะไร?

LazyColumn — สำหรับรายการคอลัมน์เดียว (ฟีด, แชท, ความคิดเห็น) LazyVerticalGrid — สำหรับกริด (แกลเลอรี, แคตตาล็อก, ไอคอน) หากองค์ประกอบควรอยู่ในหนึ่งคอลัมน์บนโทรศัพท์และสองคอลัมน์บนแท็บเล็ต — ใช้ LazyVerticalGrid กับ GridCells.Adaptive หากต้องการกริดแบบผสม (จำนวนคอลัมน์ต่างกันในส่วนเดียว) — ใช้ LazyColumn และแทรก LazyRow แนวนอนหรือ LazyVerticalGrid ที่ซ้อนอยู่ภายใน items

สรุป

  • LazyColumn — คอมโพเนนต์ Jetpack Compose สำหรับรายการแนวตั้งแบบขี้เกียจพร้อม API แบบประกาศ
  • items() API รับรายการและบล็อก DSL สำหรับแต่ละองค์ประกอบ; key จำเป็นสำหรับประสิทธิภาพ
  • LazyListState จัดการตำแหน่งการเลื่อน ให้ฟังก์ชัน suspend สำหรับการเลื่อนแบบโปรแกรม
  • contentType แยกพูลคอมโพซิชันสำหรับรายการที่มีประเภทองค์ประกอบแตกต่างกัน
  • LazyVerticalGrid / LazyRow — รูปแบบต่างๆ สำหรับกริดและรายการแนวนอนด้วย items API เดียวกัน
  • LazyColumn รักษา 60 FPS สำหรับ 10000+ องค์ประกอบด้วย SubcomposeLayout และบัฟเฟอร์ prefetch
  • สำหรับโปรเจกต์ Compose ใหม่ใช้ LazyColumn; สำหรับโปรเจกต์แบบ View — RecyclerView

เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร

IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ

ปรึกษาโครงการ

อ่านเพิ่มเติม