Menggambar UI hanyalah satu bagian dari membuat tampilan kustom. Anda juga perlu menjadikan tampilan Anda responsif terhadap input pengguna sedemikian rupa sehingga sangat mirip dengan tindakan sebenarnya yang ingin Anda tirukan. Objek harus selalu bertindak dengan cara yang sama seperti objek sebenarnya. Misalnya, gambar tidak boleh tiba-tiba menghilang lalu muncul lagi di tempat lain, karena objek yang sebenarnya tidaklah seperti itu. Sebaliknya, gambar berpindah dari satu tempat ke tempat yang lain.
Pengguna juga merasakan perilaku atau perasaan halus dalam suatu antarmuka, dan bereaksi paling baik terhadap kehalusan yang menyerupai penggunaan sebenarnya. Misalnya, saat melempar objek UI, mereka harus merasakan friksi di awal yang menunda gerakan, lalu di bagian akhir merasakan momentum yang membawa gerakan itu melampaui lemparan tersebut.
Pelajaran ini menjelaskan cara menggunakan fitur-fitur framework Android untuk menambahkan perilaku dalam pemakaian sebenarnya ini ke tampilan kustom Anda.
Selain dalam pelajaran ini, Anda dapat menemukan informasi terkait lainnya di bagian Peristiwa Input dan Animasi Properti.
Menangani Gestur Input
Seperti banyak framework UI lainnya, Android mendukung model peristiwa input. Tindakan pengguna diubah menjadi peristiwa yang memicu callback, dan Anda dapat mengganti callback untuk menyesuaikan respons aplikasi terhadap pengguna. Peristiwa input yang paling umum di sistem Android adalah sentuh, yang memicu onTouchEvent(android.view.MotionEvent)
. Ganti metode ini untuk menangani peristiwa tersebut:
Kotlin
override fun onTouchEvent(event: MotionEvent): Boolean { return super.onTouchEvent(event) }
Java
@Override public boolean onTouchEvent(MotionEvent event) { return super.onTouchEvent(event); }
Peristiwa sentuh sendiri sebenarnya tidak terlalu berguna. UI sentuh modern menentukan interaksi dalam bentuk gestur seperti mengetuk, menarik, mendorong, melempar, dan memperbesar/memperkecil. Untuk mengonversi peristiwa sentuh dasar menjadi gestur, Android menyediakan GestureDetector
.
Buatlah GestureDetector
dengan meneruskan instance class yang mengimplementasikan GestureDetector.OnGestureListener
. Jika hanya ingin memproses beberapa gestur, Anda dapat memperluas GestureDetector.SimpleOnGestureListener
, bukan mengimplementasikan antarmuka GestureDetector.OnGestureListener
. Misalnya, kode ini membuat class yang memperluas GestureDetector.SimpleOnGestureListener
dan mengganti onDown(MotionEvent)
.
Kotlin
private val myListener = object : GestureDetector.SimpleOnGestureListener() { override fun onDown(e: MotionEvent): Boolean { return true } } private val detector: GestureDetector = GestureDetector(context, myListener)
Java
class MyListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onDown(MotionEvent e) { return true; } } detector = new GestureDetector(PieChart.this.getContext(), new MyListener());
Apakah Anda menggunakan GestureDetector.SimpleOnGestureListener
atau tidak, Anda harus selalu mengimplementasikan metode onDown()
yang menampilkan true
. Langkah ini diperlukan karena semua gestur dimulai dengan pesan onDown()
. Jika Anda menampilkan false
dari onDown()
, seperti GestureDetector.SimpleOnGestureListener
, sistem akan menganggap bahwa Anda ingin mengabaikan seluruh gestur, dan metode GestureDetector.OnGestureListener
lain tidak akan pernah dipanggil. Satu-satunya waktu Anda harus menampilkan false
dari onDown()
adalah jika Anda benar-benar ingin mengabaikan seluruh gestur.
Setelah mengimplementasikan GestureDetector.OnGestureListener
dan membuat instance GestureDetector
, Anda dapat menggunakan GestureDetector
untuk menginterpretasikan peristiwa sentuh yang Anda terima di onTouchEvent()
.
Kotlin
override fun onTouchEvent(event: MotionEvent): Boolean { return detector.onTouchEvent(event).let { result -> if (!result) { if (event.action == MotionEvent.ACTION_UP) { stopScrolling() true } else false } else true } }
Java
@Override public boolean onTouchEvent(MotionEvent event) { boolean result = detector.onTouchEvent(event); if (!result) { if (event.getAction() == MotionEvent.ACTION_UP) { stopScrolling(); result = true; } } return result; }
Jika Anda meneruskan onTouchEvent()
peristiwa sentuh yang tidak dikenali sebagai bagian dari gestur, tindakan itu akan menampilkan false
. Selanjutnya Anda dapat menjalankan kode deteksi gestur kustom Anda sendiri.
Membuat Gerakan yang Masuk Akal secara Fisika
Meskipun efektif untuk mengontrol perangkat layar sentuh, gestur dapat kontra-intuitif dan sulit diingat kecuali jika gestur tersebut memberikan hasil yang masuk akal secara fisika. Contoh yang baik dari hal ini adalah gestur melempar, di mana pengguna menggerakkan jari dengan cepat di layar, lalu mengangkatnya. Gerakan ini terasa natural jika UI merespons dengan bergerak cepat searah lemparan, lalu melambat, seolah-olah pengguna mendorong flywheel dan mengaturnya supaya berputar.
Namun, menyimulasikan nuansa putaran flywheel bukanlah tugas sepele. Perlu banyak kalkulasi fisika dan matematika untuk menjadikan model flywheel berfungsi dengan benar. Untungnya, Android menyediakan class helper untuk menyimulasikan hal ini dan perilaku lainnya. Class Scroller
adalah dasar untuk menangani gestur ayun.
Untuk memulai gestur ayun, panggil fling()
dengan menetapkan nilai kecepatan awal serta nilai x-y minimum dan maksimum untuk ayunan tersebut. Untuk nilai kecepatan, Anda dapat menggunakan nilai yang dihitung secara otomatis oleh GestureDetector
.
Kotlin
fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean { scroller.fling( currentX, currentY, (velocityX / SCALE).toInt(), (velocityY / SCALE).toInt(), minX, minY, maxX, maxY ) postInvalidate() return true }
Java
@Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { scroller.fling(currentX, currentY, velocityX / SCALE, velocityY / SCALE, minX, minY, maxX, maxY); postInvalidate(); return true; }
Catatan: Meskipun kecepatan yang dihitung oleh GestureDetector
akurat secara fisika, banyak developer merasa nilai ini menghasilkan animasi ayun yang terlalu cepat. Biasanya kecepatan x dan y ini dibagi dengan faktor 4 hingga 8.
Panggilan ke fling()
akan mengatur model fisika untuk gestur ayun tersebut.
Setelah itu, Anda harus memperbarui Scroller
dengan memanggil Scroller.computeScrollOffset()
secara berkala. computeScrollOffset()
memperbarui status internal objek Scroller
dengan membaca waktu saat ini dan menggunakan model fisika untuk menghitung posisi x dan y pada waktu itu. Panggil getCurrX()
dan getCurrY()
untuk mengambil nilai-nilai ini.
Sebagian besar tampilan meneruskan posisi x dan y objek Scroller
langsung ke scrollTo()
. Contoh PieChart ini sedikit berbeda: PieChart ini menggunakan posisi y scroll saat ini untuk menetapkan sudut putaran diagram.
Kotlin
scroller.apply { if (!isFinished) { computeScrollOffset() setPieRotation(currY) } }
Java
if (!scroller.isFinished()) { scroller.computeScrollOffset(); setPieRotation(scroller.getCurrY()); }
Class Scroller
menghitung posisi scroll secara otomatis, tetapi tidak otomatis menerapkan posisi tersebut ke tampilan Anda. Andalah yang harus memastikan bahwa Anda mendapatkan dan menerapkan koordinat baru cukup sering agar animasi scroll terlihat halus. Ada 2 cara untuk melakukannya:
- Panggil
postInvalidate()
setelah memanggilfling()
, untuk memaksa penarikan ulang. Teknik ini mengharuskan Anda menghitung offset scroll dionDraw()
dan memanggilpostInvalidate()
setiap kali offset scroll berubah. - Siapkan
ValueAnimator
untuk menganimasikan durasi ayunan, dan tambahkan pemroses untuk memproses update animasi dengan memanggiladdUpdateListener()
.
Contoh PieChart menggunakan pendekatan kedua. Penyiapan teknik ini sedikit lebih sulit, tetapi terintegrasi lebih erat dengan sistem animasi dan tidak memerlukan pembatalan tampilan yang mungkin tidak perlu. Kekurangannya adalah ValueAnimator
tidak tersedia pada API level di bawah 11, jadi teknik ini tidak dapat digunakan pada perangkat yang menjalankan versi Android di bawah 3.0.
Catatan: Anda dapat menggunakan ValueAnimator
pada aplikasi yang menargetkan API level lebih rendah. Anda hanya perlu memeriksa API level saat ini pada waktu proses, dan menghilangkan panggilan ke sistem animasi tampilan jika level saat ini di bawah 11.
Kotlin
private val scroller = Scroller(context, null, true) private val scrollAnimator = ValueAnimator.ofFloat(0f, 1f).apply { addUpdateListener { if (scroller.isFinished) { scroller.computeScrollOffset() setPieRotation(scroller.currY) } else { cancel() onScrollFinished() } } }
Java
scroller = new Scroller(getContext(), null, true); scrollAnimator = ValueAnimator.ofFloat(0,1); scrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { if (!scroller.isFinished()) { scroller.computeScrollOffset(); setPieRotation(scroller.getCurrY()); } else { scrollAnimator.cancel(); onScrollFinished(); } } });
Menciptakan Transisi yang Lancar
Pengguna mengharapkan UI modern bertransisi dengan lancar dari satu keadaan ke keadaan yang lain. Elemen UI melakukan fade-in dan fade-out, bukannya muncul dan menghilang. Gerakan dimulai dan berakhir dengan lancar, bukan dimulai dan berhenti tiba-tiba. Framework animasi properti Android, yang diperkenalkan pada Android 3.0, memudahkan transisi yang lancar.
Untuk menggunakan sistem animasi ini, setiap kali properti berubah yang akan memengaruhi gaya tampilan, jangan ubah properti secara langsung. Sebagai gantinya, gunakan ValueAnimator
untuk melakukan perubahan itu. Dalam contoh berikut, memodifikasi irisan lingkaran yang saat ini dipilih di PieChart menyebabkan seluruh bagan berputar sehingga penunjuk pilihan terpusat di irisan yang dipilih. ValueAnimator
mengubah putaran dalam rentang waktu beberapa ratus milidetik, bukan langsung menetapkan nilai putaran baru.
Kotlin
autoCenterAnimator = ObjectAnimator.ofInt(this, "PieRotation", 0).apply { setIntValues(targetAngle) duration = AUTOCENTER_ANIM_DURATION start() }
Java
autoCenterAnimator = ObjectAnimator.ofInt(PieChart.this, "PieRotation", 0); autoCenterAnimator.setIntValues(targetAngle); autoCenterAnimator.setDuration(AUTOCENTER_ANIM_DURATION); autoCenterAnimator.start();
Jika nilai yang ingin Anda ubah adalah salah satu properti View
dasar, animasi dapat dijalankan lebih mudah lagi, karena Views memiliki ViewPropertyAnimator
bawaan yang dioptimalkan untuk animasi simultan beberapa properti. Contoh:
Kotlin
animate() .rotation(targetAngle) .duration = ANIM_DURATION .start()
Java
animate().rotation(targetAngle).setDuration(ANIM_DURATION).start();