
Halo, Habr! Tahun lalu, Ivan Škorić dari PSPDFKit berbicara di
MBLT DEV dengan laporan tentang membuat animasi di Android berdasarkan Kotlin dan perpustakaan RxJava.
Saya sekarang menggunakan teknik dari laporan dalam mengerjakan proyek saya, mereka banyak membantu. Di bawah cutscene adalah transkrip laporan dan video, sekarang Anda dapat memanfaatkan trik ini.
Animasi
Di Android, ada 4 kelas yang berlaku seolah-olah secara default:
- ValueAnimator - Kelas ini menyediakan mekanisme sinkronisasi sederhana untuk menjalankan animasi yang menghitung nilai animasi dan mengaturnya untuk Tampilan.
- ObjectAnimator adalah subkelas dari ValueAnimator yang memungkinkan Anda untuk mendukung animasi untuk properti objek.
- AnimatorSet digunakan untuk membuat urutan animasi. Misalnya, Anda memiliki urutan animasi:
- Lihat dedaunan di sebelah kiri layar.
- Setelah menyelesaikan animasi pertama, kami ingin melakukan animasi tampilan untuk tampilan lain, dll.
- ViewPropertyAnimator - Secara otomatis meluncurkan dan mengoptimalkan animasi untuk properti View yang dipilih. Kami terutama akan menggunakannya. Oleh karena itu, kami akan menggunakan API ini dan kemudian meletakkannya di RxJava sebagai bagian dari pemrograman reaktif.
ValueAnimator
Mari kita
menganalisis kerangka
ValueAnimator . Ini digunakan untuk mengubah nilai. Anda menentukan rentang nilai melalui
ValueAnimator.ofFloat untuk tipe float primitif dari 0 hingga 100. Tentukan nilai
Durasi dan mulai animasi.
Pertimbangkan sebuah contoh:
val animator = ValueAnimator.ofFloat(0f, 100f) animator.duration = 1000 animator.start() animator.addUpdateListener(object : ValueAnimator.AnimatorUpdateListener { override fun onAnimationUpdate(animation: ValueAnimator) { val animatedValue = animation.animatedValue as Float textView.translationX = animatedValue } })
Di sini kita menambahkan
UpdateListener dan dengan setiap pembaruan kita akan memindahkan Tampilan kita secara horizontal dan mengubah posisinya dari 0 menjadi 100, meskipun ini bukan cara yang sangat baik untuk melakukan operasi ini.
ObjectAnimator
Contoh implementasi animasi lainnya adalah ObjectAnimator:
val objectAnimator = ObjectAnimator.ofFloat(textView, "translationX", 100f) objectAnimator.duration = 1000 objectAnimator.start()
Kami memberinya perintah untuk mengubah parameter Tampilan spesifik ke nilai yang ditentukan ke Tampilan yang diinginkan dan mengatur waktu menggunakan metode
setDuration . Intinya adalah bahwa kelas Anda harus memiliki metode
setTranslationX , maka sistem akan menemukan metode ini melalui refleksi, dan kemudian View akan dianimasikan. Masalahnya adalah refleksi digunakan di sini.
Animatorset
Sekarang pertimbangkan kelas
AnimatorSet :
val bouncer = AnimatorSet() bouncer.play(bounceAnim).before(squashAnim1) bouncer.play(squashAnim1).before(squashAnim2) val fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f) fadeAnim.duration = 250 val animatorSet = AnimatorSet() animatorSet.play(bouncer).before(fadeAnim) animatorSet.start()
Bahkan, sangat tidak nyaman untuk digunakan, terutama untuk sejumlah besar objek. Jika Anda ingin membuat animasi yang lebih kompleks - misalnya, atur penundaan antara tampilan animasi, dan semakin banyak animasi yang ingin Anda lakukan, semakin sulit untuk mengendalikannya.
ViewPropertyAnimator
Kelas terakhir adalah
ViewPropertyAnimator . Ini adalah salah satu kelas terbaik untuk menjiwai View. Ini adalah API yang bagus untuk memperkenalkan rangkaian animasi yang Anda jalankan:
ViewCompat.animate(textView) .translationX(50f) .translationY(100f) .setDuration(1000) .setInterpolator(AccelerateDecelerateInterpolator()) .setStartDelay(50) .setListener(object : Animator.AnimatorListener { override fun onAnimationRepeat(animation: Animator) {} override fun onAnimationEnd(animation: Animator) {} override fun onAnimationCancel(animation: Animator) {} override fun onAnimationStart(animation: Animator) {} })
Kita memulai metode
ViewCompat.animate , yang mengembalikan
ViewPropertyAnimator , dan untuk
translationX animasi
kita menetapkan nilainya menjadi 50, untuk parameter
translatonY ke 100. Kemudian kita menentukan durasi animasi, serta interpolator. Interpolator menentukan urutan di mana animasi muncul. Contoh ini menggunakan interpolator yang mempercepat awal animasi dan menambahkan perlambatan di bagian akhir. Kami juga menambahkan penundaan untuk memulai animasi. Selain itu, kami memiliki
AnimatorListener . Dengannya, Anda dapat berlangganan ke acara tertentu yang terjadi selama animasi. Antarmuka ini memiliki 4 metode:
onAnimationStart ,
onAnimationCancel ,
onAnimationEnd ,
onAnimationRepeat .
Sebagai aturan, kami hanya tertarik untuk menyelesaikan animasi. Di API Level 16
ditambahkan denganEndAction:
.withEndAction({ //API 16+ //do something here where animation ends })
Di dalamnya, Anda dapat menentukan antarmuka
Runnable , dan setelah selesai menunjukkan animasi tertentu, suatu tindakan dilakukan.
Sekarang beberapa komentar tentang proses pembuatan animasi secara umum:
- Metode start () adalah opsional: segera setelah Anda memanggil metode animate () , serangkaian animasi diperkenalkan. Ketika ViewPropertyAnimator dikonfigurasi, sistem akan memulai animasi segera setelah siap untuk melakukannya.
- Hanya satu kelas ViewPropertyAnimator yang hanya dapat menghidupkan tampilan tertentu. Karena itu, jika Anda ingin melakukan beberapa animasi, misalnya, Anda ingin sesuatu bergerak, dan pada saat yang sama bertambah besar ukurannya, maka Anda perlu menentukan ini dalam satu animator.
Mengapa kami memilih RxJava?
Mari kita mulai dengan contoh sederhana. Misalkan kita membuat metode fadeIn:
fun fadeIn(view: View, duration: Long): Completable { val animationSubject = CompletableSubject.create() return animationSubject.doOnSubscribe { ViewCompat.animate(view) .setDuration(duration) .alpha(1f) .withEndAction { animationSubject.onComplete() } } }
Ini adalah solusi yang cukup primitif, dan untuk menerapkannya pada proyek Anda, Anda perlu mempertimbangkan beberapa nuansa.
Kami akan membuat
Subjek Completable , yang akan kami gunakan untuk menunggu animasi selesai, dan kemudian menggunakan metode
onComplete untuk mengirim pesan ke pelanggan. Untuk menjalankan animasi secara berurutan, Anda harus segera memulai animasi, tetapi segera setelah seseorang berlangganan. Dengan cara ini, beberapa animasi gaya reaktif dapat dijalankan secara berurutan.
Pertimbangkan animasi itu sendiri. Di dalamnya, kami mentransfer Lihat, di mana animasi akan dilakukan, dan juga menunjukkan durasi animasi. Dan karena animasi ini adalah penampilan, kita harus menunjukkan transparansi 1.
Mari kita coba menggunakan metode kita dan membuat animasi sederhana. Misalkan kita memiliki 4 tombol di layar, dan kami ingin menambahkan animasi untuk penampilan dengan durasi 1 detik:
val durationMs = 1000L button1.alpha = 0f button2.alpha = 0f button3.alpha = 0f button4.alpha = 0f fadeIn(button1, durationMs) .andThen(fadeIn(button2, durationMs)) .andThen(fadeIn(button3, durationMs)) .andThen(fadeIn(button4, durationMs)) .subscribe()
Hasilnya adalah kode ringkas tersebut. Dengan menggunakan operator dan kemudian,
Anda dapat menjalankan animasi secara berurutan. Saat kami berlangganan, itu akan mengirimkan acara
doOnSubscribe ke
Completable , yang merupakan yang pertama dalam antrian eksekusi. Setelah selesai, ia akan berlangganan ke rantai kedua, ketiga, dan seterusnya. Oleh karena itu, jika kesalahan terjadi pada tahap tertentu, maka seluruh urutan melempar kesalahan. Anda juga harus menentukan alpha 0 sebelum dimulainya animasi sehingga tombol tidak terlihat. Dan beginilah tampilannya:
Menggunakan
Kotlin , kita dapat menggunakan ekstensi:
fun View.fadeIn(duration: Long): Completable { val animationSubject = CompletableSubject.create() return animationSubject.doOnSubscribe { ViewCompat.animate(this) .setDuration(duration) .alpha(1f) .withEndAction { animationSubject.onComplete() } } }
Untuk kelas tampilan, fungsi ekstensi telah ditambahkan. Lebih lanjut, tidak perlu meneruskan argumen View ke metode fadeIn. Sekarang Anda dapat mengganti semua panggilan ke Lihat dengan kata kunci ini di dalam metode. Inilah yang mampu dilakukan
Kotlin.Mari kita lihat bagaimana pemanggilan fungsi ini dalam rantai animasi kami telah berubah:
button1.fadeIn(durationMs) .andThen(button2.fadeIn(durationMs)) .andThen(button3.fadeIn(durationMs)) .andThen(button4.fadeIn(durationMs)) .subscribe()
Sekarang kodenya terlihat lebih bisa dimengerti. Ini dengan jelas menyatakan bahwa kami ingin menerapkan animasi dengan durasi tertentu ke tampilan yang diinginkan. Menggunakan operator
andThen ,
kami membuat rangkaian animasi berurutan ke tombol kedua, ketiga, dan seterusnya.
Kami selalu menunjukkan durasi animasi, nilai ini sama untuk semua tampilan - 1000 milidetik.
Kotlin datang untuk menyelamatkan lagi. Kita dapat membuat nilai waktu default.
fun View.fadeIn(duration: Long = 1000L):
Jika Anda tidak menentukan parameter
durasi , maka waktu akan secara otomatis ditetapkan ke 1 detik. Tetapi jika kita ingin tombol di nomor 2 untuk meningkatkan waktu ini menjadi 2 detik, kita cukup menentukan nilai ini dalam metode:
button1.fadeIn() .andThen(button2.fadeIn(duration = 2000L)) .andThen(button3.fadeIn()) .andThen(button4.fadeIn()) .subscribe()
Menjalankan dua animasi
Kami dapat menjalankan serangkaian animasi menggunakan operator
andThen . Bagaimana jika saya perlu menjalankan 2 animasi sekaligus? Untuk melakukan ini, ada
penggabungan dengan operator di
RxJava yang memungkinkan Anda untuk menggabungkan elemen yang dapat diselesaikan sedemikian rupa sehingga mereka akan diluncurkan secara bersamaan. Pernyataan ini memulai semua elemen dan berakhir setelah elemen terakhir ditampilkan. Jika kita mengubah
danKemudian bergabung dengan , kita mendapatkan animasi di mana semua tombol muncul pada saat yang sama, tetapi tombol 2 akan muncul sedikit lebih lama daripada yang lain:
button1.fadeIn() .mergeWith(button2.fadeIn(2000)) .mergeWith(button3.fadeIn()) .mergeWith(button4.fadeIn()) .subscribe()
Sekarang kita dapat mengelompokkan animasi. Mari kita coba menyulitkan tugas: misalnya, kita ingin tombol 1 dan tombol 2 muncul pada saat yang sama, dan kemudian tombol 3 dan tombol 4:
(button1.fadeIn().mergeWith(button2.fadeIn())) .andThen(button3.fadeIn().mergeWith(button4.fadeIn())) .subscribe()
Kami menggabungkan tombol pertama dan kedua dengan operator
penggabunganDengan , mengulangi tindakan untuk yang ketiga dan keempat, dan memulai grup ini secara berurutan dengan operator
andLalu . Sekarang kita akan meningkatkan kode dengan menambahkan metode
fadeInTogether :
fun fadeInTogether(first: View, second: View): Completable { return first.fadeIn() .mergeWith(second.fadeIn()) }
Ini akan memungkinkan Anda untuk menjalankan animasi fadeIn untuk dua View secara bersamaan. Bagaimana rantai animasi berubah:
fadeInTogether(button1, button2) .andThen(fadeInTogether(button3, button4)) .subscribe()
Hasilnya adalah animasi berikut:
Pertimbangkan contoh yang lebih kompleks. Misalkan kita perlu menunjukkan animasi dengan penundaan yang diberikan. Pernyataan
interval akan membantu:
fun animate() { val timeObservable = Observable.interval(100, TimeUnit.MILLISECONDS) val btnObservable = Observable.just(button1, button2, button3, button4) }
Ini akan menghasilkan nilai setiap 100 milidetik. Setiap tombol akan muncul setelah 100 milidetik. Selanjutnya, kami menunjukkan Observable lain yang akan memancarkan tombol. Dalam hal ini, kami memiliki 4 tombol. Kami menggunakan operator
zip .

Di hadapan kami adalah aliran acara:
Observable.zip(timeObservable, btnObservable, BiFunction<Long, View, Disposable> { _, button -> button.fadeIn().subscribe() })
Yang pertama berhubungan dengan
timeObservable .
Diamati ini akan menghasilkan angka secara berkala. Misalkan 100 milidetik.
Yang Dapat
Diamati kedua akan menghasilkan tampilan. Operator
zip menunggu hingga objek pertama muncul di utas pertama, dan menghubungkannya ke objek pertama dari utas kedua. Terlepas dari kenyataan bahwa semua 4 objek di utas kedua ini akan segera muncul, ia akan menunggu sampai benda-benda mulai muncul di utas pertama. Dengan demikian, objek pertama dari aliran pertama akan terhubung ke objek pertama dari yang kedua dalam bentuk pandangan kami, dan 100 milidetik kemudian, ketika objek baru muncul, operator akan menggabungkannya dengan objek kedua. Karena itu, tampilan akan muncul dengan penundaan tertentu.
Mari kita
berurusan dengan
BiFinction di
RxJava . Fungsi ini menerima dua objek sebagai input, melakukan beberapa operasi padanya, dan mengembalikan objek ketiga. Kami ingin mengambil waktu dan melihat objek dan mendapatkan
Disposable karena kami memanggil animasi
fadeIn dan berlangganan untuk
berlangganan . Nilai waktu tidak penting bagi kami. Hasilnya, kami mendapatkan animasi ini:
Vanogogh
Saya akan memberi tahu Anda tentang
proyek yang mulai dikembangkan Ivan untuk MBLT DEV 2017.
Perpustakaan yang dikembangkan oleh Ivan menyajikan berbagai kerang untuk animasi. Kami sudah mempertimbangkan ini di atas. Ini juga berisi animasi yang sudah jadi yang bisa Anda gunakan. Anda mendapatkan seperangkat alat umum untuk membuat animasi Anda sendiri. Perpustakaan ini akan memberi Anda komponen yang lebih kuat untuk pemrograman reaktif.
Pertimbangkan perpustakaan menggunakan contoh:
fun fadeIn(view:View) : AnimationCompletable { return AnimationBuilder.forView(view) .alpha(1f) .duration(2000L) .build().toCompletable() }
Misalkan Anda ingin membuat animasi yang muncul, tetapi kali ini
AnimationCompletable muncul alih-alih objek
Completable . Kelas ini mewarisi dari
Completable , jadi sekarang lebih banyak fungsi yang muncul. Salah satu fitur penting dari kode sebelumnya adalah bahwa tidak mungkin untuk membatalkan animasi. Sekarang Anda dapat membuat objek
AnimationCompletable yang membuat animasi berhenti segera setelah kami berhenti berlangganan darinya.
Buat animasi yang muncul menggunakan
AnimationBuilder - salah satu kelas perpustakaan. Tentukan tampilan animasi yang akan diterapkan. Pada dasarnya, kelas ini menyalin perilaku
ViewPropertyAnimator , tetapi dengan perbedaan bahwa output adalah aliran.
Selanjutnya, atur alpha 1f dan durasinya adalah 2 detik. Lalu kami mengumpulkan animasi. Segera setelah kami
memunculkan statemen
build , animasi muncul. Kami menetapkan animasi properti dari objek yang tidak dapat diubah, sehingga akan menyimpan karakteristik ini untuk peluncurannya. Tetapi animasi itu sendiri tidak akan mulai.
Panggil
toCompletable , yang akan membuat
AnimationCompletable . Ini akan membungkus parameter animasi ini dalam semacam shell untuk pemrograman reaktif, dan segera setelah Anda berlangganan, itu akan memulai animasi. Jika Anda mematikannya sebelum proses selesai, animasi akan berakhir. Anda juga sekarang dapat menambahkan fungsi panggilan balik. Anda dapat menulis operator
doOnAnimationReady ,
doOnAnimationStart ,
doOnAnimationEnd, dan sejenisnya:
fun fadeIn(view:View) : AnimationCompletable { return AnimationBuilder.forView(view) .alpha(1f) .duration(2000L) .buildCompletable() .doOnAnimationReady { view.alpha = 0f } }
Dalam contoh ini, kami menunjukkan betapa nyamannya menggunakan
AnimationBuilder , dan mengubah status Tampilan kami sebelum memulai animasi.
Laporkan video
Kami melihat salah satu opsi untuk membuat, menyusun, dan mengubah animasi menggunakan Kotlin dan RxJava. Berikut ini tautan ke
proyek yang menjelaskan animasi dan contoh dasar untuk mereka, serta kerangka utama untuk bekerja dengan animasi.
Selain dekripsi, saya membagikan video laporan:
Pembicara MBLT DEV 2018
Sebelum
MBLT DEV 2018 , sedikit lebih dari dua bulan tersisa. Kami akan memiliki pertunjukan berikut:
- Laura Morinigo, Pakar Pengembang Google
- Kaushik Gopal, penulis Podcast Terfragmentasi
- Artyom Rudoi, Badoo
- Dina Sidorova, Google, dan lainnya .
Besok harga tiket akan berubah.
Daftarkan hari ini.