Menjadikan Tampilan Interaktif

Menggambar UI hanyalah salah satu langkah dalam 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. Perilaku objek harus selalu menyerupai perilaku objek sebenarnya. Misalnya, gambar tidak boleh tiba-tiba menyembul 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 dalam 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 menyediakan GestureDetector.SimpleOnGestureListener, bukannya menerapkan antarmuka GestureDetector.OnGestureListener. Misalnya, kode berikut membuat class yang menyediakan 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());
    

Terlepas dari apakah Anda menggunakan GestureDetector.SimpleOnGestureListener atau tidak, Anda harus selalu menerapkan metode onDown() yang akan mengembalikan nilai true. Langkah ini diperlukan karena semua gestur dimulai dengan pesan onDown(). Jika Anda mengembalikan false dari onDown(), seperti yang dilakukan GestureDetector.SimpleOnGestureListener, sistem akan berasumsi bahwa Anda ingin mengabaikan gestur lainnya, dan metode lain dari GestureDetector.OnGestureListener tidak akan pernah dipanggil. Satu-satunya waktu Anda harus mengembalikan 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() yang tidak dikenali sebagai bagian dari gestur, false akan dikembalikan. 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 melempar dengan gaya flywheel.

Untuk mulai melempar, panggil fling() dengan menetapkan nilai kecepatan awal serta nilai x-y minimum dan maksimum untuk lemparan 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 melempar yang terlalu cepat. Biasanya kecepatan x dan y ini dibagi dengan faktor 4 hingga 8.

Panggilan ke fling() akan mengatur model fisika untuk gestur melempar tersebut. Setelah itu, Anda perlu 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 scoll terlihat halus. Ada 2 cara untuk melakukannya:

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 level API 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 level API lebih rendah. Anda hanya perlu memeriksa level API saat ini saat runtime, 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();