Как обрабатывать жесты с несколькими касаниями

Попробуйте Compose
Jetpack Compose – это рекомендуемый набор инструментов для создания пользовательского интерфейса в Android. Узнайте, как использовать сенсорный ввод и ввод данных в Compose.

Жест с несколькими касаниями – это когда несколько указателей (пальцев) касаются экрана одновременно. This document describes how to detect gestures that involve multiple pointers.

Отслеживание нескольких указателей

Когда несколько указателей касаются экрана одновременно, система генерирует следующие события касания:

  • ACTION_DOWN – отправляется, когда первый указатель касается экрана. Это начало жеста. Данные для этого указателя всегда находятся по индексу 0 в MotionEvent.
  • ACTION_POINTER_DOWN: отправляется, когда на экране появляются дополнительные указатели после первого. Индекс указателя, который только что был опущен, можно получить с помощью getActionIndex().
  • ACTION_MOVE – отправляется при изменении жеста, в котором задействовано любое количество указателей.
  • ACTION_POINTER_UP: отправляется, когда неосновной указатель поднимается. Индекс указателя, который только что был поднят, можно получить с помощью getActionIndex().
  • ACTION_UP – отправляется, когда последний указатель покидает экран.
  • ACTION_CANCEL – указывает, что весь жест, включая все указатели, отменен.

Жесты начала и окончания

Жест – это серия событий, которая начинается с события ACTION_DOWN и заканчивается событием ACTION_UP или ACTION_CANCEL. Одновременно может быть активен только один жест. Действия DOWN, MOVE, UP и CANCEL применяются ко всему жесту. Например, событие с ACTION_MOVE может указывать на движение всех указателей, нажатых в этот момент.

Отслеживание указателей

Используйте индекс и идентификатор указателя, чтобы отслеживать положение отдельных указателей в MotionEvent.

  • Индекс. MotionEvent хранит информацию об указателях в массиве. Индекс указателя – это его позиция в массиве. Большинство методов MotionEvent принимают в качестве параметра индекс указателя, а не его идентификатор.
  • Идентификатор. У каждого указателя есть идентификатор, который не меняется при событиях касания. Это позволяет отслеживать указатель на протяжении всего жеста.

Отдельные указатели появляются в событии движения в неопределенном порядке. Таким образом, индекс указателя может меняться от одного события к другому, но идентификатор указателя гарантированно остается постоянным, пока указатель остается активным. Используйте метод getPointerId(), чтобы получить идентификатор указателя и отслеживать его во всех последующих событиях движения в жесте. Затем для последующих событий движения используйте метод findPointerIndex(), чтобы получить индекс указателя для заданного идентификатора указателя в этом событии движения. Пример:

Kotlin

private var mActivePointerId: Int = 0

override fun onTouchEvent(event: MotionEvent): Boolean {
    ...
    // Get the pointer ID.
    mActivePointerId = event.getPointerId(0)

    // ... Many touch events later...

    // Use the pointer ID to find the index of the active pointer
    // and fetch its position.
    val (x: Float, y: Float) = event.findPointerIndex(mActivePointerId).let { pointerIndex ->
        // Get the pointer's current position.
        event.getX(pointerIndex) to event.getY(pointerIndex)
    }
    ...
}

Java

private int mActivePointerId;

public boolean onTouchEvent(MotionEvent event) {
    ...
    // Get the pointer ID.
    mActivePointerId = event.getPointerId(0);

    // ... Many touch events later...

    // Use the pointer ID to find the index of the active pointer
    // and fetch its position.
    int pointerIndex = event.findPointerIndex(mActivePointerId);
    // Get the pointer's current position.
    float x = event.getX(pointerIndex);
    float y = event.getY(pointerIndex);
    ...
}

Чтобы поддерживать несколько указателей, вы можете кешировать все активные указатели с их идентификаторами во время событий ACTION_POINTER_DOWN и ACTION_DOWN. Удалите указатели из кеша в событиях ACTION_POINTER_UP и ACTION_UP. Эти кешированные идентификаторы могут быть полезны для правильной обработки других событий действий. Например, при обработке события ACTION_MOVE найдите индекс для каждого кешированного идентификатора активного указателя, получите координаты указателя с помощью функций getX() и getY(), а затем сравните эти координаты с кешированными координатами, чтобы узнать, какие указатели переместились.

Используйте функцию getActionIndex() только с событиями ACTION_POINTER_UP и ACTION_POINTER_DOWN. Не используйте эту функцию с событиями ACTION_MOVE, так как она всегда возвращает 0.

Получить действия (MotionEvent)

Чтобы получить действие MotionEvent, используйте метод getActionMasked() или совместимую версию MotionEventCompat.getActionMasked(). В отличие от метода getAction(), метод getActionMasked() предназначен для работы с несколькими указателями. Возвращает действие без индексов указателей. Для действий с действительным индексом указателя используйте getActionIndex(), чтобы вернуть индекс указателей, связанных с действием, как показано в следующем фрагменте:

Kotlin

val (xPos: Int, yPos: Int) = MotionEventCompat.getActionMasked(event).let { action ->
    Log.d(DEBUG_TAG, "The action is ${actionToString(action)}")
    // Get the index of the pointer associated with the action.
    MotionEventCompat.getActionIndex(event).let { index ->
        // The coordinates of the current screen contact, relative to
        // the responding View or Activity.
        MotionEventCompat.getX(event, index).toInt() to MotionEventCompat.getY(event, index).toInt()
    }
}

if (event.pointerCount > 1) {
    Log.d(DEBUG_TAG, "Multitouch event")

} else {
    // Single touch event.
    Log.d(DEBUG_TAG, "Single touch event")
}

...

// Given an action int, returns a string description.
fun actionToString(action: Int): String {
    return when (action) {
        MotionEvent.ACTION_DOWN -> "Down"
        MotionEvent.ACTION_MOVE -> "Move"
        MotionEvent.ACTION_POINTER_DOWN -> "Pointer Down"
        MotionEvent.ACTION_UP -> "Up"
        MotionEvent.ACTION_POINTER_UP -> "Pointer Up"
        MotionEvent.ACTION_OUTSIDE -> "Outside"
        MotionEvent.ACTION_CANCEL -> "Cancel"
        else -> ""
    }
}

Java

int action = MotionEventCompat.getActionMasked(event);
// Get the index of the pointer associated with the action.
int index = MotionEventCompat.getActionIndex(event);
int xPos = -1;
int yPos = -1;

Log.d(DEBUG_TAG,"The action is " + actionToString(action));

if (event.getPointerCount() > 1) {
    Log.d(DEBUG_TAG,"Multitouch event");
    // The coordinates of the current screen contact, relative to
    // the responding View or Activity.
    xPos = (int)MotionEventCompat.getX(event, index);
    yPos = (int)MotionEventCompat.getY(event, index);

} else {
    // Single touch event.
    Log.d(DEBUG_TAG,"Single touch event");
    xPos = (int)MotionEventCompat.getX(event, index);
    yPos = (int)MotionEventCompat.getY(event, index);
}
...

// Given an action int, returns a string description
public static String actionToString(int action) {
    switch (action) {

        case MotionEvent.ACTION_DOWN: return "Down";
	case MotionEvent.ACTION_MOVE: return "Move";
	case MotionEvent.ACTION_POINTER_DOWN: return "Pointer Down";
	case MotionEvent.ACTION_UP: return "Up";
	case MotionEvent.ACTION_POINTER_UP: return "Pointer Up";
	case MotionEvent.ACTION_OUTSIDE: return "Outside";
	case MotionEvent.ACTION_CANCEL: return "Cancel";
    }
    return "";
}
Рисунок 1. Мультисенсорные шаблоны рисования.

Дополнительные ресурсы

Дополнительную информацию о событиях ввода можно найти в следующих источниках: