Tugas 9: Membuat Aplikasi Woof
Nama : Helsa Nesta Dhaifullah
NRP : 5025201005Kelas : PPB I - 2024
Tentang Aplikasi
Aplikasi Woof merupakan sebuah platform yang menyajikan daftar lengkap anjing
beserta informasi terkait, dengan penekanan pada Desain Material untuk
menciptakan pengalaman pengguna yang menarik. Setiap entri dalam daftar anjing
dapat diperluas untuk mengungkapkan hobi mereka yang unik. Pengguna akan
dimanjakan dengan animasi yang dinamis dan perubahan warna pada setiap kartu,
meningkatkan pengalaman visual mereka saat berinteraksi dengan setiap entri
untuk mengetahui lebih lanjut tentang hobi anjing-anjing tersebut.
![]() |
Pratinjau Hasil Akhir Aplikasi Woof |
Download Kode Awal
Untuk memulai pembuatan aplikasi Woof, langkah pertama yang perlu dilakukan
adalah mengunduh kode awal yang telah disediakan dari codelabs. Ini akan
sangat membantu bagi para pengembang Android pemula dalam membangun aplikasi
Woof. Dengan menggunakan kode awal tersebut, kita dapat memulai proyek
dengan lebih mudah dan cepat, serta memiliki kerangka dasar yang sudah
disiapkan untuk aplikasi tersebut.
Download Zip Kode Awal
Mempelajari Kode Awal
- Buka kode awal di Android Studio.
- Buka com.example.woof > data > Dog.kt. File ini berisi Dog data class yang akan digunakan untuk mewakili foto, nama, usia, dan hobi anjing. File ini juga berisi daftar anjing dan informasi yang akan digunakan sebagai data di aplikasi.
- Buka res > drawable. File ini berisi semua aset gambar yang dibutuhkan untuk project ini, termasuk ikon aplikasi, gambar anjing, dan ikon.
- Buka res > values > strings.xml. File ini berisi string yang digunakan dalam aplikasi ini, termasuk nama aplikasi, nama anjing, deskripsinya, dan lainnya.
- Buka MainActivity.kt. File ini berisi kode untuk membuat daftar sederhana yang menampilkan foto anjing, nama anjing, dan usia anjing tersebut.
- WoofApp() berisi LazyColumn yang menampilkan DogItem.
- DogItem() berisi Row yang menampilkan foto anjing dan informasi tentangnya.
- DogIcon() menampilkan foto anjing.
- DogInformation() menampilkan nama dan usia anjing.
- WoofPreview() menampilkan pratinjau aplikasi di panel Design.
Menambahkan Palet Warna ke Tema
Sebelumnya, dalam website Material Theme Builder, ganti warna primary dari Core Colors dengan warna
dengan kode #006C4C. Di halaman website tersebut, ada opsi
untuk mengklik tombol Export guna mendownload file Color.kt dan
file Theme.kt dengan tema kustom yang telah dibuat di Theme Builder.
- Buka file Color.kt dan ganti konten dengan kode berikut.
- Buka file Theme.kt dan ganti konten dengan kode berikut.
package com.example.woof.ui.theme import androidx.compose.ui.graphics.Color val md_theme_light_primary = Color(0xFF006C4C) val md_theme_light_onPrimary = Color(0xFFFFFFFF) val md_theme_light_primaryContainer = Color(0xFF89F8C7) val md_theme_light_onPrimaryContainer = Color(0xFF002114) val md_theme_light_secondary = Color(0xFF4D6357) val md_theme_light_onSecondary = Color(0xFFFFFFFF) val md_theme_light_secondaryContainer = Color(0xFFCFE9D9) val md_theme_light_onSecondaryContainer = Color(0xFF092016) val md_theme_light_tertiary = Color(0xFF3D6373) val md_theme_light_onTertiary = Color(0xFFFFFFFF) val md_theme_light_tertiaryContainer = Color(0xFFC1E8FB) val md_theme_light_onTertiaryContainer = Color(0xFF001F29) val md_theme_light_error = Color(0xFFBA1A1A) val md_theme_light_errorContainer = Color(0xFFFFDAD6) val md_theme_light_onError = Color(0xFFFFFFFF) val md_theme_light_onErrorContainer = Color(0xFF410002) val md_theme_light_background = Color(0xFFFBFDF9) val md_theme_light_onBackground = Color(0xFF191C1A) val md_theme_light_surface = Color(0xFFFBFDF9) val md_theme_light_onSurface = Color(0xFF191C1A) val md_theme_light_surfaceVariant = Color(0xFFDBE5DD) val md_theme_light_onSurfaceVariant = Color(0xFF404943) val md_theme_light_outline = Color(0xFF707973) val md_theme_light_inverseOnSurface = Color(0xFFEFF1ED) val md_theme_light_inverseSurface = Color(0xFF2E312F) val md_theme_light_inversePrimary = Color(0xFF6CDBAC) val md_theme_light_shadow = Color(0xFF000000) val md_theme_light_surfaceTint = Color(0xFF006C4C) val md_theme_light_outlineVariant = Color(0xFFBFC9C2) val md_theme_light_scrim = Color(0xFF000000) val md_theme_dark_primary = Color(0xFF6CDBAC) val md_theme_dark_onPrimary = Color(0xFF003826) val md_theme_dark_primaryContainer = Color(0xFF005138) val md_theme_dark_onPrimaryContainer = Color(0xFF89F8C7) val md_theme_dark_secondary = Color(0xFFB3CCBE) val md_theme_dark_onSecondary = Color(0xFF1F352A) val md_theme_dark_secondaryContainer = Color(0xFF354B40) val md_theme_dark_onSecondaryContainer = Color(0xFFCFE9D9) val md_theme_dark_tertiary = Color(0xFFA5CCDF) val md_theme_dark_onTertiary = Color(0xFF073543) val md_theme_dark_tertiaryContainer = Color(0xFF244C5B) val md_theme_dark_onTertiaryContainer = Color(0xFFC1E8FB) val md_theme_dark_error = Color(0xFFFFB4AB) val md_theme_dark_errorContainer = Color(0xFF93000A) val md_theme_dark_onError = Color(0xFF690005) val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6) val md_theme_dark_background = Color(0xFF191C1A) val md_theme_dark_onBackground = Color(0xFFE1E3DF) val md_theme_dark_surface = Color(0xFF191C1A) val md_theme_dark_onSurface = Color(0xFFE1E3DF) val md_theme_dark_surfaceVariant = Color(0xFF404943) val md_theme_dark_onSurfaceVariant = Color(0xFFBFC9C2) val md_theme_dark_outline = Color(0xFF8A938C) val md_theme_dark_inverseOnSurface = Color(0xFF191C1A) val md_theme_dark_inverseSurface = Color(0xFFE1E3DF) val md_theme_dark_inversePrimary = Color(0xFF006C4C) val md_theme_dark_shadow = Color(0xFF000000) val md_theme_dark_surfaceTint = Color(0xFF6CDBAC) val md_theme_dark_outlineVariant = Color(0xFF404943) val md_theme_dark_scrim = Color(0xFF000000)
package com.example.woof.ui.theme import android.app.Activity import android.os.Build import android.view.View import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material3.MaterialTheme import androidx.compose.material3.darkColorScheme import androidx.compose.material3.dynamicDarkColorScheme import androidx.compose.material3.dynamicLightColorScheme import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView import androidx.core.view.WindowCompat private val LightColors = lightColorScheme( primary = md_theme_light_primary, onPrimary = md_theme_light_onPrimary, primaryContainer = md_theme_light_primaryContainer, onPrimaryContainer = md_theme_light_onPrimaryContainer, secondary = md_theme_light_secondary, onSecondary = md_theme_light_onSecondary, secondaryContainer = md_theme_light_secondaryContainer, onSecondaryContainer = md_theme_light_onSecondaryContainer, tertiary = md_theme_light_tertiary, onTertiary = md_theme_light_onTertiary, tertiaryContainer = md_theme_light_tertiaryContainer, onTertiaryContainer = md_theme_light_onTertiaryContainer, error = md_theme_light_error, errorContainer = md_theme_light_errorContainer, onError = md_theme_light_onError, onErrorContainer = md_theme_light_onErrorContainer, background = md_theme_light_background, onBackground = md_theme_light_onBackground, surface = md_theme_light_surface, onSurface = md_theme_light_onSurface, surfaceVariant = md_theme_light_surfaceVariant, onSurfaceVariant = md_theme_light_onSurfaceVariant, outline = md_theme_light_outline, inverseOnSurface = md_theme_light_inverseOnSurface, inverseSurface = md_theme_light_inverseSurface, inversePrimary = md_theme_light_inversePrimary, surfaceTint = md_theme_light_surfaceTint, outlineVariant = md_theme_light_outlineVariant, scrim = md_theme_light_scrim, ) private val DarkColors = darkColorScheme( primary = md_theme_dark_primary, onPrimary = md_theme_dark_onPrimary, primaryContainer = md_theme_dark_primaryContainer, onPrimaryContainer = md_theme_dark_onPrimaryContainer, secondary = md_theme_dark_secondary, onSecondary = md_theme_dark_onSecondary, secondaryContainer = md_theme_dark_secondaryContainer, onSecondaryContainer = md_theme_dark_onSecondaryContainer, tertiary = md_theme_dark_tertiary, onTertiary = md_theme_dark_onTertiary, tertiaryContainer = md_theme_dark_tertiaryContainer, onTertiaryContainer = md_theme_dark_onTertiaryContainer, error = md_theme_dark_error, errorContainer = md_theme_dark_errorContainer, onError = md_theme_dark_onError, onErrorContainer = md_theme_dark_onErrorContainer, background = md_theme_dark_background, onBackground = md_theme_dark_onBackground, surface = md_theme_dark_surface, onSurface = md_theme_dark_onSurface, surfaceVariant = md_theme_dark_surfaceVariant, onSurfaceVariant = md_theme_dark_onSurfaceVariant, outline = md_theme_dark_outline, inverseOnSurface = md_theme_dark_inverseOnSurface, inverseSurface = md_theme_dark_inverseSurface, inversePrimary = md_theme_dark_inversePrimary, surfaceTint = md_theme_dark_surfaceTint, outlineVariant = md_theme_dark_outlineVariant, scrim = md_theme_dark_scrim, ) @Composable fun WoofTheme( darkTheme: Boolean = isSystemInDarkTheme(), // Dynamic color is available on Android 12+ dynamicColor: Boolean = false, content: @Composable () -> Unit ) { val colorScheme = when { dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { val context = LocalContext.current if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) } darkTheme -> DarkColors else -> LightColors } val view = LocalView.current if (!view.isInEditMode) { SideEffect { setUpEdgeToEdge(view, darkTheme) } } MaterialTheme( colorScheme = colorScheme, typography = Typography, shapes = Shapes, content = content ) } /** * Sets up edge-to-edge for the window of this [view]. The system icon colors are set to either * light or dark depending on whether the [darkTheme] is enabled or not. */ private fun setUpEdgeToEdge(view: View, darkTheme: Boolean) { val window = (view.context as Activity).window WindowCompat.setDecorFitsSystemWindows(window, false) window.statusBarColor = Color.Transparent.toArgb() val navigationBarColor = when { Build.VERSION.SDK_INT >= 29 -> Color.Transparent.toArgb() Build.VERSION.SDK_INT >= 26 -> Color(0xFF, 0xFF, 0xFF, 0x63).toArgb() // Min sdk version for this app is 24, this block is for SDK versions 24 and 25 else -> Color(0x00, 0x00, 0x00, 0x50).toArgb() } window.navigationBarColor = navigationBarColor val controller = WindowCompat.getInsetsController(window, view) controller.isAppearanceLightStatusBars = !darkTheme controller.isAppearanceLightNavigationBars = !darkTheme }
Pemetaan Warna
Di bagian ini, kita akan menggabungkan Row yang berisi
DogIcon() dan DogInformation() dengan
Card untuk membedakan warna item daftar dengan latar belakang.
- Dalam fungsi composable DogItem(), gabungkan Row() dengan Card().
- Karena Card kini merupakan composable turunan pertama di DogItem(), teruskan pengubah dari DogItem() ke Card, dan update pengubah Row ke instance baru Modifier.
- Lihat WoofPreview(). Item daftar sekarang telah otomatis berubah warna karena Composable Card. Warnanya terlihat bagus, tetapi tidak ada spasi di antara item daftar.
Pratinjau WoofPreview() untuk Pemetaan Warna
Card() { Row( modifier = modifier .fillMaxWidth() .padding(dimensionResource(id = R.dimen.padding_small)) ) { DogIcon(dog.imageResourceId) DogInformation(dog.name, dog.age) } }
Card(modifier = modifier) { Row( modifier = Modifier .fillMaxWidth() .padding(dimensionResource(id = R.dimen.padding_small)) ) { DogIcon(dog.imageResourceId) DogInformation(dog.name, dog.age) } }
File Dimensi
Gunakan file dimens.xml untuk menyimpan nilai dimensi seperti yang
Anda lakukan dengan strings.xml untuk string. Ini memungkinkan
pengelolaan nilai-nilai dimensi secara terpusat dan memungkinkan untuk
perubahan yang konsisten.
Buka app > res > values > dimens.xml dan lihat file. Fungsi
ini menyimpan nilai dimensi untuk padding_small,
padding_medium, dan image_size. Dimensi ini akan digunakan di
seluruh aplikasi.
<resources> <dimen name="padding_small">8dp</dimen> <dimen name="padding_medium">16dp</dimen> <dimen name="image_size">64dp</dimen> </resources>
- Di WoofApp(), tambahkan modifier dengan padding_small dalam panggilan ke DogItem().
@Composable fun WoofApp() { Scaffold { it -> LazyColumn(contentPadding = it) { items(dogs) { DogItem( dog = it, modifier = Modifier.padding(dimensionResource(R.dimen.padding_small)) ) } } } }
Pratinjau WoofPreview() setelah Ada Spacing antar Item |
Menambahkan Bentuk
Membentuk gambar anjing menjadi lingkaran
- Buka file Shape.kt dan perhatikan bahwa parameter small ditetapkan ke RoundedCornerShape(50.dp). Ini akan membentuk gambar menjadi lingkaran.
- Buka MainActivity.kt. Di DogIcon(), tambahkan atribut clip ke modifier dari Image; tindakan ini akan memotong gambar menjadi sebuah bentuk. Teruskan MaterialTheme.shapes.small.
- Untuk membuat semua foto menjadi lingkaran, tambahkan atribut ContentScale dan Crop; tindakan ini akan memangkas gambar agar sesuai. Perlu diketahui bahwa contentScale adalah atribut dari Image, dan bukan bagian dari modifier.
val Shapes = Shapes( small = RoundedCornerShape(50.dp), )
import androidx.compose.ui.draw.clip @Composable fun DogIcon( @DrawableRes dogIcon: Int, modifier: Modifier = Modifier ) { Image( modifier = modifier .size(dimensionResource(id = R.dimen.image_size)) .padding(dimensionResource(id = R.dimen.padding_small)) .clip(MaterialTheme.shapes.small),
Pratinjau WoofPreview() Bentuk Gambar Anjing belum Sempurna |
import androidx.compose.ui.layout.ContentScale @Composable fun DogIcon( @DrawableRes dogIcon: Int, modifier: Modifier = Modifier ) { Image( modifier = modifier .size(dimensionResource(id = R.dimen.image_size)) .padding(dimensionResource(id = R.dimen.padding_small)) .clip(MaterialTheme.shapes.small), contentScale = ContentScale.Crop,
@Composable fun DogIcon( @DrawableRes dogIcon: Int, modifier: Modifier = Modifier ) { Image( modifier = modifier .size(dimensionResource(R.dimen.image_size)) .padding(dimensionResource(R.dimen.padding_small)) .clip(MaterialTheme.shapes.small), contentScale = ContentScale.Crop, painter = painterResource(dogIcon), // Content Description is not needed here - image is decorative, and setting a null content // description allows accessibility services to skip this element during navigation. contentDescription = null ) }
- Dalam file Shape.kt, Card adalah komponen media yang memerlukan parameter media objek Shapes. Dalam aplikasi ini, sudut kanan atas dan kiri bawah dari item daftar ditentukan, tetapi tidak membentuk lingkaran penuh. Untuk mencapainya, teruskan nilai 16.dp ke atribut medium.
medium = RoundedCornerShape(bottomStart = 16.dp, topEnd = 16.dp)
Membuat Direktori Resource Android font
- Di tampilan project Android Studio, klik kanan folder res.
-
Pilih New > Android Resource Directory.
Pembuatan Android Resource Directory Baru -
Beri nama Direktori font, setel jenis Resource sebagai
font, dan klik OK.
Konfigurasi Pembuatan Resource Djrectory untuk Font - Buka direktori resource font baru yang terletak di res > font.
Mendownload font kustom
- Buka https://fonts.google.com/, dan telusuri Montserrat, lalu klik Download Family.
-
Ekstrak file Zip. Di folder static, temukan
Montserrat-Bold.ttf dan Montserrat-Regular.ttf (ttf adalah singkatan dari TrueType Font dan merupakan format untuk
file font). Pilih kedua font dan tarik ke direktori resource font
dalam project Anda di Android Studio.
Isi File Folder Static Hasil Download Family Font Montserrat - Di folder font, ganti nama Montserrat-Bold.ttf menjadi montserrat_bold.ttf dan ganti nama Montserrat-Regular.ttf menjadi montserrat_regular.ttf.
- Telusuri Abril Fatface dan klik Download family.
- Buka folder Abril_Fatface yang telah didownload. Pilih AbrilFatface-Regular.ttf, lalu tarik ke direktori resource font.
- Di folder font, ganti nama Abril_Fatface_Regular.ttf menjadi abril_fatface_regular.ttf.
Import Font ke Resource Directory Font |
Melakukan inisialisasi font
- Di jendela project, buka ui.theme > Type.kt. Lakukan inisialisasi font yang didownload di bawah pernyataan impor dan di atas Typography val.
- Untuk atribut displayLarge, tetapkan sama dengan TextStyle, lalu isi fontFamily, fontWeight, dan fontSize dengan informasi dari tabel di atas. Artinya, semua teks yang ditetapkan ke displayLarge akan memiliki Abril Fatface sebagai font, dengan ketebalan font normal, dan fontSize 36.sp. Ulangi proses ini untuk displayMedium, labelSmall, dan bodyLarge.
import androidx.compose.ui.text.font.FontWeight val AbrilFatface = FontFamily( Font(R.font.abril_fatface_regular) ) val Montserrat = FontFamily( Font(R.font.montserrat_regular), Font(R.font.montserrat_bold, FontWeight.Bold) )
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.sp val Typography = Typography( displayLarge = TextStyle( fontFamily = AbrilFatface, fontWeight = FontWeight.Normal, fontSize = 36.sp ), displayMedium = TextStyle( fontFamily = Montserrat, fontWeight = FontWeight.Bold, fontSize = 20.sp ), labelSmall = TextStyle( fontFamily = Montserrat, fontWeight = FontWeight.Bold, fontSize = 14.sp ), bodyLarge = TextStyle( fontFamily = Montserrat, fontWeight = FontWeight.Normal, fontSize = 14.sp ) )
Menambahkan tipografi ke teks aplikasi
- Tambahkan displayMedium sebagai gaya untuk dogName karena merupakan informasi singkat yang penting. Tambahkan bodyLarge sebagai gaya untuk dogAge karena berfungsi cukup baik dengan ukuran teks yang lebih kecil.
- Sekarang di WoofPreview(), nama anjing akan menampilkan font Montserrat tebal dalam 20.sp, dan usia anjing menampilkan font Montserrat normal dalam 14.sp.
@Composable fun DogInformation( @StringRes dogName: Int, dogAge: Int, modifier: Modifier = Modifier ) { Column(modifier = modifier) { Text( text = stringResource(dogName), style = MaterialTheme.typography.displayMedium, modifier = Modifier.padding(top = dimensionResource(id = R.dimen.padding_small)) ) Text( text = stringResource(R.string.years_old, dogAge), style = MaterialTheme.typography.bodyLarge ) } }
Pratinjau WoofPreview() Penambahan Tipografi pada Teks |
Menambahkan panel atas
Scaffold adalah tata letak yang menyediakan slot untuk
berbagai komponen dan elemen layar, seperti Image,
Row, atau Column. Scaffold juga menyediakan
slot untuk TopAppBar, yang akan digunakan untuk branding
dan memberikan karakteristik pada aplikasi.
Struktur TopAppBar Aplikasi Woof |
Menambahkan gambar dan teks ke panel atas
- Di MainActivity.kt, buat composable bernama WoofTopAppBar() dengan modifier opsional.
- Parameter contentWindowInsets dalam Scaffold membantu menetapkan inset untuk konten, mewakili bagian layar tempat aplikasi berinteraksi dengan UI sistem, yang kemudian diteruskan ke konten melalui parameter PaddingValues. Nilai contentWindowInsets diteruskan ke LazyColumn sebagai contentPadding.
- Dalam Scaffold, tambahkan atribut topBar dan tetapkan ke WoofTopAppBar().
- Dalam WoofTopAppBar() Composable, tambahkan CenterAlignedTopAppBar() dan tetapkan parameter pengubah ke pengubah yang diteruskan ke WoofTopAppBar().
- Untuk parameter judul, teruskan Row yang akan menyimpan Image dan Text dari CenterAlignedTopAppBar.
-
Tambahkan logo Image ke Row
📌 Setel ukuran gambar di modifier sebagai image_size dalam file dimens.xml dan padding sebagai padding_small dari file dimens.xml.
📌 Gunakan painter untuk menetapkan Image sebagai ic_woof_logo dari folder drawable.
📌 Tetapkan contentDescription sebagai null. -
Selanjutnya, tambahkan Composable Text di dalam
Row setelah Image.
📌 Gunakan stringResource() untuk menetapkannya ke nilai app_name. Tindakan ini akan menetapkan teks ke nama aplikasi, yang disimpan di strings.xml.
📌 Setel gaya teks ke displayLarge karena nama aplikasi singkat dan penting. - Untuk menyelaraskan ikon dan teks secara vertikal, tambahkan parameter verticalAlignment ke Row dan tetapkan nilainya sama dengan Alignment.CenterVertically.
@Composable fun WoofApp() { Scaffold( topBar = { WoofTopAppBar() } ) { it -> LazyColumn(contentPadding = it) { items(dogs) { DogItem( dog = it, modifier = Modifier.padding(dimensionResource(R.dimen.padding_small)) ) } } } }
@Composable fun WoofTopAppBar(modifier: Modifier = Modifier){ CenterAlignedTopAppBar( title = { Row() { } }, modifier = modifier ) }
Row() {
Image(
modifier = Modifier
.size(dimensionResource(id = R.dimen.image_size))
.padding(dimensionResource(id = R.dimen.padding_small)),
painter = painterResource(R.drawable.ic_woof_logo),
contentDescription = null
)
}
Text( text = stringResource(R.string.app_name), style = MaterialTheme.typography.displayLarge )
import androidx.compose.ui.Alignment
Row(
verticalAlignment = Alignment.CenterVertically
)
Menambahkan ikon luaskan
Di bagian ini, Anda akan menambahkan ikon
Luaskan
dan Ciutkan
ke aplikasi.
![30c384f00846e69b.png](https://developer.android.com/static/codelabs/basic-android-kotlin-compose-woof-animation/img/30c384f00846e69b.png?hl=id)
![f88173321938c003.png](https://developer.android.com/static/codelabs/basic-android-kotlin-compose-woof-animation/img/f88173321938c003.png?hl=id)
Bentuk Card Item dengan Ikon Luaskan |
Menambahkan dependensi Gradle
- Di panel Project, buka Gradle Scripts > build.gradle.kts (Module :app).
- Scroll ke akhir file build.gradle.kts (Module :app). Di blok dependencies{}, tambahkan baris berikut:
implementation("androidx.compose.material:material-icons-extended")
Menambahkan composable ikon
- Di MainActivity.kt, setelah fungsi DogItem(), buat fungsi composable baru yang disebut DogItemButton().
- Teruskan Boolean untuk status diperluas, ekspresi lambda untuk pengendali onClick tombol, dan Modifier opsional sebagai berikut.
- Di dalam DogItemButton(), tambahkan IconButton() dengan parameter onClick sebagai lambda di akhir, yang akan dipanggil saat ikon ditekan, serta modifier opsional yang nilainya sama dengan yang diteruskan ke DogItemButton.
- Di dalam blok lambda IconButton(), tambahkan composable Icon dengan parameter imageVector yang diatur ke Icons.Filled.ExpandMore. Icon inilah yang akan ditampilkan di akhir item daftar.
- Tambahkan parameter tint dengan nilai MaterialTheme.colorScheme.secondary untuk mengatur warna ikon. Juga, tambahkan parameter contentDescription dengan nilai resource string R.string.expand_button_content_description.
@Composable private fun DogItemButton( expanded: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier ) { }
@Composable private fun DogItemButton( expanded: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier ){ IconButton( onClick = onClick, modifier = modifier ) { } }
IconButton( onClick = onClick, modifier = modifier ){ Icon( imageVector = Icons.Filled.ExpandMore, contentDescription = stringResource(R.string.expand_button_content_description), tint = MaterialTheme.colorScheme.secondary ) }
Menampilkan ikon
- Di awal DogItem(), tambahkan var untuk menyimpan status yang diluaskan dari item daftar. Setel nilai awal ke false.
- Pada composable DogItem(), di akhir blok Row, setelah panggilan ke DogInformation(), tambahkan DogItemButton(). Teruskan status expanded dan lambda kosong untuk callback.
-
Lihat WoofPreview() di panel Design.
Pratinjau WoofPreview() Penambahan Ikon Luaskan
import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue var expanded by remember { mutableStateOf(false) }
Row(
modifier = Modifier
.fillMaxWidth()
.padding(dimensionResource(R.dimen.padding_small))
) {
DogIcon(dog.imageResourceId)
DogInformation(dog.name, dog.age)
DogItemButton(
expanded = expanded,
onClick = { /*TODO*/ }
)
}
Menambahkan pengatur jarak ke baris item daftar
- Di DogItem(), antara DogInformation() dan DogItemButton(), tambahkan Spacer. Teruskan Modifier dengan weight(1f). Modifier.weight() menyebabkan pengatur jarak mengisi ruang yang tersisa di baris.
-
Lihat WoofPreview() di panel Design.
Perhatikan bahwa tombol luaskan kini sejajar dengan
bagian akhir item daftar.
Pratinjau WoofPreview() Pengaturan Layout Ikon Luaskan pada Card
Menambahkan Composable untuk menampilkan hobi
Bentuk Card yang Diluaskan untuk Menampilkan Hobi |
- Buat fungsi composable baru yang disebut DogHobby() yang menggunakan ID resource string hobi anjing dan Modifier opsional.
- Di dalam fungsi DogHobby(), buat Column dan teruskan pengubah yang diteruskan ke DogHobby().
-
Di dalam blok Column, tambahkan dua
composable Text. Satu untuk menampilkan teks
About di atas informasi hobi, dan satu lagi untuk
menampilkan informasi hobi.
📌 Setel text dari composable yang pertama ke about dari file strings.xml dan tetapkan style sebagai labelSmall.
📌 Tetapkan text dari composable yang kedua ke dogHobby yang diteruskan dan setel style ke bodyLarge. - Di dalam DogItem(), gabungkan Row dengan Column untuk mengizinkan penempatan DogHobby() di bawahnya, setelah Row.
- Tambahkan DogHobby() setelah Row sebagai turunan kedua dari Column. Teruskan dog.hobbies yang berisi hobi unik anjing yang diteruskan dan modifier dengan padding untuk composable DogHobby().
-
Lihat WoofPreview() di panel Design.
Perhatikan hobi anjing yang ditampilkan.
Pratinjau WoofPreview() Penambahan Composable Menampilkan Hobi
@Composable fun DogHobby( @StringRes dogHobby: Int, modifier: Modifier = Modifier ) { }
Column( modifier = modifier ) { Text( text = stringResource(R.string.about), style = MaterialTheme.typography.labelSmall ) Text( text = stringResource(dogHobby), style = MaterialTheme.typography.bodyLarge ) }
Column() { Row() { ... } DogHobby( dog.hobbies, modifier = Modifier.padding( start = dimensionResource(R.dimen.padding_medium), top = dimensionResource(R.dimen.padding_small), end = dimensionResource(R.dimen.padding_medium), bottom = dimensionResource(R.dimen.padding_medium) ) ) }
Menampilkan atau menyembunyikan hobi saat tombol diklik
- Pada fungsi composable DogItem(), dalam panggilan fungsi DogItemButton(), tentukan ekspresi lambda onClick(), ubah nilai status boolean expanded menjadi true saat tombol diklik, dan ubah kembali ke false jika tombol diklik lagi.
- Di fungsi DogItem(), gabungkan panggilan fungsi DogHobby() dengan pemeriksaan if pada boolean expanded.
- Dalam fungsi DogItemButton(), tambahkan pernyataan if yang memperbarui nilai imageVector berdasarkan status expanded sebagai berikut:
-
Jalankan aplikasi di perangkat atau emulator, atau
gunakan mode interaktif lagi dalam pratinjau.
Lihat hasil perubahan imageVector pada
daftar item berdasarkan status
expanded.
Pratinjau WoofPreview() Interaktifitas Tombol Ikon Luaskan
DogItemButton( expanded = expanded, onClick = { expanded = !expanded } )
@Composable fun DogItem( dog: Dog, modifier: Modifier = Modifier ) { var expanded by remember { mutableStateOf(false) } Card( ... ) { Column( ... ) { Row( ... ) { ... } if (expanded) { DogHobby( dog.hobbies, modifier = Modifier.padding( start = dimensionResource(R.dimen.padding_medium), top = dimensionResource(R.dimen.padding_small), end = dimensionResource(R.dimen.padding_medium), bottom = dimensionResource(R.dimen.padding_medium) ) ) } } } }
import androidx.compose.material.icons.filled.ExpandLess @Composable private fun DogItemButton( ... ) { IconButton(onClick = onClick) { Icon( imageVector = if (expanded) Icons.Filled.ExpandLess else Icons.Filled.ExpandMore, ... ) } }
Menambahkan animasi
- Di MainActivity.kt, di DogItem(), tambahkan parameter modifier ke tata letak Column.
- Buat rantai pengubah dengan pengubah animateContentSize untuk menganimasikan perubahan ukuran (tinggi item daftar).
- Untuk Woof, animasi akan mudah masuk dan keluar tanpa pantulan. Untuk mencapainya, tambahkan parameter animationSpec ke panggilan fungsi animateContentSize(). Setel ke animasi pegas dengan DampingRatioNoBouncy sehingga tidak ada pantulan dan parameter StiffnessMedium untuk membuat pegas sedikit kaku.
-
Lihat WoofPreview() di panel Design,
dan gunakan mode interaktif atau jalankan aplikasi
di emulator atau perangkat untuk melihat cara
kerja animasi pegas.
Pratinjau WoofPreview() Penambahan Animasi untuk Interaktifitas
@Composable fun DogItem( dog: Dog, modifier: Modifier = Modifier ) { ... Card( ... ) { Column( modifier = Modifier .animateContentSize() ){ ... } } }
import androidx.compose.animation.core.Spring import androidx.compose.animation.core.spring Column( modifier = Modifier .animateContentSize( animationSpec = spring( dampingRatio = Spring.DampingRatioNoBouncy, stiffness = Spring.StiffnessMedium ) ) )
(Opsional) Bereksperimen dengan animasi lain
Fungsi animate*AsState() adalah salah satu
Animation API yang paling sederhana di
Compose untuk menganimasikan satu nilai. Anda hanya
memberikan nilai akhir (atau nilai target), dan API
akan memulai animasi dari nilai saat ini ke nilai
akhir yang ditentukan.
- Dalam DogItem(), deklarasikan warna dan delegasikan inisialisasinya ke fungsi animateColorAsState().
- Setel parameter bernama targetValue, bergantung pada nilai boolean expanded. Jika item daftar diluaskan, tetapkan item daftar ke warna tertiaryContainer. Selain itu, tetapkan ke warna primaryContainer.
- Setel color sebagai pengubah latar belakang ke Column.
-
Lihat bagaimana warna berubah saat item daftar
diluaskan. Item daftar yang tidak diluaskan
memiliki warna primaryContainer dan item
daftar yang diluaskan berwarna
tertiaryContainer.
Pratinjau WoofPreview() Animasi Perubahan Warna Container
Berdasarkan Status Extended
import androidx.compose.animation.animateColorAsState @Composable fun DogItem( dog: Dog, modifier: Modifier = Modifier ) { var expanded by remember { mutableStateOf(false) } val color by animateColorAsState() ... }
import androidx.compose.animation.animateColorAsState @Composable fun DogItem( dog: Dog, modifier: Modifier = Modifier ) { var expanded by remember { mutableStateOf(false) } val color by animateColorAsState( targetValue = if (expanded) MaterialTheme.colorScheme.tertiaryContainer else MaterialTheme.colorScheme.primaryContainer, ) ... }
@Composable fun DogItem( dog: Dog, modifier: Modifier = Modifier ) { ... Card( ... ) { Column( modifier = Modifier .animateContentSize( ... ) ) .background(color = color) ) {...} }
Mendapatkan Kode Solusi
Untuk melihat kode secara lebih lengkap dan jelas,
Anda dapat menggunakan perintah git ini:
$ git clone https://github.com/helsanesta/Woof-AndroidApps.git
Atau, Anda dapat mendownload repositori sebagai file ZIP, lalu mengekstraknya, dan membukanya di Android Studio.
Download ZIP
Download ZIP
Jika Anda ingin melihat kode solusi, lihat di GitHub.
Komentar
Posting Komentar