Ilustrasi oleh Virginia Poltrack

Animasi di aplikasi Google I/O

Saya baru-baru ini menjadi bagian dari tim hebat yang bekerja untuk aplikasi Android Google I/O 2018. Ini adalah aplikasi pendamping konferensi, yang memungkinkan peserta dan orang-orang yang tinggal jauh untuk menemukan sesi, mem-build jadwal terpersonalisasi dan memesan tiket event (bila Anda cukup beruntung bisa berada di sana!). Kami build sejumlah fitur animasi menarik di aplikasi yang saya yakini sangat meningkatkan pengalaman. Kode untuk aplikasi ini baru saja dibuat open source dan saya ingin menyoroti beberapa instance ini dan beberapa detail implementasi yang menarik.

Beberapa elemen animasi di aplikasi I/O

Umumnya ada 3 tipe animasi yang kami gunakan dalam aplikasi:
  1. Animasi hero — digunakan untuk memperkuat branding dan menghadirkan momen kegembiraan
  2. Transisi layar
  3. Perubahan status
Saya ingin membahas beberapa hal ini secara mendetail.

Hitung mundur

Bagian dari peran aplikasi adalah untuk mem-build antisipasi dan kegembiraan yang meluap terhadap konferensi. Karena itu, tahun ini kami menyertakan hitung mundur animasi berukuran besar yang menampilkan waktu dimulainya konferensi di layar pembuka dan bagian Info. Ini juga merupakan peluang bagus untuk menyematkan branding event ke dalam aplikasi, membawa banyak karakter.

Hitung mundur dimulainya konferensi

Animasi ini dirancang oleh perancang gerakan dan diserahkan sebagai serangkaian file Lottie json: setiap 1 detik menampilkan angka yang menganimasikan ‘masuk’ lalu ‘keluar’. Format Lottie memudahkan kami untuk memasukkan file ke dalam assets bahkan menawarkan metode mudah seperti setMinAndMaxProgress yang memungkinkan kita untuk memainkan hanya paruh pertama atau terakhir dari sebuah animasi (untuk menampilkan angka yang menganimasikan masuk atau keluar).
Bagian yang menarik sebenarnya adalah mengatur semua animasi ini ke dalam hitung mundur secara menyeluruh. Untuk melakukannya, kami membuat CountdownView khusus yang merupakan ConstraintLayout yang cukup kompleks yang menyimpan sejumlah LottieAnimationViews. Di sini, kami membuat delegasi Kotlin untuk mengenkapsulasi permulaan animasi yang sesuai. Ini memungkinkan kami untuk menetapkan sebuah Int bagi setiap delegasi digit yang seharusnya ditampilkan dan delegasi akan menyiapkan dan memulai animasi. Kami memperluas delegasi ObservableProperty yang memastikan bahwa kami hanya menjalankan animasi ketika digit berubah. Loop animasi kemudian hanya memposting runnable setiap detik (ketika tampilan dilampirkan) yang mengalkulasi digit apa yang harus ditampilkan setiap tampilan dan mengupdate delegasi.

Reservasi

Salah satu tindakan kunci dari aplikasi ini adalah mengizinkan peserta memesan tiket. Karena itu, kami menampilkan aksi ini dengan jelas dalam FAB di layar detail sesi. Kami merasa bahwa penting untuk hanya melaporkan sesi telah dipesan setelah pemesanan berhasil diselesaikan di backend (tidak seperti tindakan yang kurang penting seperti membintangi sesi ketika kami langsung mengupdate UI). Ini mungkin memerlukan waktu beberapa saat selagi kita menunggu respons dari backend sehingga untuk membuatnya lebih responsif kami menggunakan ikon animasi untuk memberikan umpan balik bahwa kami sedang memprosesnya dan untuk memperlancar transisi ke status baru.

Memberikan umpan balik saat memesan tiket sesi

Hal ini dipersulit oleh fakta bahwa ada sejumlah status yang perlu digambarkan oleh ikon ini: sesi mungkin dapat dipesan, mereka mungkin sudah memesan tiket, bila sesi penuh maka daftar tunggu mungkin tersedia atau mereka mungkin berada di daftar tunggu, atau menonaktifkan pemesanan yang dekat dengan awal sesi. Hal ini mengakibatkan banyak permutasi dari berbagai status yang harus dianimasikan. Untuk menyederhanakan transisinya, kami memutuskan untuk selalu melewati status ‘working’; animasi jam pasir di atas. Oleh karena itu setiap transisi sebenarnya adalah sambungan: status 1 → working & working → status 2. Hal ini sangat menyederhanakan semuanya. Masing-masing animasi ini kami build menggunakan shapeshifter; lihat file avd_state_to_state di sini.
Untuk menampilkannya, kami menggunakan tampilan khusus dan sebuah AnimatedStateListDrawable (ASLD). Bila Anda belum pernah menggunakan ASLD sebelumnya, itu adalah (seperti namanya) versi animasi dari StateListDrawable yang kemungkinan besar telah Anda jumpai — yang memungkinkan Anda untuk tidak hanya menyediakan drawable berbeda tiap status tetapi juga transisi antar status (dalam bentuk AnimatedVectorDrawable atau AnimationDrawable). Berikut adalah drawable yang menetapkan gambar statis serta transisi masuk dan keluar status working untuk ikon reservasi.
Kami membuat tampilan khusus untuk mendukung status khusus kami sendiri. Tampilan menawarkan beberapa status standar seperti ditekan atau dipilih. Demikian pula, Anda bisa menetapkannya sendiri dan memiliki rute Tampilan ke setiap Drawable yang ditampilkan. Kami menetapkan state_reservable, state_reserved kami sendiri, dll. Kami kemudian membuat sebuah enum dari status-status yang berbeda ini, mengenkapsulasi status tampilan ditambah setiap atribut yang terkait seperti deskripsi konten yang berhubungan. Logika bisnis kita kemudian bisa dengan mudah menetapkan nilai yang tepat dari enum ini pada tampilan (melalui data binding) yang akan mengupdate status drawable, yang memulai animasi melalui ASLD. Kombinasi status khusus dan AnimatedStateListDrawable adalah cara yang cerdik untuk menerapkan hal ini, menjaga banyak status dalam layer deklaratif, menghasilkan kode tampilan yang minimal.

Transisi pembicara

Banyak transisi layar bekerja dengan baik bersama animasi jendela standar. Sebuah pengecualian dari hal ini adalah transisi ke layar detail pembicara. Layar ini menampilkan gambar pembicara di salah satu sisi transisi dan merupakan kandidat sempurna untuk transisi elemen bersama. Ini membantu memudahkan perubahan konteks antar layar.

Transisi elemen bersama

Ini adalah transisi elemen bersama yang cukup standar, menggunakan class ChangeBounds dan ArcMotion platform pada ImageView.
Yang lebih menarik adalah bagaimana memulai transisi ini sesuai dengan pola Event yang kita gunakan untuk navigasi. Pada dasarnya, pola ini memisahkan event masukan (seperti menge-tap pembicara) dari event navigasi, menempatkan ViewModel yang bertanggung jawab mengenai cara merespons input. Dalam hal ini, pemisahan ini berarti bahwa ViewModel mengekspos LiveData Event, yang hanya mengetahui ID pembicara yang akan dibuka. Memulai transisi elemen bersama membutuhkan View bersama, yang tidak kita miliki saat ini. Kita memecahkan masalah ini dengan menyimpan ID pembicara sebagai tag pada tampilan ketika terikat, sehingga tampilan nantinya bisa diambil kembali saat kita perlu membuka layar detail pembicara tertentu.

Filter

Bagian inti dari aplikasi konferensi adalah memfilter banyak event hingga ke hal-hal yang Anda minati. Setiap topik memiliki warna yang terkait dengan hal tersebut agar mudah dikenali dan kami menerima desain sangat bagus untuk 'chip' khusus yang digunakan saat memilih filter:

Chip filter animasi

Kami melihat Chip dari Material Components tetapi memilih untuk mengimplementasikan tampilan khusus kami sendiri untuk kontrol yang lebih besar terhadap tampilan dan animasi di antara status ‘checked’. Ini diimplementasikan menggunakan canvas drawing dan StaticLayout untuk menampilkan teks. Tampilan memiliki properti progress tunggal [0–1] yang memodelkan unchecked–checked. Untuk mengubah status, kami cukup menganimasikan nilai ini serta membuat tampilan tidak valid dan kode rendering secara linear menginterpolasi posisi dan ukuran elemen berdasarkan hal ini.
Awalnya ketika mengimplementasikan ini, saya membuat tampilan mengimplementasikan antarmuka Checkable dan memulai animasi ketika metode setChecked menetapkan status baru. Saat kami menampilkan beberapa filter dalam RecyclerView, ini memiliki efek menjalankan animasi yang tidak diinginkan bila filter yang dipilih di-scroll keluar dan tampilan diikat kembali ke filter tidak dipilih yang di-scroll masuk. Ups. Oleh karena itu, kami menambahkan metode terpisah untuk memulai animasi yang memungkinkan kita untuk membedakan antara animasi yang diaktifkan oleh klik dan update langsung ketika mengikat data baru ke tampilan.
Selain itu, ketika kami memperkenalkan tombol beralih animasi ini, kami menemukan bahwa itu tidak dapat diandalkan, yaitu bingkai yang turun. Apakah kode animasi saya harus disalahkan? Filter ini ditampilkan di BottomSheet di depan layar jadwal konferensi utama. Saat filter diaktifkan, kami memulai logika pemfilteran untuk diterapkan ke jadwal (dan mengupdate jumlah event yang sesuai dalam judul lembar filter). Kemudian penjelajahan systrace dimulai, kami memutuskan bahwa masalahnya terjadi ketika filter diterapkan, ViewPager dari RecyclerView yang menampilkan jadwal sebagaimana mestinya dan diupdate ke data yang baru dikirim. Hal ini menyebabkan sejumlah tampilan digelembungkan lalu diikat. Semua pekerjaan ini menghabiskan bujet bingkai kami… tetapi jadwal mengupdate tidak terlihat karena terletak di belakang lembar filter. Kami membuat keputusan untuk menunda menjalankan pemfilteran yang sebenarnya hingga animasi berjalan, kami mengorbankan beberapa kerumitan implementasi untuk pengalaman pengguna yang lebih mulus. Awalnya saya mengimplementasikannya menggunakan postDelayed, tetapi ini menyebabkan masalah bagi pengujian UI. Sebagai gantinya, kami mengubah metode yang memulai animasi untuk menerima lambda untuk dijalankan di akhir. Hal ini memungkinkan kami untuk lebih mengindahkan setelan animasi pengguna dan menguji eksekusi dengan benar.

Ayo Gunakan Animasi

Secara keseluruhan saya merasa animasi benar-benar berkontribusi untuk pengalaman, karakter, branding, dan responsivitas aplikasi. Semoga postingan ini bisa membantu menjelaskan mengapa dan bagaimana mereka digunakan dan memberi Anda petunjuk untuk implementasinya.



Menganimasikan Jadwal awalnya dipublikasikan di Developer Android di Medium, di sini pengguna terus melanjutkan konversasi dengan menandai dan merespons cerita ini.