Melacak sentuhan dan gerakan pointer

Mencoba cara Compose
Jetpack Compose adalah toolkit UI yang direkomendasikan untuk Android. Pelajari cara menggunakan sentuhan dan input di Compose.

Tutorial ini menjelaskan cara melacak gerakan dalam peristiwa sentuhan.

onTouchEvent() baru dipicu dengan peristiwa ACTION_MOVE setiap kali posisi kontak sentuh, tekanan, atau ukuran saat ini berubah. Seperti yang dijelaskan dalam Mendeteksi gestur umum, semua peristiwa ini dicatat dalam parameter MotionEvent dari onTouchEvent().

Karena sentuhan berbasis jari tidak selalu merupakan bentuk interaksi yang paling tepat, mendeteksi peristiwa sentuh sering kali lebih didasarkan pada gerakan daripada kontak sederhana. Untuk membantu aplikasi membedakan gestur berbasis gerakan (seperti geser) dan gestur non-gerakan (seperti sekali ketuk), Android menyertakan gagasan slop sentuh. Touch slop mengacu pada jarak dalam piksel yang dapat dijelajahi dengan sentuhan pengguna sebelum gestur ditafsirkan sebagai gestur berbasis gerakan. Untuk informasi selengkapnya tentang topik ini, lihat Mengelola peristiwa sentuh pada ViewGroup.

Ada beberapa cara untuk melacak gerakan dalam gestur, bergantung pada kebutuhan aplikasi Anda. Berikut ini contohnya:

  • Posisi awal dan akhir pointer, seperti memindahkan objek di layar dari titik A ke titik B.
  • Arah gerakan pointer, seperti yang ditentukan oleh koordinat X dan Y.
  • Histori. Anda dapat menemukan ukuran histori gestur dengan memanggil metode MotionEvent getHistorySize(). Selanjutnya, Anda dapat memperoleh posisi, ukuran, waktu, dan tekanan dari setiap peristiwa historis menggunakan metode getHistorical<Value> peristiwa gerakan. Histori berguna saat merender jejak jari pengguna, seperti untuk gambar sentuh. Lihat referensi MotionEvent untuk mengetahui detailnya.
  • Kecepatan pointer saat bergerak di layar sentuh.

Lihat referensi terkait berikut ini:

Melacak kecepatan

Anda dapat memiliki gestur berbasis gerakan yang didasarkan pada jarak atau arah yang ditempuh pointer. Namun, kecepatan sering kali menjadi faktor penentu dalam melacak karakteristik gestur atau memutuskan apakah gestur terjadi atau tidak. Untuk mempermudah penghitungan kecepatan, Android menyediakan class VelocityTracker. VelocityTracker membantu Anda melacak kecepatan peristiwa sentuh. Ini berguna untuk gestur yang kecepatan menjadi bagian dari kriteria gestur tersebut, seperti mengayunkan jari.

Berikut adalah contoh yang menggambarkan tujuan metode dalam VelocityTracker API:

Kotlin

private const val DEBUG_TAG = "Velocity"

class MainActivity : Activity() {
    private var mVelocityTracker: VelocityTracker? = null

    override fun onTouchEvent(event: MotionEvent): Boolean {

        when (event.actionMasked) {
            MotionEvent.ACTION_DOWN -> {
                // Reset the velocity tracker back to its initial state.
                mVelocityTracker?.clear()
                // If necessary, retrieve a new VelocityTracker object to watch
                // the velocity of a motion.
                mVelocityTracker = mVelocityTracker ?: VelocityTracker.obtain()
                // Add a user's movement to the tracker.
                mVelocityTracker?.addMovement(event)
            }
            MotionEvent.ACTION_MOVE -> {
                mVelocityTracker?.apply {
                    val pointerId: Int = event.getPointerId(event.actionIndex)
                    addMovement(event)
                    // When you want to determine the velocity, call
                    // computeCurrentVelocity(). Then, call getXVelocity() and
                    // getYVelocity() to retrieve the velocity for each pointer
                    // ID.
                    computeCurrentVelocity(1000)
                    // Log velocity of pixels per second. It's best practice to
                    // use VelocityTrackerCompat where possible.
                    Log.d("", "X velocity: ${getXVelocity(pointerId)}")
                    Log.d("", "Y velocity: ${getYVelocity(pointerId)}")
                }
            }
            MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
                // Return a VelocityTracker object back to be re-used by others.
                mVelocityTracker?.recycle()
                mVelocityTracker = null
            }
        }
        return true
    }
}

Java

public class MainActivity extends Activity {
    private static final String DEBUG_TAG = "Velocity";
        ...
    private VelocityTracker mVelocityTracker = null;
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int index = event.getActionIndex();
        int action = event.getActionMasked();
        int pointerId = event.getPointerId(index);

        switch(action) {
            case MotionEvent.ACTION_DOWN:
                if(mVelocityTracker == null) {
                    // Retrieve a new VelocityTracker object to watch the
                    // velocity of a motion.
                    mVelocityTracker = VelocityTracker.obtain();
                }
                else {
                    // Reset the velocity tracker back to its initial state.
                    mVelocityTracker.clear();
                }
                // Add a user's movement to the tracker.
                mVelocityTracker.addMovement(event);
                break;
            case MotionEvent.ACTION_MOVE:
                mVelocityTracker.addMovement(event);
                // When you want to determine the velocity, call
                // computeCurrentVelocity(). Then call getXVelocity() and
                // getYVelocity() to retrieve the velocity for each pointer ID.
                mVelocityTracker.computeCurrentVelocity(1000);
                // Log velocity of pixels per second. It's best practice to use
                // VelocityTrackerCompat where possible.
                Log.d("", "X velocity: " + mVelocityTracker.getXVelocity(pointerId));
                Log.d("", "Y velocity: " + mVelocityTracker.getYVelocity(pointerId));
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                // Return a VelocityTracker object back to be re-used by others.
                mVelocityTracker.recycle();
                break;
        }
        return true;
    }
}

Menggunakan rekaman pointer

Beberapa aplikasi, seperti game serta klien desktop dan virtualisasi jarak jauh, mendapatkan manfaat dari kontrol kursor mouse. Perekaman pointer adalah fitur yang tersedia di Android 8.0 (API level 26) dan versi lebih tinggi yang menyediakan kontrol ini dengan mengirimkan semua peristiwa mouse ke tampilan yang difokuskan dalam aplikasi Anda.

Meminta rekaman pointer

Tampilan di aplikasi Anda dapat meminta rekaman pointer hanya jika hierarki tampilan yang berisinya memiliki fokus. Karena alasan ini, minta rekaman pointer saat ada tindakan pengguna tertentu pada tampilan, seperti selama peristiwa onClick() atau di pengendali peristiwa onWindowFocusChanged() aktivitas Anda.

Untuk meminta rekaman pointer, panggil metode requestPointerCapture() pada tampilan. Contoh kode berikut menunjukkan cara meminta rekaman pointer saat pengguna mengklik tampilan:

Kotlin

fun onClick(view: View) {
    view.requestPointerCapture()
}

Java

@Override
public void onClick(View view) {
    view.requestPointerCapture();
}

Setelah permintaan untuk merekam pointer berhasil, Android akan memanggil onPointerCaptureChange(true). Sistem mengirimkan peristiwa mouse ke tampilan yang difokuskan di aplikasi Anda selama berada dalam hierarki tampilan yang sama dengan tampilan yang meminta rekaman. Aplikasi lain berhenti menerima peristiwa mouse hingga rekaman dirilis, termasuk peristiwa ACTION_OUTSIDE. Android mengirimkan peristiwa pointer dari sumber selain mouse seperti normal, tetapi pointer mouse tidak lagi terlihat.

Menangani peristiwa pointer yang direkam

Setelah tampilan berhasil memperoleh rekaman pointer, Android akan mengirimkan peristiwa mouse. Tampilan terfokus dapat menangani peristiwa dengan melakukan salah satu tugas berikut:

Contoh kode berikut menunjukkan cara mengimplementasikan onCapturedPointerEvent(MotionEvent):

Kotlin

override fun onCapturedPointerEvent(motionEvent: MotionEvent): Boolean {
    // Get the coordinates required by your app.
    val verticalOffset: Float = motionEvent.y
    // Use the coordinates to update your view and return true if the event is
    // successfully processed.
    return true
}

Java

@Override
public boolean onCapturedPointerEvent(MotionEvent motionEvent) {
  // Get the coordinates required by your app.
  float verticalOffset = motionEvent.getY();
  // Use the coordinates to update your view and return true if the event is
  // successfully processed.
  return true;
}

Contoh kode berikut menunjukkan cara mendaftarkan OnCapturedPointerListener:

Kotlin

myView.setOnCapturedPointerListener { view, motionEvent ->
    // Get the coordinates required by your app.
    val horizontalOffset: Float = motionEvent.x
    // Use the coordinates to update your view and return true if the event is
    // successfully processed.
    true
}

Java

myView.setOnCapturedPointerListener(new View.OnCapturedPointerListener() {
  @Override
  public boolean onCapturedPointer (View view, MotionEvent motionEvent) {
    // Get the coordinates required by your app.
    float horizontalOffset = motionEvent.getX();
    // Use the coordinates to update your view and return true if the event is
    // successfully processed.
    return true;
  }
});

Baik Anda menggunakan tampilan kustom maupun mendaftarkan pemroses, tampilan Anda akan menerima MotionEvent dengan koordinat pointer yang menentukan gerakan relatif seperti delta X atau Y, mirip dengan koordinat yang dikirimkan oleh perangkat trackball. Anda dapat mengambil koordinat menggunakan getX() dan getY().

Merilis rekaman pointer

Tampilan di aplikasi Anda dapat merilis rekaman pointer dengan memanggil releasePointerCapture(), seperti yang ditunjukkan dalam contoh kode berikut:

Kotlin

override fun onClick(view: View) {
    view.releasePointerCapture()
}

Java

@Override
public void onClick(View view) {
    view.releasePointerCapture();
}

Sistem dapat mengambil rekaman dari tampilan tanpa Anda perlu memanggil releasePointerCapture() secara eksplisit, biasanya karena hierarki tampilan yang berisi tampilan yang diminta rekamannya akan kehilangan fokus.