Animasi Sederhana dengan Jetpack Compose

1. Sebelum memulai

Dalam codelab ini, Anda akan mempelajari cara menambahkan animasi sederhana ke aplikasi Android. Animasi dapat membuat aplikasi Anda lebih interaktif, menarik, dan mudah diinterpretasikan oleh pengguna. Menganimasikan setiap perubahan di layar yang penuh informasi dapat membantu pengguna melihat perubahan yang terjadi.

Ada banyak jenis animasi yang dapat digunakan di antarmuka pengguna aplikasi. Item dapat perlahan semakin jelas saat muncul dan perlahan memudar saat menghilang, dapat bergerak ke dalam atau keluar layar, atau dapat berubah dengan cara yang menarik. Hal ini membantu membuat UI aplikasi menjadi ekspresif dan mudah digunakan.

Animasi juga dapat membuat aplikasi Anda terlihat lebih menarik dengan tampilan dan nuansa elegan, serta membantu pengguna pada saat yang sama.

Prasyarat

  • Pengetahuan tentang Kotlin, termasuk fungsi, lambda, dan composable stateless.
  • Pengetahuan dasar tentang cara membuat tata letak di Jetpack Compose.
  • Pengetahuan dasar tentang cara membuat daftar di Jetpack Compose.
  • Pengetahuan dasar tentang Desain Material.

Yang akan Anda pelajari

  • Cara membuat animasi pegas sederhana dengan Jetpack Compose.

Yang akan Anda build

Yang Anda butuhkan

  • Versi stabil terbaru Android Studio.
  • Koneksi internet untuk mendownload kode awal.

2. Ringkasan Aplikasi

Dalam codelab Penerapan Tema Material dengan Jetpack Compose, Anda membuat aplikasi Woof menggunakan Desain Material yang menampilkan daftar anjing dan informasinya.

36c6cabd93421a92.png

Dalam codelab ini, Anda akan menambahkan animasi ke aplikasi Woof. Anda akan menambahkan informasi hobi yang akan ditampilkan saat meluaskan item daftar. Anda juga akan menambahkan animasi pegas untuk menganimasikan item daftar yang diperluas.

c0d0a52463332875.gif

Mendapatkan kode awal

Untuk memulai, download kode awal:

Atau, Anda dapat membuat clone repositori GitHub untuk kode tersebut:

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-woof.git
$ cd basic-android-kotlin-compose-training-woof
$ git checkout material

Anda dapat menjelajahi kode di repositori GitHub Woof app.

3. Menambahkan ikon luaskan

Di bagian ini, Anda akan menambahkan ikon Luaskan 30c384f00846e69b.png dan Ciutkan f88173321938c003.png ke aplikasi.

def59d71015c0fbe.png

Ikon

Ikon adalah simbol yang dapat membantu pengguna memahami antarmuka pengguna dengan menyampaikan fungsi yang diinginkan secara visual. Aplikasi ini sering kali mengambil inspirasi dari objek di dunia nyata yang dialami secara langsung oleh pengguna. Desain ikon sering kali mengurangi tingkat detail hingga jumlah minimum yang diperlukan agar mudah dikenali oleh pengguna. Misalnya, pensil dalam dunia nyata digunakan untuk menulis, sehingga ikon bandingannya biasanya menunjukkan buat atau edit.

Pensil pada buku catatan Foto oleh Angelina Litvin di Unsplash

Ikon pensil hitam dan putih

Desain Material menyediakan sejumlah ikon yang diatur dalam kategori umum untuk sebagian besar kebutuhan Anda.

Library Ikon Material

Menambahkan dependensi Gradle

Tambahkan dependensi library material-icons-extended ke project Anda. Anda akan menggunakan ikon Icons.Filled.ExpandLess 30c384f00846e69b.png dan Icons.Filled.ExpandMore f88173321938c003.png dari library ini.

  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:
implementation("androidx.compose.material:material-icons-extended")

Menambahkan composable ikon

Tambahkan fungsi untuk menampilkan ikon Luaskan dari library ikon Material dan gunakan sebagai tombol.

  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:
@Composable
private fun DogItemButton(
   expanded: Boolean,
   onClick: () -> Unit,
   modifier: Modifier = Modifier
) {
 

}
  1. Di dalam fungsi DogItemButton(), tambahkan composable IconButton() yang menerima parameter bernama onClick, lambda menggunakan sintaksis lambda di akhir, yang dipanggil saat ikon ini ditekan dan modifier opsional. Tetapkan IconButton's onClick dan modifier value parameters sama dengan yang diteruskan ke DogItemButton.
@Composable
private fun DogItemButton(
   expanded: Boolean,
   onClick: () -> Unit,
   modifier: Modifier = Modifier
){
   IconButton(
       onClick = onClick,
       modifier = modifier
   ) {

   }
}
  1. Di dalam blok lambda IconButton(), tambahkan composable Icon dan tetapkan imageVector value-parameter ke Icons.Filled.ExpandMore. Inilah yang akan ditampilkan di akhir item daftar f88173321938c003.png. Android Studio menampilkan peringatan untuk parameter composable Icon() yang akan Anda perbaiki di langkah berikutnya.
import androidx.compose.material.icons.filled.ExpandMore
import androidx.compose.material.icons.Icons
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton

IconButton(
   onClick = onClick,
   modifier = modifier
) {
   Icon(
       imageVector = Icons.Filled.ExpandMore
   )
}
  1. Tambahkan parameter nilai tint, dan setel warna ikon ke MaterialTheme.colorScheme.secondary. Tambahkan parameter bernama contentDescription, dan setel ke resource string R.string.expand_button_content_description.
IconButton(
   onClick = onClick,
   modifier = modifier
){
   Icon(
       imageVector = Icons.Filled.ExpandMore,
       contentDescription = stringResource(R.string.expand_button_content_description),
       tint = MaterialTheme.colorScheme.secondary
   )
}

Menampilkan ikon

Tampilkan composable DogItemButton() dengan menambahkannya ke tata letak.

  1. Di awal DogItem(), tambahkan var untuk menyimpan status yang diluaskan dari item daftar. Setel nilai awal ke false.
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) }
  1. Tampilkan tombol ikon di dalam item daftar. Pada composable DogItem(), di akhir blok Row, setelah panggilan ke DogInformation(), tambahkan DogItemButton(). Teruskan status expanded dan lambda kosong untuk callback. Anda akan menentukan tindakan onClick di langkah berikutnya.
Row(
   modifier = Modifier
       .fillMaxWidth()
       .padding(dimensionResource(R.dimen.padding_small))
) {
   DogIcon(dog.imageResourceId)
   DogInformation(dog.name, dog.age)
   DogItemButton(
       expanded = expanded,
       onClick = { /*TODO*/ }
   )
}
  1. Lihat WoofPreview() di panel Design.

5bbf09cd2828b6.png

Perhatikan bahwa tombol luaskan tidak sejajar dengan bagian akhir item daftar. Anda akan memperbaikinya di langkah berikutnya.

Menyejajarkan tombol luaskan

Untuk menyejajarkan tombol luaskan dengan bagian akhir item daftar, Anda perlu menambahkan pengatur jarak dalam tata letak dengan atribut Modifier.weight().

Di aplikasi Woof, setiap baris item daftar berisi gambar anjing, informasi anjing, dan tombol luaskan. Anda akan menambahkan composable Spacer sebelum tombol luaskan dengan bobot 1f untuk menyejajarkan ikon tombol dengan benar. Karena pengatur jarak adalah satu-satunya elemen turunan berbobot di baris, pengatur jarak akan mengisi ruang yang tersisa di baris setelah mengukur lebar elemen turunan tanpa bobot lainnya.

733f6d9ef2939ab5.png

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.
import androidx.compose.foundation.layout.Spacer

Row(
   modifier = Modifier
       .fillMaxWidth()
       .padding(dimensionResource(R.dimen.padding_small))
) {
   DogIcon(dog.imageResourceId)
   DogInformation(dog.name, dog.age)
   Spacer(modifier = Modifier.weight(1f))
   DogItemButton(
       expanded = expanded,
       onClick = { /*TODO*/ }
   )
}
  1. Lihat WoofPreview() di panel Design. Perhatikan bahwa tombol luaskan kini sejajar dengan bagian akhir item daftar.

8df42b9d85a5dbaa.png

4. Menambahkan Composable untuk menampilkan hobi

Dalam tugas ini, Anda akan menambahkan composable Text untuk menampilkan informasi hobi anjing.

bba8146c6332cc37.png

  1. Buat fungsi composable baru yang disebut DogHobby() yang menggunakan ID resource string hobi anjing dan Modifier opsional.
@Composable
fun DogHobby(
   @StringRes dogHobby: Int,
   modifier: Modifier = Modifier
) {
}
  1. Di dalam fungsi DogHobby(), buat Column dan teruskan pengubah yang diteruskan ke DogHobby().
@Composable
fun DogHobby(
   @StringRes dogHobby: Int,
   modifier: Modifier = Modifier
){
   Column(
       modifier = modifier
   ) { 

   }
}
  1. 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.

Column(
   modifier = modifier
) {
   Text(
       text = stringResource(R.string.about),
       style = MaterialTheme.typography.labelSmall
   )
   Text(
       text = stringResource(dogHobby),
       style = MaterialTheme.typography.bodyLarge
   )
}
  1. Di DogItem(), composable DogHobby() akan berada di bawah Row yang berisi DogIcon(), DogInformation(), Spacer(), dan DogItemButton(). Untuk melakukannya, gabungkan Row dengan Column sehingga hobi dapat ditambahkan di bawah Row.
Column() {
   Row(
       modifier = Modifier
           .fillMaxWidth()
           .padding(dimensionResource(R.dimen.padding_small))
   ) {
       DogIcon(dog.imageResourceId)
       DogInformation(dog.name, dog.age)
       Spacer(modifier = Modifier.weight(1f))
       DogItemButton(
           expanded = expanded,
           onClick = { /*TODO*/ }
       )
   }
}
  1. 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().
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)
       )
   )
}

Fungsi DogItem() lengkap akan terlihat seperti ini:

@Composable
fun DogItem(
   dog: Dog,
   modifier: Modifier = Modifier
) {
   var expanded by remember { mutableStateOf(false) }
   Card(
       modifier = modifier
   ) {
       Column() {
           Row(
               modifier = Modifier
                   .fillMaxWidth()
                   .padding(dimensionResource(R.dimen.padding_small))
           ) {
               DogIcon(dog.imageResourceId)
               DogInformation(dog.name, dog.age)
               Spacer(Modifier.weight(1f))
               DogItemButton(
                   expanded = expanded,
                   onClick = { /*TODO*/ },
               )
           }
           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)
               )
           )
       }
   }
}
  1. Lihat WoofPreview() di panel Design. Perhatikan hobi anjing yang ditampilkan.

Pratinjau Woof dengan item daftar yang diluaskan

5. Menampilkan atau menyembunyikan hobi saat tombol diklik

Aplikasi Anda memiliki tombol Luaskan untuk setiap item daftar, namun belum ada tindakan apa pun. Di bagian ini, Anda akan menambahkan opsi untuk menyembunyikan atau menampilkan informasi hobi saat pengguna mengklik tombol luaskan.

  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.
DogItemButton(
   expanded = expanded,
   onClick = { expanded = !expanded }
)
  1. Di fungsi DogItem(), gabungkan panggilan fungsi DogHobby() dengan pemeriksaan if pada boolean 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)
                   )
               )
           }
       }
   }
}

Sekarang, informasi hobi anjing hanya ditampilkan jika nilai expanded adalah true.

  1. Pratinjau dapat menunjukkan tampilan UI, dan Anda juga dapat berinteraksi dengannya. Untuk berinteraksi dengan pratinjau UI, arahkan kursor ke atas teks WoofPreview di Panel Design, lalu klik tombol Interactive Mode 42379dbe94a7a497.png di sudut kanan atas panel Design. Tombol ini akan memulai pratinjau dalam mode interaktif.

74e1624d68fb4131.png

  1. Berinteraksilah dengan pratinjau dengan mengklik tombol luaskan. Perhatikan bahwa informasi hobi anjing disembunyikan dan ditampilkan saat Anda mengklik tombol luaskan.

Animasi item daftar Woof yang diperluas dan diciutkan

Perhatikan bahwa ikon tombol luaskan tetap sama saat item daftar diluaskan. Untuk pengalaman pengguna yang lebih baik, ubahlah ikon sehingga ExpandMore menampilkan panah bawah c761ef298c2aea5a.png, dan ExpandLess menampilkan panah atas b380f933be0b6ff4.png.

  1. Dalam fungsi DogItemButton(), tambahkan pernyataan if yang memperbarui nilai imageVector berdasarkan status expanded sebagai berikut:
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,
           ...
       )
   }
}

Perhatikan cara Anda menulis if-else di cuplikan kode sebelumnya.

if (expanded) Icons.Filled.ExpandLess else Icons.Filled.ExpandMore

Hal ini sama dengan menggunakan tanda kurung kurawal { } dalam kode berikut:

if (expanded) {

`Icons.Filled.ExpandLess`

} else {

`Icons.Filled.ExpandMore`

}

Tanda kurung kurawal bersifat opsional jika ada satu baris kode untuk pernyataan if-else.

  1. Jalankan aplikasi di perangkat atau emulator, atau gunakan mode interaktif lagi dalam pratinjau. Perhatikan bahwa ikon berganti-ganti antara ikon ExpandMore c761ef298c2aea5a.png dan ExpandLess b380f933be0b6ff4.png.

de5dc4a953f11e65.gif

Bagus, Anda telah memperbarui ikon!

Saat memperluas item daftar, apakah Anda menyadari bahwa tingginya tiba-tiba berubah? Perubahan tinggi yang tiba-tiba tentu membuat aplikasi tidak menarik. Untuk mengatasinya, tambahkan animasi ke aplikasi seperti yang akan dijelaskan di bagian berikutnya.

6. Menambahkan animasi

Animasi dapat menambahkan petunjuk visual yang memberi tahu pengguna tentang apa yang terjadi di aplikasi Anda. Fitur ini berguna terutama saat status UI berubah, misalnya saat konten baru dimuat atau tindakan baru tersedia. Animasi juga dapat membuat aplikasi Anda tampil lebih menarik.

Pada bagian ini, Anda akan menambahkan animasi pegas untuk menganimasikan perubahan tinggi item daftar.

Animasi Pegas

Animasi pegas adalah animasi berbasis fisika yang digerakkan oleh gaya pegas. Dengan animasi pegas, nilai dan kecepatan gerakan dihitung berdasarkan gaya pegas yang diterapkan.

Misalnya, jika Anda menarik ikon aplikasi mengitari layar lalu melepaskannya dengan mengangkat jari, ikon akan kembali ke lokasi semula dengan gaya yang tak terlihat.

Animasi berikut menunjukkan efek pegas. Setelah jari dilepaskan dari ikon, ikon akan kembali ke posisi semula, yang meniru pegas.

Efek pelepasan pegas

Efek pegas

Gaya pegas digerakkan oleh dua properti berikut:

  • Rasio redaman: Kelenturan pegas.
  • Tingkat kekakuan: Kekakuan pegas, yaitu seberapa cepat pegas bergerak menuju ujungnya.

Berikut adalah beberapa contoh animasi dengan berbagai rasio redaman dan tingkat kekakuan.

Efek pegasPantulan Tinggi

Efek pegasTanpa Pantulan

Kekakuan Tinggi

kekakuan rendah Kekakuan Sangat Rendah

Lihat panggilan fungsi DogHobby() dalam fungsi composable DogItem(). Informasi hobi anjing disertakan dalam komposisi, berdasarkan nilai boolean expanded. Tinggi item daftar berubah, bergantung pada apakah informasi hobi terlihat atau tersembunyi. Saat ini, transisi tersebut belum berjalan lancar. Di bagian ini, Anda akan menggunakan pengubah animateContentSize untuk menambahkan transisi yang lebih lancar antara status yang diperluas dan tidak diperluas.

// No need to copy over
@Composable
fun DogItem(...) {
  ...
    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)
          )
      )
   }
}
  1. Di MainActivity.kt, di DogItem(), tambahkan parameter modifier ke tata letak Column.
@Composable
fun DogItem(
   dog: Dog, 
   modifier: Modifier = Modifier
) {
   ...
   Card(
       ...
   ) {
       Column(
          modifier = Modifier
       ){
           ...
       }
   }
}
  1. Buat rantai pengubah dengan pengubah animateContentSize untuk menganimasikan perubahan ukuran (tinggi item daftar).
import androidx.compose.animation.animateContentSize

Column(
   modifier = Modifier
       .animateContentSize()
)

Dalam implementasi saat ini, Anda menganimasikan tinggi item daftar di aplikasi Anda. Namun, animasinya sangat halus sehingga sulit untuk dilihat saat Anda menjalankan aplikasi. Untuk mengatasi hal ini, gunakan parameter animationSpec opsional yang memungkinkan Anda menyesuaikan animasi.

  1. 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.
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring

Column(
   modifier = Modifier
       .animateContentSize(
           animationSpec = spring(
               dampingRatio = Spring.DampingRatioNoBouncy,
               stiffness = Spring.StiffnessMedium
           )
       )
)
  1. Lihat WoofPreview() di panel Design, dan gunakan mode interaktif atau jalankan aplikasi di emulator atau perangkat untuk melihat cara kerja animasi pegas.

c0d0a52463332875.gif

Anda berhasil! Nikmati aplikasi yang menarik dengan animasi.

7. (Opsional) Bereksperimen dengan animasi lain

animate*AsState

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.

Compose menyediakan fungsi animate*AsState() untuk Float, Color, Dp, Size, Offset, dan Int, sebagai contoh. Anda dapat dengan mudah menambahkan dukungan untuk jenis data lainnya menggunakan animateValueAsState() yang menggunakan jenis umum.

Coba gunakan fungsi animateColorAsState() untuk mengubah warna saat item daftar diluaskan.

  1. Dalam DogItem(), deklarasikan warna dan delegasikan inisialisasinya ke fungsi animateColorAsState().
import androidx.compose.animation.animateColorAsState

@Composable
fun DogItem(
   dog: Dog,
   modifier: Modifier = Modifier
) {
   var expanded by remember { mutableStateOf(false) }
   val color by animateColorAsState()
   ...
}
  1. 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.
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,
   )
   ...
}
  1. Setel color sebagai pengubah latar belakang ke Column.
@Composable
fun DogItem(
   dog: Dog, 
   modifier: Modifier = Modifier
) {
   ...
   Card(
       ...
   ) {
       Column(
           modifier = Modifier
               .animateContentSize(
                   ...
                   )
               )
               .background(color = color)
       ) {...}
}
  1. Lihat bagaimana warna berubah saat item daftar diluaskan. Item daftar yang tidak diluaskan memiliki warna primaryContainer dan item daftar yang diluaskan berwarna tertiaryContainer.

Animasi animateAsState

8. Mendapatkan kode solusi

Guna mendownload kode untuk codelab yang sudah selesai, Anda dapat menggunakan perintah git ini:

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-woof.git

Atau, Anda dapat mendownload repositori sebagai file ZIP, lalu mengekstraknya, dan membukanya di Android Studio.

Jika Anda ingin melihat kode solusi, lihat di GitHub.

9. Kesimpulan

Selamat! Anda telah menambahkan tombol untuk menyembunyikan dan menampilkan informasi tentang anjing tersebut. Anda telah meningkatkan pengalaman pengguna menggunakan animasi pegas. Anda juga telah mempelajari cara menggunakan mode interaktif di panel Design.

Anda juga dapat mencoba berbagai jenis Animasi Jetpack Compose. Jangan lupa untuk membagikan karya Anda di media sosial dengan #AndroidBasics.

Mempelajari lebih lanjut