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

Попробуйте способ создания
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 . Одновременно активен один жест. Действия ВНИЗ, ДВИЖЕНИЕ, ВВЕРХ и ОТМЕНА применяются ко всему жесту. Например, событие с 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)
    }
    ...
}

Ява

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

Используйте метод getActionMasked() или версию совместимости MotionEventCompat.getActionMasked() чтобы получить действие MotionEvent . В отличие от более раннего метода 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 -> ""
    }
}

Ява

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. Шаблоны мультитач-рисования.

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

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