Membuat Tampilan Interaktif

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:

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();