Compose tidak memakai XML layout, namun menyediakan sekumpulan composable layout yang mengatur posisi children. Column menyusun children vertikal, Row horizontal, Box menumpuk. Untuk daftar panjang yang perlu scroll, LazyColumn dan LazyRow menggantikan RecyclerView dengan kode jauh lebih ringkas. Bab ini memetakan perilaku setiap layout, mengenal LazyVerticalGrid untuk tampilan grid, dan menunjukkan bagaimana Modifier dirantai untuk padding, spacing, dan ukuran.
Column, Row, Box #
Tiga layout dasar Compose memiliki perilaku yang konsisten: children disusun sesuai sumbu utama, dan alignment dapat diatur lewat parameter verticalArrangement / horizontalArrangement untuk sumbu utama, serta horizontalAlignment / verticalAlignment untuk sumbu silang.
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Baris 1")
Text("Baris 2")
Text("Baris 3")
}Arrangement.spacedBy(8.dp) menambah jarak 8 dp antar children tanpa harus mengatur padding manual di tiap child. Parameter serupa tersedia untuk Row (horizontalArrangement) dan Box (contentAlignment).
Lazy Layouts #
Untuk daftar panjang (ratusan hingga ribuan item), Column dan Row tidak efisien karena semua child di-render sekaligus. LazyColumn dan LazyRow hanya membuat composable yang terlihat di viewport. Pola yang dipakai disebut lazy karena item dibuat on-demand saat pengguna menggulir.
@Composable
fun DaftarTugas(tugasList: List<String>) {
LazyColumn(
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(tugasList) { tugas ->
Text(tugas)
}
}
}Fungsi items() adalah extension pada LazyListScope yang menerima List<T> dan lambda untuk setiap item. Untuk daftar dengan tipe berbeda, kita dapat memakai beberapa blok item { }, items(), dan itemsIndexed() dalam satu LazyColumn.
LazyVerticalGrid #
Grid berguna untuk galeri foto, katalog produk, atau dashboard ikon. LazyVerticalGrid menyusun children dalam jumlah kolom yang ditentukan:
LazyVerticalGrid(
columns = GridCells.Fixed(3),
contentPadding = PaddingValues(8.dp),
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
items(fotoList) { foto -> Image(painter = painterResource(foto), contentDescription = null) }
}Selain GridCells.Fixed(n), tersedia GridCells.Adaptive(minSize) yang menentukan jumlah kolom otomatis berdasarkan lebar minimum per cell — berguna untuk layout responsif tablet.
Simulasi Layout Manager #
Cuplikan berikut memodelkan keluarga layout Compose sebagai sealed class yang mengembalikan posisi (Rect) setiap child. LazyColumn hanya menyertakan item yang masih dalam viewport:
// compose_layouts.kt
// Mensimulasikan Column, Row, Box, LazyColumn, LazyRow, dan LazyVerticalGrid
// sebagai node layout ringkas yang menghasilkan daftar item dengan posisi
// dan dimensi — tanpa memakai toolkit Compose sungguhan.
data class Rect(val x: Int, val y: Int, val w: Int, val h: Int)
sealed class ComposeLayout {
abstract fun measure(children: List<String>, spacing: Int = 0): List<Pair<String, Rect>>
}
class ColumnLayout : ComposeLayout() {
override fun measure(children: List<String>, spacing: Int): List<Pair<String, Rect>> {
var y = 0
return children.map { c ->
val rect = Rect(0, y, w = 200, h = 40)
y += rect.h + spacing
c to rect
}
}
}
class RowLayout : ComposeLayout() {
override fun measure(children: List<String>, spacing: Int): List<Pair<String, Rect>> {
var x = 0
return children.map { c ->
val rect = Rect(x, 0, w = 60, h = 40)
x += rect.w + spacing
c to rect
}
}
}
class BoxLayout : ComposeLayout() {
override fun measure(children: List<String>, spacing: Int): List<Pair<String, Rect>> {
// Semua child menumpuk pada origin
return children.map { c -> c to Rect(0, 0, 200, 40) }
}
}
// Lazy layout hanya membuat item yang terlihat di "viewport"
class LazyColumnLayout(val viewportHeight: Int) : ComposeLayout() {
override fun measure(children: List<String>, spacing: Int): List<Pair<String, Rect>> {
var y = 0
val result = mutableListOf<Pair<String, Rect>>()
for (c in children) {
val rect = Rect(0, y, 200, 40)
if (y < viewportHeight) result.add(c to rect) // hanya yang terlihat
y += rect.h + spacing
}
return result
}
}
class LazyVerticalGrid(val columns: Int, val viewportHeight: Int) : ComposeLayout() {
override fun measure(children: List<String>, spacing: Int): List<Pair<String, Rect>> {
val result = mutableListOf<Pair<String, Rect>>()
children.forEachIndexed { i, c ->
val col = i % columns
val row = i / columns
val y = row * (40 + spacing)
if (y < viewportHeight) {
val x = col * (60 + spacing)
result.add(c to Rect(x, y, 60, 40))
}
}
return result
}
}
fun main() {
val items = (1..6).map { "Item $it" }
println("Column:")
ColumnLayout().measure(items, spacing = 8).forEach { (n, r) -> println(" $n $r") }
println("\nRow:")
RowLayout().measure(items, spacing = 4).forEach { (n, r) -> println(" $n $r") }
println("\nLazyColumn (viewport=120, total 12 item):")
val many = (1..12).map { "Item $it" }
LazyColumnLayout(viewportHeight = 120).measure(many, spacing = 8).forEach { (n, r) ->
println(" $n $r")
}
println("\nLazyVerticalGrid 3 kolom, viewport=120:")
LazyVerticalGrid(columns = 3, viewportHeight = 120).measure(many, spacing = 4).forEach { (n, r) ->
println(" $n $r")
}
}Output yang Dihasilkan:
Column:
Item 1 Rect(x=0, y=0, w=200, h=40)
Item 2 Rect(x=0, y=48, w=200, h=40)
Item 3 Rect(x=0, y=96, w=200, h=40)
Item 4 Rect(x=0, y=144, w=200, h=40)
Item 5 Rect(x=0, y=192, w=200, h=40)
Item 6 Rect(x=0, y=240, w=200, h=40)
Row:
Item 1 Rect(x=0, y=0, w=60, h=40)
Item 2 Rect(x=64, y=0, w=60, h=40)
Item 3 Rect(x=128, y=0, w=60, h=40)
Item 4 Rect(x=192, y=0, w=60, h=40)
Item 5 Rect(x=256, y=0, w=60, h=40)
Item 6 Rect(x=320, y=0, w=60, h=40)
LazyColumn (viewport=120, total 12 item):
Item 1 Rect(x=0, y=0, w=200, h=40)
Item 2 Rect(x=0, y=48, w=200, h=40)
Item 3 Rect(x=0, y=96, w=200, h=40)
LazyVerticalGrid 3 kolom, viewport=120:
Item 1 Rect(x=0, y=0, w=60, h=40)
Item 2 Rect(x=64, y=0, w=60, h=40)
Item 3 Rect(x=128, y=0, w=60, h=40)
Item 4 Rect(x=0, y=44, w=60, h=40)
Item 5 Rect(x=64, y=44, w=60, h=40)
Item 6 Rect(x=128, y=44, w=60, h=40)
Item 7 Rect(x=0, y=88, w=60, h=40)
Item 8 Rect(x=64, y=88, w=60, h=40)
Item 9 Rect(x=128, y=88, w=60, h=40)Perhatikan bahwa LazyColumn dengan viewport tinggi 120 hanya me-render 3 item pertama dari 12. Hal inilah yang membuat LazyColumn hemat memori untuk daftar panjang.
Rantai Modifier #
Modifier adalah cara Compose mengatur dekorasi dan perilaku composable. Modifier dirantai dengan titik; urutan menentukan hasil akhir karena setiap modifier membungkus hasil modifier sebelumnya.
Box(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.background(Color.Gray)
.clip(RoundedCornerShape(8.dp))
) {
Text("Halo")
}| Modifier | Efek |
|---|---|
padding(n.dp) | Jarak dari tepi elemen ke tetangga |
fillMaxWidth() / fillMaxHeight() | Memenuhi sumbu yang ditentukan |
background(color) | Warna latar belakang |
clip(shape) | Memotong konten ke bentuk tertentu |
clickable { } | Menambahkan respons klik |
Referensi Lanjutan #
- Jetpack Compose
— rujukan Jetpack Compose untuk layout
Column,Row,Box, danLazyList
Selanjutnya #
Setelah layout Compose terbentuk, kita perlu menghubungkan satu screen ke screen lain. Chapter Compose Navigation akan membahas NavHost, composable("route"), dan bottom navigation bar.