Tugas 9 PPB
TUGAS 9 PPB
MATERIAL THEMING - WOOF APPLICATION
Nama: Muhammad Lintang Panjerino
NRP: 5025201045
Kelas: PPB I
Tahun: 2024
Link Github: Tugas 9 - Material Theming Woof Application
Pada Tugas 9 PPB kali ini, diberikan tugas untuk membuat proyek Material Theming - Woof Application dengan menggunakan Jetpack Compose pada Android Studio. Secara garis besar, aplikasi Material Theming - Woof Application ini adalah sebuah aplikasi yang memuat daftar beberapa card yang berisi foto, nama, dan gambar hewan peliharaan, yaitu anjing. Daftar anjing tersebut dapat di-scroll dan karena merupakan daftar anjing, aplikasi ini diberi nama Woof. Proyek ini bertujuan untuk mempelajari lebih dalam tentang konsep Card, Lazy Column, List, Color, Shape, Typography, dan Top Bar pada Jetpack Compose.
Untuk memulai pengerjaan proyek, langkah awal yang dilakukan adalah mengunduh file zip dari repository github berikut https://github.com/google-developer-training/basic-android-kotlin-compose-training-woof dan pilih branch dengan nama "starter". Kemudian ekstrak file zip tersebut pada folder lokal PC/laptop dan ubah nama folder hasil ekstrak menjadi "Woof Application". Pada Android Studio, pilih "File" > pilih "Open" > pilih file tempat folder projek diekstrak > pilih "OK".
Setelah aplikasi dijalankan melalui emulator dengan perintah "Run app", berikut adalah tampilan awal atau starter dari aplikasi Woof.
1. Menambahkan Warna
Hal pertama yang harus dilakukan adalah mengubah skema warna pada Woof Application. Skema warna adalah kombinasi warna yang digunakan oleh aplikasi. Kombinasi warna yang berbeda pada suatu aplikasi akan membangkitkan suasana hati yang berbeda dan memengaruhi perasaan orang saat menggunakan aplikasi. Berikut adalah langkah-langkah pada bagian menambahkan warna
Menambahkan palet warna ke tema
a. Buka file Color.kt dan ubah isi file dengan kode di bawah ini untuk skema warna aplikasi.
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)
b. Buka file Theme.kt dan ubah isi file dengan kode di bawah ini untuk menambahkan warna baru ke tema.
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, shapes = Shapes, typography = Typography, 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
a. Dalam fungsi composable DogItem(), panggil fungsi Card() dan masukkan fungsi Row() ke dalamnya.
b. Karena Card adalah composable turunan pertama di DogItem(), teruskan modifier dari DogItem() ke Card, dan update modifier Row menjadi instance baru Modifier.
Card(modifier = modifier) { Row( modifier = Modifier .fillMaxWidth() .padding(dimensionResource(id = R.dimen.padding_small)) ) { DogIcon(dog.imageResourceId) DogInformation(dog.name, dog.age) } }
Mengatur ukuran dengan file dimensi
a. Buka file dimens.xml melalui app > res > values > dimens.xml. Terdapat fungsi yang menyimpan nilai dimensi untuk padding_small, padding_medium, dan image_size. Dimensi ini dapat digunakan di seluruh aplikasi Woof.
<resources> <dimen name="padding_small">8dp</dimen> <dimen name="padding_medium">16dp</dimen> <dimen name="image_size">64dp</dimen> </resources>
b. Pada file MainActivity.kt di fungsi WoofApp(), tambahkan modifier dengan padding_small dalam panggilan ke DogItem(). Untuk menggunakan padding_small, gunakan dimensionResource(id = R.dimen.padding_small).
@Composable fun WoofApp() { Scaffold { it -> LazyColumn(contentPadding = it) { items(dogs) { DogItem( dog = it, modifier = Modifier.padding(dimensionResource(R.dimen.padding_small)) ) } } } }
Preview Light Theme dan Dark Theme
Untuk melihat preview dari tema terang dan tema gelap, panggil fungsi anotasi @Preview dan @Composable di atas fungsi yang memanggil aplikasi dengan tema terang/gelap.
a. Buat fungsi baru bernama WoofPreview() dan WoofDarkThemePreview(), anotasikan keduanya dengan @Preview dan @Composable.
b. Di dalam kedua fungsi, tambahkan WoofTheme(). Kemudian, isi parameter darkTheme ke false untuk tema terang dan isi parameter darkTheme ke true untuk tema gelap.
c. Panggil fungsi WoofApp() di dalam WoofTheme().
@Preview @Composable fun WoofPreview() { WoofTheme(darkTheme = false) { WoofApp() } }
@Preview @Composable fun WoofDarkThemePreview() { WoofTheme(darkTheme = true) { WoofApp() } }
2. Menambahkan Bentuk
Menerapkan bentuk akan sangat memengaruhi tampilan composable. Banyak bentuk ditentukan menggunakan fungsi RoundedCornerShape, yang menggambarkan persegi panjang dengan sudut membulat. Angka yang diteruskan menentukan kebulatan sudut. Nilai RoundedCornerShape(0.dp) akan membentuk persegi panjang yang tidak memiliki sudut membulat, sedangkan nilai RoundedCornerShape(50.dp) akan membentuk sudut yang membulat sepenuhnya. File Shape.kt digunakan untuk menentukan bentuk komponen di Compose. Ada tiga jenis komponen: kecil, sedang, dan besar. Di bagian ini, komponen Card akan diubah, yang ditentukan sebagai ukuran medium.
Membentuk gambar anjing menjadi bentuk lingkaran
a. Pada file Shape.kt perhatikan bahwa parameter small diisi nilai RoundedCornerShape(50.dp). Parameter ini akan digunakan untuk membentuk gambar anjing menjadi lingkaran.
val Shapes = Shapes( small = RoundedCornerShape(50.dp), )
b. Pada MainActivity.kt di fungsi DogIcon(), tambahkan atribut clip ke modifier dari Image yang akan memotong gambar menjadi sebuah bentuk. Isi atribut clip dengan nilai MaterialTheme.shapes.small.
c. Masih di fungsi DogIcon(), untuk membuat semua foto menjadi lingkaran, tambahkan atribut ContentScale.Crop untuk memotong gambar agar sesuai dan menjadi bentuk lingkaran sempurna. Atribut contentScale bukanlah atribut dari modifier, melainkan atribut dari Image.
Berikut adalah kode lengkap dari fungsi DogIcon().
@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 ) }
Berikut adalah tampilan gambar yang ada di WoofPreview(). Gambar/ikon anjing sudah berbentuk lingkaran sempurna.
Mengubah bentuk item card
Buja file Shape.kt. Card adalah komponen medium, sehingga objek Shapes perlu ditambahkan parameter medium. Untuk aplikasi Woof ini, pojok kanan atas dan kiri bawah item daftar akan dibentuk sedikit melengkung, tetapi tidak membentuk lingkaran penuh. Oleh karena itu, isi nilai 16.dp ke atribut medium.
medium = RoundedCornerShape(bottomStart = 16.dp, topEnd = 16.dp)
Secara default, Card sudah menggunakan ukuran medium pada objek, jadi tidak perlu untuk menyetel secara eksplisit ke bentuk medium.
3. Menambahkan TipografiPlatform Android menyediakan berbagai font, tetapi mungkin developer ingin menyesuaikan aplikasi dengan font yang tidak disediakan secara default. Font kustom dapat menambah karakteristik dan digunakan untuk branding. Di bagian ini, akan ditambahkan 3 font khusus, yaitu Abril Fatface, Montserrat Bold, dan Montserrat Regular. Beberapa properti/atribut yang akan digunakan adalah displayLarge, displayMedium, dan bodyLarge.
Membuat Direktori Resource Android untuk font
a. Di tampilan project Android Studio, klik kanan folder res.
b. Pilih New > Android Resource Directory.
c. Beri nama direktori font, isi tipe Resource font, dan klik OK.
d. Buka direktori resource font baru yang terletak di res > font.
Mendownload custom font
a. Buka https://fonts.google.com/.
b. Telusuri Montserrat, lalu klik Download family.
c. Buka file zip.
d. Buka folder Montserrat yang didownload. Di dalam folder static, temukan file 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 di Android Studio.
e. Di folder font, ganti nama Montserrat-Bold.ttf menjadi montserrat_bold.ttf dan ganti nama Montserrat-Regular.ttf menjadi montserrat_regular.ttf.
f. Telusuri Abril Fatface dan klik Download family.
g. Buka folder Abril_Fatface yang telah didownload. Pilih file AbrilFatface-Regular.ttf, lalu tarik ke direktori resource font.
h. Di folder font, ganti nama Abril_Fatface_Regular.ttf menjadi abril_fatface_regular.ttf.
Inisialisasi font
a. Buka ui.theme > Type.kt. Lakukan inisialisasi dari font yang sudah didownload dan di atas val Typography. Pertama, lakukan inisialisasi Abril Fatface dengan memanggil fungsi FontFamily dan meneruskan Font dengan file font abril_fatface_regular.
b. Lakukan inisialisasi Montserrat dengan memanggil fungsi FontFamily dan meneruskan Font dengan file font montserrat_regular. Untuk montserrat_bold, sertakan juga FontWeight.Bold.
@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 ) } }
c. Untuk atribut displayLarge, panggil fungsi TextStyle, lalu isi fontFamily, fontWeight, dan fontSize dengan nilai yang ditentukan. Untuk kasus ini, semua teks yang ditetapkan ke displayLarge akan memiliki Abril Fatface sebagai font, dengan ketebalan font normal, dan fontSize 36.sp. Ulangi proses yang sama untuk displayMedium, labelSmall, dan bodyLarge.
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 aplikasi
a. 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.
@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 ) } }
b. Pada WoofPreview(), nama anjing akan menampilkan font Montserrat tebal dengan ukuran 20.sp, dan usia anjing menampilkan font Montserrat normal dengan ukuran 14.sp. Berikut adalah tampilan dari WoofPreview().
4. Menambahkan Panel Atas Aplikasi
Menambahkan gambar dan teks ke panel atas
a. Di MainActivity.kt, buat composable bernama WoofTopAppBar() dengan modifier opsional.
@Composable fun WoofTopAppBar(modifier: Modifier = Modifier) { }
b. Pada fungsi WoofApp(), nilai contentWindowInsets diteruskan ke LazyColumn sebagai contentPadding.
c. Dalam Scaffold, tambahkan atribut topBar dan isi dengan WoofTopAppBar().
@Composable fun WoofApp() { Scaffold( topBar = { WoofTopAppBar() } ) { it -> LazyColumn(contentPadding = it) { items(dogs) { DogItem( dog = it, modifier = Modifier.padding(dimensionResource(R.dimen.padding_small)) ) } } } }
d. Di dalam composable WoofTopAppBar(), panggil fungsi CenterAlignedTopAppBar() dan isi parameter modifier dengan nilai modifier pada fungsi WoofTopAppBar().
e. Isi parameter title dengan memanggil fungsi Row yang akan menyimpan Image dan Text dari CenterAlignedTopAppBar().
f. Tambahkan logo Image ke Row. Set ukuran gambar di modifier dengan nilai image_size yang ada pada file dimens.xml dan juga set padding dengan nilai padding_small yang ada pada file dimens.xml. Untuk mengisi gambar/ikon aplikasi, di atribut painter panggil fungsi painterResource() dengan nilai R.drawable.ic_woof_logo. Sedangkan atribut contentDescription diisi dengan nilai null.
g. Selanjutnya, tambahkan fungsi Text di dalam Row setelah Image. Untuk isi teks, gunakan nama aplikasi yang berada di file strings.xml dengan cara memanggil fungsi stringResource() dan isi dengan nilai R.string.app_name. Isi atribut style pada teks ke displayLarge karena nama aplikasi singkat dan penting.
h. Untuk memperbaiki posisi ikon aplikasi dan nama aplikasi yang tidak sejajar, di fungsi Row, tambahkan parameter verticalAlignment dengan nilai Alignment.CenterVertically.
Berikut adalah kode lengkap fungsi WoofTopAppBar().
@Composable fun WoofTopAppBar(modifier: Modifier = Modifier) { CenterAlignedTopAppBar( title = { Row( verticalAlignment = Alignment.CenterVertically ) { 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 ) } }, modifier = modifier ) }
Preview panel atas aplikasi
Berikut adalah preview dari panel atas aplikasi. Panel atas ini berisi ikon aplikasi yang berupa gambar telapak kaki anjing dan teks berupa nama aplikasi, yaitu Woof.
Hasil Akhir
Berikut adalah tampilan atau hasil akhir dari proyek Material Theming - Woof Application. Gambar di bawah menunjukkan perbedaan ketika awal aplikasi (starter) sebelum dimodifikasi dengan Material Theming dengan hasil akhir aplikasi setelah dimodifikasi dengan Material Theming.
Berikut adalah link video demo aplikasi Woof Application
Referensihttps://developer.android.com/courses/pathways/android-basics-compose-unit-3-pathway-3?hl=id
Komentar
Posting Komentar