Обработка мультитач-жестов

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

Мультитач-жест — это когда несколько указателей (пальцев) одновременно касаются экрана. В этом документе описывается, как распознавать жесты, включающие несколько указателей.

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

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

  • 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 принимают в качестве параметра индекс указателя, а не его идентификатор.
  • ID : каждый указатель также имеет сопоставление идентификаторов, которое сохраняется между событиями касания, что позволяет отслеживать отдельный указатель на протяжении всего жеста.

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

Котлин

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() , чтобы получить индекс указателей, связанных с действием, как показано в следующем фрагменте кода:

Котлин

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. Шаблоны для рисования с использованием нескольких касаний.

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

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