Tugas 9: Membuat Aplikasi Woof

Nama     : Helsa Nesta Dhaifullah
NRP       : 5025201005
Kelas      : 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

  1. Buka kode awal di Android Studio.
  2. 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.
  3. Buka res > drawable. File ini berisi semua aset gambar yang dibutuhkan untuk project ini, termasuk ikon aplikasi, gambar anjing, dan ikon.
  4. Buka res > values > strings.xml. File ini berisi string yang digunakan dalam aplikasi ini, termasuk nama aplikasi, nama anjing, deskripsinya, dan lainnya.
  5. Buka MainActivity.kt. File ini berisi kode untuk membuat daftar sederhana yang menampilkan foto anjing, nama anjing, dan usia anjing tersebut.
  6. WoofApp() berisi LazyColumn yang menampilkan DogItem.
  7. DogItem() berisi Row yang menampilkan foto anjing dan informasi tentangnya.
  8. DogIcon() menampilkan foto anjing.
  9. DogInformation() menampilkan nama dan usia anjing.
  10. 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.
  1. Buka file Color.kt dan ganti konten dengan kode berikut.
  2. 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)
  3. Buka file Theme.kt dan ganti konten dengan kode berikut.
  4. 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.
  1. Dalam fungsi composable DogItem(), gabungkan Row() dengan Card().
  2. Card() {
       Row(
           modifier = modifier
               .fillMaxWidth()
               .padding(dimensionResource(id = R.dimen.padding_small))
       ) {
           DogIcon(dog.imageResourceId)
           DogInformation(dog.name, dog.age)
       }
    }
  3. Karena Card kini merupakan composable turunan pertama di DogItem(), teruskan pengubah dari DogItem() ke Card, dan update pengubah Row ke instance baru Modifier.
  4. Card(modifier = modifier) {
       Row(
           modifier = Modifier
               .fillMaxWidth()
               .padding(dimensionResource(id = R.dimen.padding_small))
       ) {
           DogIcon(dog.imageResourceId)
           DogInformation(dog.name, dog.age)
       }
    }
  5. 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

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>
  1. Di WoofApp(), tambahkan modifier dengan padding_small dalam panggilan ke DogItem().
  2. @Composable
    fun WoofApp() {
        Scaffold { it ->
            LazyColumn(contentPadding = it) {
                items(dogs) {
                    DogItem(
                        dog = it,
                        modifier = Modifier.padding(dimensionResource(R.dimen.padding_small))
                    )
                }
            }
        }
    }
Di WoofPreview(), sekarang ada lebih banyak definisi di antara item daftar.
Pratinjau WoofPreview() setelah Ada Spacing antar Ite

Menambahkan Bentuk

Membentuk gambar anjing menjadi lingkaran

  1. Buka file Shape.kt dan perhatikan bahwa parameter small ditetapkan ke RoundedCornerShape(50.dp). Ini akan membentuk gambar menjadi lingkaran.
  2. val Shapes = Shapes(
       small = RoundedCornerShape(50.dp),
    )
  3. Buka MainActivity.kt. Di DogIcon(), tambahkan atribut clip ke modifier dari Image; tindakan ini akan memotong gambar menjadi sebuah bentuk. Teruskan MaterialTheme.shapes.small.
  4. 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

  5. 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.
  6. 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,
Berikut adalah Composable DogIcon() lengkap beserta pratinjau dari WoofPreview().
@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
    )
}
Pratinjau WoofPreview() Bentuk Gambar Anjing Sempurna

Menambahkan bentuk ke item daftar

  1. 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.
  2. medium = RoundedCornerShape(bottomStart = 16.dp, topEnd = 16.dp)
Pratinjau WoofPreview() Pembentukan Shape Card Daftar Item

Menambahkan Tipografi

Membuat Direktori Resource Android font

  1. Di tampilan project Android Studio, klik kanan folder res.
  2. Pilih New > Android Resource Directory.
    Pembuatan Android Resource Directory Baru
  3. Beri nama Direktori font, setel jenis Resource sebagai font, dan klik OK.
    Konfigurasi Pembuatan Resource Djrectory untuk Font
  4. Buka direktori resource font baru yang terletak di res > font.

Mendownload font kustom

  1. Buka https://fonts.google.com/, dan telusuri Montserrat, lalu klik Download Family.
  2. 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
  3. Di folder font, ganti nama Montserrat-Bold.ttf menjadi montserrat_bold.ttf dan ganti nama Montserrat-Regular.ttf menjadi montserrat_regular.ttf.
  4. Telusuri Abril Fatface dan klik Download family.
  5. Buka folder Abril_Fatface yang telah didownload. Pilih AbrilFatface-Regular.ttf, lalu tarik ke direktori resource font.
  6. Di folder font, ganti nama Abril_Fatface_Regular.ttf menjadi abril_fatface_regular.ttf.
Seperti inilah tampilan direktori resource font dalam project Anda dengan tiga file font kustom:
Import Font ke Resource Directory Font

Melakukan inisialisasi font

  1. Di jendela project, buka ui.theme > Type.kt. Lakukan inisialisasi font yang didownload di bawah pernyataan impor dan di atas Typography val.
  2. 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)
    )
  3. 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.
  4. 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

  1. 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.
  2. @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
           )
       }
    }
  3. Sekarang di WoofPreview(), nama anjing akan menampilkan font Montserrat tebal dalam 20.sp, dan usia anjing menampilkan font Montserrat normal dalam 14.sp.
  4. 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

  1. Di MainActivity.kt, buat composable bernama WoofTopAppBar() dengan modifier opsional.
  2. 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.
  3. Dalam Scaffold, tambahkan atribut topBar dan tetapkan ke WoofTopAppBar().
  4. @Composable
    fun WoofApp() {
        Scaffold(
            topBar = {
                WoofTopAppBar()
            }
        ) { it ->
            LazyColumn(contentPadding = it) {
                items(dogs) {
                    DogItem(
                        dog = it,
                        modifier = Modifier.padding(dimensionResource(R.dimen.padding_small))
                    )
                }
            }
        }
    }
  5. Dalam WoofTopAppBar() Composable, tambahkan CenterAlignedTopAppBar() dan tetapkan parameter pengubah ke pengubah yang diteruskan ke WoofTopAppBar().
  6. Untuk parameter judul, teruskan Row yang akan menyimpan Image dan Text dari CenterAlignedTopAppBar.
  7. @Composable
    fun WoofTopAppBar(modifier: Modifier = Modifier){
       CenterAlignedTopAppBar(
           title = {
               Row() {
    
               }
           },
           modifier = modifier
       )
    }
  8. 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.
  9. 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
       )
    }
  10. 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.
  11. Text(
       text = stringResource(R.string.app_name),
       style = MaterialTheme.typography.displayLarge
    )
  12. Untuk menyelaraskan ikon dan teks secara vertikal, tambahkan parameter verticalAlignment ke Row dan tetapkan nilainya sama dengan Alignment.CenterVertically.
  13. import androidx.compose.ui.Alignment
    
    Row(
       verticalAlignment = Alignment.CenterVertically
    )
    Pratinjau Aplikasi Woof dengan Tema Terang

    Pratinjau Aplikasi Woof dengan Tema Gelap

Menambahkan ikon luaskan

Di bagian ini, Anda akan menambahkan ikon Luaskan 30c384f00846e69b.png dan Ciutkan f88173321938c003.png ke aplikasi.
Bentuk Card Item dengan Ikon Luaskan

Menambahkan dependensi Gradle

  1. Di panel Project, buka Gradle Scripts > build.gradle.kts (Module :app).
  2. Scroll ke akhir file build.gradle.kts (Module :app). Di blok dependencies{}, tambahkan baris berikut:
  3. implementation("androidx.compose.material:material-icons-extended")

Menambahkan composable ikon

  1. Di MainActivity.kt, setelah fungsi DogItem(), buat fungsi composable baru yang disebut DogItemButton().
  2. Teruskan Boolean untuk status diperluas, ekspresi lambda untuk pengendali onClick tombol, dan Modifier opsional sebagai berikut.
  3. @Composable
    private fun DogItemButton(
       expanded: Boolean,
       onClick: () -> Unit,
       modifier: Modifier = Modifier
    ) {
    
    }
  4. 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.
  5. @Composable
    private fun DogItemButton(
       expanded: Boolean,
       onClick: () -> Unit,
       modifier: Modifier = Modifier
    ){
       IconButton(
           onClick = onClick,
           modifier = modifier
       ) {
    
       }
    }
  6. 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.
  7. 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.
  8. IconButton(
       onClick = onClick,
       modifier = modifier
    ){
       Icon(
           imageVector = Icons.Filled.ExpandMore,
           contentDescription = stringResource(R.string.expand_button_content_description),
           tint = MaterialTheme.colorScheme.secondary
       )
    }

Menampilkan ikon

  1. Di awal DogItem(), tambahkan var untuk menyimpan status yang diluaskan dari item daftar. Setel nilai awal ke false.
  2. 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) }
  3. Pada composable DogItem(), di akhir blok Row, setelah panggilan ke DogInformation(), tambahkan DogItemButton(). Teruskan status expanded dan lambda kosong untuk callback.
  4. Row(
       modifier = Modifier
           .fillMaxWidth()
           .padding(dimensionResource(R.dimen.padding_small))
    ) {
       DogIcon(dog.imageResourceId)
       DogInformation(dog.name, dog.age)
       DogItemButton(
           expanded = expanded,
           onClick = { /*TODO*/ }
       )
    }
  5. Lihat WoofPreview() di panel Design.
    Pratinjau WoofPreview() Penambahan Ikon Luaskan
    Perhatikan bahwa tombol luaskan tidak sejajar dengan bagian akhir item daftar. Anda akan memperbaikinya di langkah berikutnya.

Menambahkan pengatur jarak ke baris item daftar

  1. Di DogItem(), antara DogInformation() dan DogItemButton(), tambahkan Spacer. Teruskan Modifier dengan weight(1f). Modifier.weight() menyebabkan pengatur jarak mengisi ruang yang tersisa di baris.
  2. 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
  1. Buat fungsi composable baru yang disebut DogHobby() yang menggunakan ID resource string hobi anjing dan Modifier opsional.
  2. @Composable
    fun DogHobby(
       @StringRes dogHobby: Int,
       modifier: Modifier = Modifier
    ) {
    }
  3. Di dalam fungsi DogHobby(), buat Column dan teruskan pengubah yang diteruskan ke DogHobby().
  4. 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.
  5. Column(
       modifier = modifier
    ) {
       Text(
           text = stringResource(R.string.about),
           style = MaterialTheme.typography.labelSmall
       )
       Text(
           text = stringResource(dogHobby),
           style = MaterialTheme.typography.bodyLarge
       )
    }
  6. Di dalam DogItem(), gabungkan Row dengan Column untuk mengizinkan penempatan DogHobby() di bawahnya, setelah Row.
  7. 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().
  8. 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)
           )
       )
    }
  9. Lihat WoofPreview() di panel Design. Perhatikan hobi anjing yang ditampilkan.
    Pratinjau WoofPreview() Penambahan Composable Menampilkan Hobi

Menampilkan atau menyembunyikan hobi saat tombol diklik

  1. 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.
  2. DogItemButton(
       expanded = expanded,
       onClick = { expanded = !expanded }
    )
  3. Di fungsi DogItem(), gabungkan panggilan fungsi DogHobby() dengan pemeriksaan if pada boolean expanded.
  4. @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)
                       )
                   )
               }
           }
       }
    }
  5. Dalam fungsi DogItemButton(), tambahkan pernyataan if yang memperbarui nilai imageVector berdasarkan status expanded sebagai berikut:
  6. 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,
               ...
           )
       }
    }
  7. Jalankan aplikasi di perangkat atau emulator, atau gunakan mode interaktif lagi dalam pratinjau. Lihat hasil perubahan imageVector pada daftar item berdasarkan status expanded.

    de5dc4a953f11e65.gif
    Pratinjau WoofPreview() Interaktifitas Tombol Ikon Luaskan

Menambahkan animasi

  1. Di MainActivity.kt, di DogItem(), tambahkan parameter modifier ke tata letak Column.
  2. Buat rantai pengubah dengan pengubah animateContentSize untuk menganimasikan perubahan ukuran (tinggi item daftar).
  3. @Composable
    fun DogItem(
       dog: Dog,
       modifier: Modifier = Modifier
    ) {
       ...
       Card(
           ...
       ) {
           Column(
               modifier = Modifier
                   .animateContentSize()
           ){
               ...
           }
       }
    }
  4. 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.
  5. import androidx.compose.animation.core.Spring
    import androidx.compose.animation.core.spring
    
    Column(
       modifier = Modifier
           .animateContentSize(
               animationSpec = spring(
                   dampingRatio = Spring.DampingRatioNoBouncy,
                   stiffness = Spring.StiffnessMedium
               )
           )
    )
  6. Lihat WoofPreview() di panel Design, dan gunakan mode interaktif atau jalankan aplikasi di emulator atau perangkat untuk melihat cara kerja animasi pegas.

    c0d0a52463332875.gif
    Pratinjau WoofPreview() Penambahan Animasi untuk Interaktifitas

(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.
  1. Dalam DogItem(), deklarasikan warna dan delegasikan inisialisasinya ke fungsi animateColorAsState().
  2. import androidx.compose.animation.animateColorAsState
    
    @Composable
    fun DogItem(
       dog: Dog,
       modifier: Modifier = Modifier
    ) {
       var expanded by remember { mutableStateOf(false) }
       val color by animateColorAsState()
       ...
    }
  3. 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.
  4. 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,
       )
       ...
    }
  5. Setel color sebagai pengubah latar belakang ke Column.
  6. @Composable
    fun DogItem(
       dog: Dog,
       modifier: Modifier = Modifier
    ) {
       ...
       Card(
           ...
       ) {
           Column(
               modifier = Modifier
                   .animateContentSize(
                       ...
                       )
                   )
                   .background(color = color)
           ) {...}
    }
  7. 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

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

Jika Anda ingin melihat kode solusi, lihat di GitHub.

Referensi


Komentar

Postingan Populer