Desain Material 3 adalah evolusi berikutnya dari Desain Material. Desain Material ini mencakup versi terbaru tema, komponen, dan fitur personalisasi Material You seperti warna dinamis. Ini adalah update dari Desain Material 2 dan terkait erat dengan gaya visual dan UI sistem baru di Android 12 dan yang lebih baru.
Panduan ini berfokus pada migrasi dari library Jetpack Compose Material (androidx.compose.material) ke library Jetpack Compose Material 3 (androidx.compose.material3).
Pendekatan
Secara umum, Anda tidak boleh menggunakan M2 dan M3 dalam satu aplikasi jangka panjang. Hal ini karena kedua sistem desain dan library masing-masing sangat berbeda dalam hal desain UX/UI dan implementasi Compose-nya.
Aplikasi Anda mungkin menggunakan sistem desain, seperti yang dibuat menggunakan Figma. Jika demikian, kami sangat menyarankan Anda atau tim desain Anda untuk memigrasikannya dari M2 ke M3 sebelum memulai migrasi Compose. Sangat tidak umum jika migrasi aplikasi ke M3 saat desain UX/UI-nya didasarkan pada M2.
Selain itu, pendekatan Anda terhadap migrasi harus berbeda bergantung pada ukuran, kompleksitas, dan desain UX/UI aplikasi. Dengan begitu, Anda dapat meminimalkan dampak terhadap codebase. Anda harus menggunakan pendekatan bertahap untuk migrasi.
Waktu bermigrasi
Anda harus memulai migrasi sesegera mungkin. Namun, penting untuk mempertimbangkan apakah aplikasi Anda berada di posisi realistis untuk sepenuhnya bermigrasi dari M2 ke M3. Ada beberapa skenario pemblokir yang perlu dipertimbangkan sebelum memulai:
Skenario | Pendekatan yang direkomendasikan |
---|---|
Tidak ada pemblokir | Mulai migrasi bertahap |
Komponen dari M2 belum tersedia di M3. Lihat bagian Komponen dan tata letak di bawah. | Mulai migrasi bertahap |
Anda atau tim desain Anda belum memigrasikan sistem desain aplikasi dari M2 ke M3 | Migrasikan sistem desain dari M2 ke M3, lalu mulai migrasi bertahap |
Meskipun terpengaruh oleh skenario di atas, Anda harus melakukan pendekatan bertahap untuk migrasi sebelum melakukan dan merilis update aplikasi. Dalam hal ini, Anda akan menggunakan M2 dan M3 secara berdampingan, dan secara bertahap menghentikan M2 saat bermigrasi ke M3.
Pendekatan bertahap
Langkah-langkah umum untuk migrasi bertahap adalah sebagai berikut:
- Tambahkan dependensi M3 bersama dependensi M2.
- Tambahkan versi M3 tema aplikasi Anda bersama versi M2 tema aplikasi.
- Migrasikan setiap modul, layar, atau composable ke M3, bergantung pada ukuran dan kompleksitas aplikasi Anda (lihat bagian di bawah untuk detailnya).
- Setelah dimigrasikan sepenuhnya, hapus versi M2 tema aplikasi Anda.
- Hapus dependensi M2.
Dependensi
M3 memiliki paket dan versi terpisah untuk M2:
M2
implementation "androidx.compose.material:material:$m2-version"
M3
implementation "androidx.compose.material3:material3:$m3-version"
Lihat versi M3 terbaru di halaman rilis Compose Material 3.
Dependensi Material lainnya di luar library M2 dan M3 utama tidak berubah. Dependensi tersebut menggunakan kombinasi paket dan versi M2 dan M3, tetapi hal ini tidak memengaruhi migrasi. Dependensi dapat digunakan begitu saja dengan M3:
Library | Paket dan versi |
---|---|
Compose Material Icons | androidx.compose.material:material-icons-*:$m2-version |
Compose Material Ripple | androidx.compose.material:material-ripple:$m2-version |
API Eksperimental
Beberapa API M3 dianggap eksperimental. Dalam hal ini, Anda harus memilih untuk mengaktifkan
fungsi atau level file menggunakan anotasi ExperimentalMaterial3Api
:
import androidx.compose.material3.ExperimentalMaterial3Api
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AppComposable() {
// M3 composables
}
Tema
Pada M2 dan M3, composable tema diberi nama MaterialTheme
, tetapi paket dan parameter
impornya berbeda:
M2
import androidx.compose.material.MaterialTheme
MaterialTheme(
colors = AppColors,
typography = AppTypography,
shapes = AppShapes
) {
// M2 content
}
M3
import androidx.compose.material3.MaterialTheme
MaterialTheme(
colorScheme = AppColorScheme,
typography = AppTypography,
shapes = AppShapes
) {
// M3 content
}
Warna
Sistem warna di M3 sangat berbeda dengan M2. Jumlah
parameter warna telah meningkat, memiliki nama berbeda, dan
memetakan secara berbeda ke komponen M3. Di Compose, ini berlaku untuk class
Colors
M2, class ColorScheme
M3, dan fungsi terkait:
M2
import androidx.compose.material.lightColors
import androidx.compose.material.darkColors
val AppLightColors = lightColors(
// M2 light Color parameters
)
val AppDarkColors = darkColors(
// M2 dark Color parameters
)
val AppColors = if (darkTheme) {
AppDarkColors
} else {
AppLightColors
}
M3
import androidx.compose.material3.lightColorScheme
import androidx.compose.material3.darkColorScheme
val AppLightColorScheme = lightColorScheme(
// M3 light Color parameters
)
val AppDarkColorScheme = darkColorScheme(
// M3 dark Color parameters
)
val AppColorScheme = if (darkTheme) {
AppDarkColorScheme
} else {
AppLightColorScheme
}
Mengingat perbedaan yang signifikan antara sistem warna M2 dan M3, tidak ada
pemetaan yang wajar untuk parameter Color
. Sebagai gantinya, gunakan alat
Builder Tema Material untuk membuat skema warna M3. Gunakan warna M2
sebagai warna sumber inti pada alat, yang diperluas alat ini ke palet warna
yang digunakan oleh skema warna M3. Pemetaan berikut direkomendasikan sebagai
titik awal:
M2 | Material Theme Builder |
---|---|
primary |
Primer |
primaryVariant |
Sekunder |
secondary |
Tersier |
surface atau background |
Netral |
Anda dapat menyalin nilai kode heksadesimal warna untuk tema terang dan gelap dari alat ini dan menggunakannya untuk menerapkan instance ColorScheme M3. Atau, Material Theme Builder dapat mengekspor kode Compose.
isLight
Tidak seperti class Colors
M2, class ColorScheme
M3 tidak menyertakan
parameter isLight
. Secara umum, Anda harus mencoba membuat model apa pun yang
memerlukan informasi ini di level tema. Contoh:
M2
import androidx.compose.material.lightColors
import androidx.compose.material.darkColors
import androidx.compose.material.MaterialTheme
@Composable
private fun AppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val colors = if (darkTheme) darkColors(…) else lightColors(…)
MaterialTheme(
colors = colors,
content = content
)
}
@Composable
fun AppComposable() {
AppTheme {
val cardElevation = if (MaterialTheme.colors.isLight) 0.dp else 4.dp
…
}
}
M3
import androidx.compose.material3.lightColorScheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.MaterialTheme
val LocalCardElevation = staticCompositionLocalOf { Dp.Unspecified }
@Composable
private fun AppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val cardElevation = if (darkTheme) 4.dp else 0.dp
CompositionLocalProvider(LocalCardElevation provides cardElevation) {
val colorScheme = if (darkTheme) darkColorScheme(…) else lightColorScheme(…)
MaterialTheme(
colorScheme = colorScheme,
content = content
)
}
}
@Composable
fun AppComposable() {
AppTheme {
val cardElevation = LocalCardElevation.current
…
}
}
Lihat panduan Sistem desain kustom di Compose untuk informasi selengkapnya.
Warna dinamis
Fitur baru di M3 adalah warna dinamis. Daripada menggunakan warna
kustom, ColorScheme
M3 dapat menggunakan warna wallpaper perangkat di Android
12 dan yang lebih baru, menggunakan fungsi berikut:
Tipografi
Sistem tipografi di M3 berbeda dengan M2. Jumlah
parameter tipografi kurang lebih sama, tetapi memiliki nama dan
pemetaan yang berbeda untuk komponen M3. Di Compose, ini berlaku untuk class
Typography
M2 dan class Typography
M3:
M2
import androidx.compose.material.Typography
val AppTypography = Typography(
// M2 TextStyle parameters
)
M3
import androidx.compose.material3.Typography
val AppTypography = Typography(
// M3 TextStyle parameters
)
Pemetaan parameter TextStyle
berikut direkomendasikan sebagai titik
awal:
M2 | M3 |
---|---|
h1 |
displayLarge |
h2 |
displayMedium |
h3 |
displaySmall |
T/A | headlineLarge |
h4 |
headlineMedium |
h5 |
headlineSmall |
h6 |
titleLarge |
subtitle1 |
titleMedium |
subtitle2 |
titleSmall |
body1 |
bodyLarge |
body2 |
bodyMedium |
caption |
bodySmall |
button |
labelLarge |
T/A | labelMedium |
overline |
labelSmall |
Bentuk
Sistem bentuk di M3 berbeda dengan M2. Jumlah parameter bentuk
telah meningkat, diberi nama berbeda dan dipetakan berbeda ke
komponen M3. Di Compose, ini berlaku untuk class Shapes
M2 dan
class Shapes
M3:
M2
import androidx.compose.material.Shapes
val AppShapes = Shapes(
// M2 Shape parameters
)
M3
import androidx.compose.material3.Shapes
val AppShapes = Shapes(
// M3 Shape parameters
)
Pemetaan parameter Shape
berikut direkomendasikan sebagai titik
awal:
M2 | M3 |
---|---|
T/A | extraSmall |
small |
small |
medium |
medium |
large |
large |
T/A | extraLarge |
Komponen dan tata letak
Sebagian besar komponen dan tata letak dari M2 tersedia di M3. Namun, ada beberapa yang hilang serta yang baru dan tidak ada di M2. Selain itu, beberapa komponen M3 memiliki lebih banyak variasi dibandingkan dengan yang setara di M2. Secara umum, antarmuka API M3 berupaya semirip mungkin dengan persamaan terdekatnya di M2.
Dengan sistem warna, tipografi, dan bentuk terbaru, komponen M3 cenderung dipetakan secara berbeda ke nilai tema baru. Sebaiknya lihat direktori token di kode sumber Compose Material 3 sebagai sumber kebenaran untuk pemetaan ini.
Meskipun beberapa komponen memerlukan pertimbangan khusus, pemetaan fungsi berikut direkomendasikan sebagai titik awal:
API tidak ada:
M2 | M3 |
---|---|
androidx.compose.material.swipeable |
Belum tersedia |
API yang diganti:
M2 | M3 |
---|---|
androidx.compose.material.BackdropScaffold |
Tidak ada M3 yang setara, migrasikan ke Scaffold atau BottomSheetScaffold |
androidx.compose.material.BottomDrawer |
Tidak ada M3 yang setara, migrasikan ke ModalBottomSheet |
API yang berganti nama:
Semua API lainnya:
Lihat komponen dan tata letak M3 terbaru di ringkasan Referensi API Compose Material 3, dan pantau terus halaman rilis untuk API baru dan yang diupdate.
Scaffold, snackbar, dan panel navigasi
Scaffold di M3 berbeda dengan M2. Pada M2 dan M3, composable tata letak utama
bernama Scaffold
, tetapi paket dan parameter impor berbeda:
M2
import androidx.compose.material.Scaffold
Scaffold(
// M2 scaffold parameters
)
M3
import androidx.compose.material3.Scaffold
Scaffold(
// M3 scaffold parameters
)
Scaffold
M2 yang berisi parameter backgroundColor
kini diberi nama
containerColor
di Scaffold
M3:
M2
import androidx.compose.material.Scaffold
Scaffold(
backgroundColor = …,
content = { … }
)
M3
import androidx.compose.material3.Scaffold
Scaffold(
containerColor = …,
content = { … }
)
Class ScaffoldState
M2 tidak ada lagi di M3 karena berisi
parameter drawerState
yang tidak diperlukan lagi. Untuk menampilkan snackbar dengan
Scaffold
M3, gunakan SnackbarHostState
:
M2
import androidx.compose.material.Scaffold
import androidx.compose.material.rememberScaffoldState
val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
Scaffold(
scaffoldState = scaffoldState,
content = {
…
scope.launch {
scaffoldState.snackbarHostState.showSnackbar(…)
}
}
)
M3
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
val snackbarHostState = remember { SnackbarHostState() }
val scope = rememberCoroutineScope()
Scaffold(
snackbarHost = { SnackbarHost(snackbarHostState) },
content = {
…
scope.launch {
snackbarHostState.showSnackbar(…)
}
}
)
Semua parameter drawer*
dari Scaffold
M2 telah dihapus dari
Scaffold
M3. Hal ini mencakup parameter seperti drawerShape
dan
drawerContent
. Untuk menampilkan panel samping dengan Scaffold
M3, gunakan composable
panel navigasi, seperti ModalNavigationDrawer
, sebagai gantinya:
M2
import androidx.compose.material.DrawerValue
import
import androidx.compose.material.Scaffold
import androidx.compose.material.rememberDrawerState
import androidx.compose.material.rememberScaffoldState
val scaffoldState = rememberScaffoldState(
drawerState = rememberDrawerState(DrawerValue.Closed)
)
val scope = rememberCoroutineScope()
Scaffold(
scaffoldState = scaffoldState,
drawerContent = { … },
drawerGesturesEnabled = …,
drawerShape = …,
drawerElevation = …,
drawerBackgroundColor = …,
drawerContentColor = …,
drawerScrimColor = …,
content = {
…
scope.launch {
scaffoldState.drawerState.open()
}
}
)
M3
import androidx.compose.material3.DrawerValue
import androidx.compose.material3.ModalDrawerSheet
import androidx.compose.material3.ModalNavigationDrawer
import androidx.compose.material3.Scaffold
import androidx.compose.material3.rememberDrawerState
val drawerState = rememberDrawerState(DrawerValue.Closed)
val scope = rememberCoroutineScope()
ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
ModalDrawerSheet(
drawerShape = …,
drawerTonalElevation = …,
drawerContainerColor = …,
drawerContentColor = …,
content = { … }
)
},
gesturesEnabled = …,
scrimColor = …,
content = {
Scaffold(
content = {
…
scope.launch {
drawerState.open()
}
}
)
}
)
Panel aplikasi atas
Panel aplikasi atas di M3 berbeda dengan yang ada di M2. Dalam M2
dan M3, composable panel aplikasi atas utama diberi nama TopAppBar
, tetapi
paket dan parameter impornya berbeda:
M2
import androidx.compose.material.TopAppBar
TopAppBar(…)
M3
import androidx.compose.material3.TopAppBar
TopAppBar(…)
Sebaiknya gunakan CenterAlignedTopAppBar
M3 jika sebelumnya Anda
meletakkan konten di tengah-tengah TopAppBar
M2. Sebaiknya Anda juga mengetahui
MediumTopAppBar
dan LargeTopAppBar
.
Panel aplikasi atas M3 berisi parameter scrollBehavior
baru untuk memberikan fungsi
yang berbeda saat men-scroll melalui class TopAppBarScrollBehavior
, seperti
perubahan elevasi. Hal ini berfungsi bersama dengan konten scroll melalui
Modifer.nestedScroll
. Hal ini dapat dilakukan dalam TopAppBar
M2 dengan
mengubah parameter elevation
secara manual:
M2
import androidx.compose.material.AppBarDefaults
import androidx.compose.material.Scaffold
import androidx.compose.material.TopAppBar
val state = rememberLazyListState()
val isAtTop by remember {
derivedStateOf {
state.firstVisibleItemIndex == 0 && state.firstVisibleItemScrollOffset == 0
}
}
Scaffold(
topBar = {
TopAppBar(
elevation = if (isAtTop) {
0.dp
} else {
AppBarDefaults.TopAppBarElevation
},
…
)
},
content = {
LazyColumn(state = state) { … }
}
)
M3
import androidx.compose.material3.Scaffold
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
TopAppBar(
scrollBehavior = scrollBehavior,
…
)
},
content = {
LazyColumn { … }
}
)
Navigasi bawah/Menu navigasi
Navigasi bawah di M2 telah diganti namanya menjadi menu navigasi di
M3. Di M2 terdapat composable BottomNavigation
dan
BottomNavigationItem
, sedangkan di M3 terdapat composable
NavigationBar
dan NavigationBarItem
:
M2
import androidx.compose.material.BottomNavigation
import androidx.compose.material.BottomNavigationItem
BottomNavigation {
BottomNavigationItem(…)
BottomNavigationItem(…)
BottomNavigationItem(…)
}
M3
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
NavigationBar {
NavigationBarItem(…)
NavigationBarItem(…)
NavigationBarItem(…)
}
Tombol, tombol ikon, dan FAB
Tombol, tombol ikon, dan tombol tindakan mengambang (FAB) di M3 berbeda dengan yang ada di M2. M3 mencakup semua composable tombol M2:
M2
import androidx.compose.material.Button
import androidx.compose.material.ExtendedFloatingActionButton
import androidx.compose.material.FloatingActionButton
import androidx.compose.material.IconButton
import androidx.compose.material.IconToggleButton
import androidx.compose.material.OutlinedButton
import androidx.compose.material.TextButton
// M2 buttons
Button(…)
OutlinedButton(…)
TextButton(…)
// M2 icon buttons
IconButton(…)
IconToggleButton(…)
// M2 FABs
FloatingActionButton(…)
ExtendedFloatingActionButton(…)
M3
import androidx.compose.material3.Button
import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.IconButton
import androidx.compose.material3.IconToggleButton
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.TextButton
// M3 buttons
Button(…)
OutlinedButton(…)
TextButton(…)
// M3 icon buttons
IconButton(…)
IconToggleButton(…)
// M3 FABs
FloatingActionButton(…)
ExtendedFloatingActionButton(…)
M3 juga menyertakan variasi tombol baru. Lihat di ringkasan Referensi API Compose Material 3.
Tombol alih
Tombol alih di M3 berbeda dengan M2. Pada M2 dan M3, composable
tombol alih diberi nama Switch
, tetapi paket impornya berbeda:
M2
import androidx.compose.material.Switch
Switch(…)
M3
import androidx.compose.material3.Switch
Switch(…)
Permukaan dan elevasi
Sistem permukaan dan elevasi di M3 berbeda dengan M2. Ada dua jenis elevasi di M3:
- Elevasi bayangan (menambahkan bayangan, sama seperti di M2)
- Elevasi warna (mengubah warnanya, baru di M3)
Di Compose, ini berlaku untuk fungsi Surface
M2 dan fungsi
Surface
M3:
M2
import androidx.compose.material.Surface
Surface(
elevation = …
) { … }
M3
import androidx.compose.material3.Surface
Surface(
shadowElevation = …,
tonalElevation = …
) { … }
Anda dapat menggunakan nilai Dp
elevation
di M2 untuk shadowElevation
dan/atau tonalElevation
di M3, bergantung pada preferensi desain UX/UI.
Surface
adalah composable pendukung di balik sebagian besar komponen, sehingga composable
komponen juga dapat menampilkan parameter elevasi yang harus Anda migrasikan dengan cara
yang sama.
Elevasi warna di M3 menggantikan konsep overlay elevasi di tema gelap M2. Akibatnya, ElevationOverlay
dan LocalElevationOverlay
tidak ada di M3, dan LocalAbsoluteElevation
di M2 telah diubah menjadi
LocalAbsoluteTonalElevation
di M3.
Penekanan dan alfa konten
Penekanan di M3 berbeda secara signifikan dengan M2. Di M2, penekanan melibatkan penggunaan warna aktif dengan nilai alfa tertentu untuk membedakan konten seperti teks dan ikon. Di M3, kini ada beberapa pendekatan yang berbeda:
- Menggunakan warna aktif beserta warna varian aktif dari sistem warna M3 yang diperluas.
- Penggunaan ketebalan font yang berbeda untuk teks.
Akibatnya, ContentAlpha
dan LocalContentAlpha
tidak ada di
M3 dan harus diganti.
Pemetaan berikut direkomendasikan sebagai titik awal:
M2 | M3 |
---|---|
onSurface dengan ContentAlpha.high |
onSurface secara umum, FontWeight.Medium - FontWeight.Black untuk teks |
onSurface dengan ContentAlpha.medium |
onSurfaceVariant secara umum, FontWeight.Thin - FontWeight.Normal untuk teks |
onSurface dengan ContentAlpha.disabled |
onSurface.copy(alpha = 0.38f) |
Berikut adalah contoh penekanan ikon di M2 vs. M3:
M2
import androidx.compose.material.ContentAlpha
import androidx.compose.material.LocalContentAlpha
// High emphasis
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.high) {
Icon(…)
}
// Medium emphasis
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
Icon(…)
}
// Disabled emphasis
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.disabled) {
Icon(…)
}
M3
import androidx.compose.material3.LocalContentColor
// High emphasis
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface) {
Icon(…)
}
// Medium emphasis
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurfaceVariant) {
Icon(…)
}
// Disabled emphasis
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f)) {
Icon(…)
}
Berikut adalah contoh penekanan teks di M2 dan M3:
M2
import androidx.compose.material.ContentAlpha
import androidx.compose.material.LocalContentAlpha
// High emphasis
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.high) {
Text(…)
}
// Medium emphasis
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
Text(…)
}
// Disabled emphasis
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.disabled) {
Text(…)
}
M3
import androidx.compose.material3.LocalContentColor
// High emphasis
Text(
…,
fontWeight = FontWeight.Bold
)
// Medium emphasis
Text(
…,
fontWeight = FontWeight.Normal
)
// Disabled emphasis
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f)) {
Text(
…,
fontWeight = FontWeight.Normal
)
}
Latar belakang dan penampung
Latar belakang di M2 diberi nama penampung di M3. Secara umum, Anda dapat mengganti
parameter background*
di M2 dengan container*
di M3, menggunakan nilai yang sama.
Contoh:
M2
Badge(
backgroundColor = MaterialTheme.colors.primary
) { … }
M3
Badge(
containerColor = MaterialTheme.colorScheme.primary
) { … }
Link penting
Untuk mempelajari migrasi dari M2 ke M3 di Compose lebih lanjut, lihat referensi tambahan berikut.
Dokumen
Aplikasi contoh
- Aplikasi contoh Reply M3
- Migrasi aplikasi contoh Jetchat M2 ke M3
- Migrasi aplikasi contoh Jetnews M2 ke M3
- Modul :core-designsystem aplikasi hero Android M3 Now in Android
Video
Referensi API dan kode sumber
Direkomendasikan untuk Anda
- Catatan: teks link ditampilkan saat JavaScript nonaktif
- Desain Material 2 di Compose
- Desain Material 3 di Compose
- Sistem desain kustom di Compose