Komponen Material dan tata letak

Jetpack Compose menawarkan implementasi Desain Material, yaitu sistem desain komprehensif untuk membuat antarmuka digital. Komponen Material (tombol, kartu, tombol akses, dll.) dan tata letak seperti Scaffold tersedia sebagai fungsi yang dapat dikomposisi.

Komponen Material adalah elemen penyusun interaktif untuk membuat antarmuka pengguna. Compose menawarkan sejumlah komponen yang bisa langsung digunakan. Untuk melihat komponen yang tersedia, lihat referensi API Compose Material.

Komponen Material menggunakan nilai yang diberikan oleh MaterialTheme di aplikasi Anda:

@Composable
fun MyApp() {
    MaterialTheme {
        // Material Components like Button, Card, Switch, etc.
    }
}

Untuk mempelajari tema lebih lanjut, lihat panduan Tema dalam Compose.

Slot konten

Komponen Material yang mendukung konten internal (label teks, ikon, dll.) cenderung menawarkan “slot” — lambda generik yang menerima konten composable — serta konstanta publik, seperti ukuran dan padding, guna mendukung tata letak konten internal agar sesuai dengan spesifikasi Material.

Contohnya adalah Button:

Button(
    onClick = { /* ... */ },
    // Uses ButtonDefaults.ContentPadding by default
    contentPadding = PaddingValues(
        start = 20.dp,
        top = 12.dp,
        end = 20.dp,
        bottom = 12.dp
    )
) {
    // Inner content including an icon and a text label
    Icon(
        Icons.Filled.Favorite,
        contentDescription = "Favorite",
        modifier = Modifier.size(ButtonDefaults.IconSize)
    )
    Spacer(Modifier.size(ButtonDefaults.IconSpacing))
    Text("Like")
}

Gambar 1. Button menggunakan slot content dan padding default (kiri), lalu Button menggunakan slot content yang menyediakan contentPadding kustom (kanan).

Button memiliki slot lambda akhir content generik, yang menggunakan RowScope untuk membuat tata letak composable konten dalam satu baris. Ini juga memiliki parameter contentPadding untuk menerapkan padding ke konten internal. Anda dapat menggunakan konstanta yang disediakan melalui ButtonDefaults, atau nilai kustom.

Contoh lainnya adalah ExtendedFloatingActionButton:

ExtendedFloatingActionButton(
    onClick = { /* ... */ },
    icon = {
        Icon(
            Icons.Filled.Favorite,
            contentDescription = "Favorite"
        )
    },
    text = { Text("Like") }
)

Gambar 2. ExtendedFloatingActionButton menggunakan slot icon dan text.

ExtendedFloatingActionButton memiliki dua slot untuk icon dan label text, bukan lambda content generik. Meskipun setiap slot mendukung konten composable generik, komponen tersebut tidak fleksibel dalam menerapkan setiap konten internal. Komponen tersebut menangani padding, perataan, dan ukuran secara internal.

Scaffold

Compose menyediakan tata letak yang mudah untuk menggabungkan Komponen Material ke dalam pola layar yang umum. Composable seperti Scaffold menyediakan slot untuk berbagai komponen dan elemen layar lainnya.

Konten layar

Scaffold memiliki slot lambda akhir content generik. Lambda menerima instance PaddingValues yang harus diterapkan ke root konten — misalnya, melalui Modifier.padding — untuk mengimbangi bagian atas dan bawah panel, jika ada.

Scaffold(/* ... */) { contentPadding ->
    // Screen content
    Box(modifier = Modifier.padding(contentPadding)) { /* ... */ }
}

Panel aplikasi

Scaffold menyediakan slot untuk panel aplikasi atas atau panel aplikasi bawah. Penempatan composable ditangani secara internal.

Anda dapat menggunakan slot topBar dan TopAppBar:

Scaffold(
    topBar = {
        TopAppBar { /* Top app bar content */ }
    }
) {
    // Screen content
}

Anda dapat menggunakan slot bottomBar dan BottomAppBar:

Scaffold(
    bottomBar = {
        BottomAppBar { /* Bottom app bar content */ }
    }
) {
    // Screen content
}

Slot ini dapat digunakan untuk Komponen Material lain seperti BottomNavigation. Anda juga dapat menggunakan composable kustom — misalnya, lihat layar aktivasi dari contoh Owl.

Tombol tindakan mengambang

Scaffold menyediakan slot untuk tombol tindakan mengambang.

Anda dapat menggunakan slot floatingActionButton dan FloatingActionButton:

Scaffold(
    floatingActionButton = {
        FloatingActionButton(onClick = { /* ... */ }) {
            /* FAB content */
        }
    }
) {
    // Screen content
}

Penempatan composable FAB di bawah ditangani secara internal. Anda dapat menggunakan parameter floatingActionButtonPosition untuk menyesuaikan posisi horizontal:

Scaffold(
    floatingActionButton = {
        FloatingActionButton(onClick = { /* ... */ }) {
            /* FAB content */
        }
    },
    // Defaults to FabPosition.End
    floatingActionButtonPosition = FabPosition.Center
) {
    // Screen content
}

Jika Anda menggunakan slot bottomBar composable Scaffold, Anda dapat menggunakan parameter isFloatingActionButtonDocked untuk menimpa FAB dengan panel aplikasi bawah:

Scaffold(
    floatingActionButton = {
        FloatingActionButton(onClick = { /* ... */ }) {
            /* FAB content */
        }
    },
    // Defaults to false
    isFloatingActionButtonDocked = true,
    bottomBar = {
        BottomAppBar { /* Bottom app bar content */ }
    }
) {
    // Screen content
}

Gambar 3. Scaffold menggunakan slot floatingActionButton dan slot bottomBar. Parameter isFloatingActionButtonDocked disetel ke false (atas) dan true (bawah).

BottomAppBar mendukung potongan FAB dengan parameter cutoutShape, yang menerima semua Shape. Sebaiknya berikan Shape yang sama dengan yang digunakan oleh komponen yang dikaitkan. Misalnya, FloatingActionButton menggunakan MaterialTheme.shapes.small dengan ukuran sudut 50 persen sebagai nilai default untuk parameter shape-nya:

Scaffold(
    floatingActionButton = {
        FloatingActionButton(onClick = { /* ... */ }) {
            /* FAB content */
        }
    },
    isFloatingActionButtonDocked = true,
    bottomBar = {
        BottomAppBar(
            // Defaults to null, that is, No cutout
            cutoutShape = MaterialTheme.shapes.small.copy(
                CornerSize(percent = 50)
            )
        ) {
            /* Bottom app bar content */
        }
    }
) {
  // Screen content
}

Gambar 4. Scaffold dengan BottomAppBar dan FloatingActionButton yang dikaitkan. BottomAppBar memiliki cutoutShape kustom yang cocok dengan Shape yang digunakan oleh FloatingActionButton.

Snackbar

Scaffold menyediakan cara untuk menampilkan snackbar.

Snackbar disediakan melalui ScaffoldState, yang mencakup properti SnackbarHostState. Anda dapat menggunakan rememberScaffoldState untuk membuat instance ScaffoldState yang harus diteruskan ke Scaffold dengan parameter scaffoldState. SnackbarHostState memberikan akses ke fungsi showSnackbar. Fungsi penangguhan ini memerlukan CoroutineScope — misalnya, menggunakan rememberCoroutineScope — dan dapat dipanggil sebagai respons terhadap peristiwa UI untuk menampilkan Snackbar dalam Scaffold.

val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
Scaffold(
    scaffoldState = scaffoldState,
    floatingActionButton = {
        ExtendedFloatingActionButton(
            text = { Text("Show snackbar") },
            onClick = {
                scope.launch {
                    scaffoldState.snackbarHostState
                        .showSnackbar("Snackbar")
                }
            }
        )
    }
) {
    // Screen content
}

Anda dapat memberikan tindakan opsional dan menyesuaikan durasi Snackbar. Fungsi snackbarHostState.showSnackbar menerima parameter actionLabel dan duration tambahan, serta menampilkan SnackbarResult.

val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
Scaffold(
    scaffoldState = scaffoldState,
    floatingActionButton = {
        ExtendedFloatingActionButton(
            text = { Text("Show snackbar") },
            onClick = {
                scope.launch {
                    val result = scaffoldState.snackbarHostState
                        .showSnackbar(
                            message = "Snackbar",
                            actionLabel = "Action",
                            // Defaults to SnackbarDuration.Short
                            duration = SnackbarDuration.Indefinite
                        )
                    when (result) {
                        SnackbarResult.ActionPerformed -> {
                            /* Handle snackbar action performed */
                        }
                        SnackbarResult.Dismissed -> {
                            /* Handle snackbar dismissed */
                        }
                    }
                }
            }
        )
    }
) {
    // Screen content
}

Anda dapat memberikan Snackbar kustom dengan parameter snackbarHost. Lihat SnackbarHost API reference docs untuk informasi selengkapnya.

Panel samping

Scaffold menyediakan slot untuk panel navigasi modal. Sheet dan tata letak yang dapat ditarik dari composable tersebut ditangani secara internal.

Anda dapat menggunakan slot drawerContent, yang menggunakan ColumnScope untuk membuat tata letak composable konten panel samping dalam kolom:

Scaffold(
    drawerContent = {
        Text("Drawer title", modifier = Modifier.padding(16.dp))
        Divider()
        // Drawer items
    }
) {
    // Screen content
}

Scaffold menerima sejumlah parameter panel samping tambahan. Misalnya, Anda dapat beralih apakah panel samping akan merespons tarik dengan parameter drawerGesturesEnabled:

Scaffold(
    drawerContent = {
        // Drawer content
    },
    // Defaults to true
    drawerGesturesEnabled = false
) {
    // Screen content
}

Membuka dan menutup panel samping secara terprogram dilakukan melalui ScaffoldState, yang mencakup properti DrawerState yang harus diteruskan ke Scaffold dengan parameter scaffoldState. DrawerState memberikan akses ke fungsi open dan close, serta properti yang terkait dengan status panel samping saat ini. Fungsi penangguhan ini memerlukan CoroutineScope — misalnya, menggunakan rememberCoroutineScope — dan dapat dipanggil sebagai respons terhadap peristiwa UI.

val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
Scaffold(
    scaffoldState = scaffoldState,
    drawerContent = {
        // Drawer content
    },
    floatingActionButton = {
        ExtendedFloatingActionButton(
            text = { Text("Open or close drawer") },
            onClick = {
                scope.launch {
                    scaffoldState.drawerState.apply {
                        if (isClosed) open() else close()
                    }
                }
            }
        )
    }
) {
    // Screen content
}

Jika ingin mengimplementasikan panel navigasi modal tanpa Scaffold, Anda dapat menggunakan composable ModalDrawer. Kode ini menerima parameter panel samping yang serupa dengan Scaffold.

val drawerState = rememberDrawerState(DrawerValue.Closed)
ModalDrawer(
    drawerState = drawerState,
    drawerContent = {
        // Drawer content
    }
) {
    // Screen content
}

Jika ingin mengimplementasikan panel navigasi bawah, Anda dapat menggunakan composable BottomDrawer:

val drawerState = rememberBottomDrawerState(BottomDrawerValue.Closed)
BottomDrawer(
    drawerState = drawerState,
    drawerContent = {
        // Drawer content
    }
) {
    // Screen content
}

Sheet bawah

Jika ingin mengimplementasikan sheet bawah standar, Anda dapat menggunakan composable BottomSheetScaffold. Composable ini menerima parameter serupa dengan Scaffold, seperti topBar, floatingActionButton, dan snackbarHost. Composable ini menyertakan parameter tambahan yang memberikan cara untuk menampilkan sheet bawah.

Anda dapat menggunakan slot sheetContent, yang menggunakan ColumnScope untuk membuat tata letak composable konten sheet dalam kolom:

BottomSheetScaffold(
    sheetContent = {
        // Sheet content
    }
) {
    // Screen content
}

BottomSheetScaffold menerima sejumlah parameter sheet tambahan. Misalnya, Anda dapat menyetel tinggi tampilan sheet dengan parameter sheetPeekHeight. Anda juga dapat beralih apakah panel samping merespons tarik dengan parameter sheetGesturesEnabled.

BottomSheetScaffold(
    sheetContent = {
        // Sheet content
    },
    // Defaults to BottomSheetScaffoldDefaults.SheetPeekHeight
    sheetPeekHeight = 128.dp,
    // Defaults to true
    sheetGesturesEnabled = false

) {
    // Screen content
}

Meluaskan dan menciutkan sheet secara terprogram dilakukan melalui BottomSheetScaffoldState, yang menyertakan properti BottomSheetState. Anda dapat menggunakan rememberBottomSheetScaffoldState untuk membuat instance BottomSheetScaffoldState yang harus diteruskan ke BottomSheetScaffold dengan parameter scaffoldState. BottomSheetState memberikan akses ke fungsi expand dan collapse, serta properti yang terkait dengan status sheet saat ini. Fungsi penangguhan ini memerlukan CoroutineScope — misalnya, menggunakan rememberCoroutineScope — dan dapat dipanggil sebagai respons terhadap peristiwa UI.

val scaffoldState = rememberBottomSheetScaffoldState()
val scope = rememberCoroutineScope()
BottomSheetScaffold(
    scaffoldState = scaffoldState,
    sheetContent = {
        // Sheet content
    },
    floatingActionButton = {
        ExtendedFloatingActionButton(
            text = { Text("Expand or collapse sheet") },
            onClick = {
                scope.launch {
                    scaffoldState.bottomSheetState.apply {
                        if (isCollapsed) expand() else collapse()
                    }
                }
            }
        )
    }
) {
    // Screen content
}

Jika ingin mengimplementasikan sheet bawah modal, Anda dapat menggunakan composable ModalBottomSheetLayout:

val sheetState = rememberModalBottomSheetState(
    ModalBottomSheetValue.Hidden
)
ModalBottomSheetLayout(
    sheetState = sheetState,
    sheetContent = {
        // Sheet content
    }
) {
    // Screen content
}

Tampilan latar

Jika ingin mengimplementasikan tampilan latar, Anda dapat menggunakan composable BackdropScaffold.

BackdropScaffold(
    appBar = {
        // Top app bar
    },
    backLayerContent = {
        // Back layer content
    },
    frontLayerContent = {
        // Front layer content
    }
)

BackdropScaffold menerima sejumlah parameter tampilan latar tambahan. Misalnya, Anda dapat menyetel tinggi tampilan lapisan belakang dan tinggi minimum tidak aktif lapisan depan dengan parameter peekHeight dan headerHeight. Anda juga dapat beralih apakah tampilan latar merespons tarik dengan parameter gesturesEnabled.

BackdropScaffold(
    appBar = {
        // Top app bar
    },
    backLayerContent = {
        // Back layer content
    },
    frontLayerContent = {
        // Front layer content
    },
    // Defaults to BackdropScaffoldDefaults.PeekHeight
    peekHeight = 40.dp,
    // Defaults to BackdropScaffoldDefaults.HeaderHeight
    headerHeight = 60.dp,
    // Defaults to true
    gesturesEnabled = false
)

Menampilkan dan menyembunyikan tampilan latar secara terprogram dilakukan melalui BackdropScaffoldState. Anda dapat menggunakan rememberBackdropScaffoldState untuk membuat instance BackdropScaffoldState yang harus diteruskan ke BackdropScaffold dengan parameter scaffoldState. BackdropScaffoldState memberikan akses ke fungsi reveal dan conceal, serta properti yang terkait dengan status tampilan latar saat ini. Fungsi penangguhan ini memerlukan CoroutineScope — misalnya, menggunakan rememberCoroutineScope — dan dapat dipanggil sebagai respons terhadap peristiwa UI.

val scaffoldState = rememberBackdropScaffoldState(
    BackdropValue.Concealed
)
val scope = rememberCoroutineScope()
BackdropScaffold(
    scaffoldState = scaffoldState,
    appBar = {
        TopAppBar(
            title = { Text("Backdrop") },
            navigationIcon = {
                if (scaffoldState.isConcealed) {
                    IconButton(
                        onClick = {
                            scope.launch { scaffoldState.reveal() }
                        }
                    ) {
                        Icon(
                            Icons.Default.Menu,
                            contentDescription = "Menu"
                        )
                    }
                } else {
                    IconButton(
                        onClick = {
                            scope.launch { scaffoldState.conceal() }
                        }
                    ) {
                        Icon(
                            Icons.Default.Close,
                            contentDescription = "Close"
                        )
                    }
                }
            },
            elevation = 0.dp,
            backgroundColor = Color.Transparent
        )
    },
    backLayerContent = {
        // Back layer content
    },
    frontLayerContent = {
        // Front layer content
    }
)