В этом уроке:

- обрабатываем множественные касания

 

Разобравшись с одиночным касанием на прошлом уроке, приступаем к множественному касанию, которое зовется мультитач.

Система умеет обрабатывать до 10 касаний включительно. Есть мнение, что это как-то связано с количеством пальцев на руках :) При этом учитывайте, что далеко не все устройства поддерживают 10 касаний.

Рассмотрим систему событий для мультитача. К событиям ACTION_DOWN, ACTION_MOVE и ACTION_UP добавляются ACTION_POINTER_DOWN и ACTION_POINTER_UP.

ACTION_DOWN – срабатывает при касании первого пальца
ACTION_POINTER_DOWN – срабатывает при касании каждого последующего пальца
ACTION_MOVE - срабатывает при любом движении
ACTION_ POINTER_UP – срабатывает при отпускании каждого пальца кроме последнего
ACTION_ UP – срабатывает при отпускании последнего пальца

Теперь надо понять, как отличить - для какого именно пальца сработали события ACTION_POINTER_DOWN и ACTION_ POINTER_UP. Для этого используются две системы нумерации – индекс и ID.

Индекс – порядковый номер пальца. Не привязан к пальцу – один палец может иметь разные индексы в течение одного касания.

ID -  привязан к пальцу от начала до конца касания.

 

Чтобы стало понятнее, рассмотрим ситуацию с тремя пальцами. Обозначим их - П1, П2 и П3. Будем касаться ими экрана и смотреть какие индексы и ID система им присваивает.

Касаемся экрана пальцем П1.

Для П1: индекс = 0, ID = 0

 

Далее касаемся экрана пальцем П2, не отпуская П1. Получим такие данные:

П1: индекс = 0, ID = 0
П2: индекс = 1, ID = 1

 

Далее касаемся экрана пальцем П3, не отпуская П1 и П2. Получим такие данные:

П1: индекс = 0, ID = 0
П2: индекс = 1, ID = 1
П3: индекс = 2, ID = 2

 

Теперь отпускаем палец П1. Получаем:

П2: индекс = 0, ID = 1
П3: индекс = 1, ID = 2

Видим, что П2 и П3 сохранили свои ID, а их индексы сместились.

 

Отпустим палец П2, получим:

П3: индекс = 0, ID = 2

П3 сохранил свой ID, который был изначально. А индекс его сначала был 2, потом 1, теперь 0.

 

Держим П3. Коснемся экрана пальцем П1, получим:

П1: индекс = 0, ID = 0
П3: индекс = 1, ID = 2

П1 получил первый свободный ID - 0.  Индекс его тоже стал 0. А П3 получил индекс 1.

 

Держим П3 и П1. Коснемся экрана пальцем П2, получим:

П1: индекс = 0, ID = 0
П2: индекс = 1, ID = 1
П3: индекс = 2, ID = 2

П2 получил первый свободный ID - 1. И он сместил П3 в списке индексов.

 

На этом примере мы видим, что новое касание получает минимальный свободный ID, и индексы всегда перестраиваются так, чтобы ID шли по возрастанию. На этом примере четко видно, что ID привязан к касанию (пока оно длится – ID неизменен). А индексы – это просто номера касаний, но эти номера вовсе не означают порядок касаний. Индексы и ID могут принимать значения от 0 до 9.

Можно считать, что все текущие касания хранятся в некоем массиве. И ID - это их идентификаторы, а индексы - индексы этого массива касаний.

 

Вернемся к событиям. События UP и DOWN содержат в себе индекс касания. По этому индексу мы всегда можем получить ID. Событие MOVE информации об индексах не дает. Оно просто уведомляет, что происходит какое-то движение.

 

Напишем приложение, которое будет выводить на экран индекс последнего прикоснувшегося пальца, индекс последнего отпущенного пальца и всю таблицу индексов, ID и координат касаний.

  

Создадим проект:

Project name: P1031_MultiTouch
Build Target: Android 2.3.3
Application name: MultiTouch
Package name: ru.startandroid.develop.p1031multitouch
Create Activity: MainActivity

 

strings.xml и main.xml нам снова не понадобятся, их не трогаем.

 

Кодим MainActivity.java:

package ru.startandroid.develop.p1031multitouch;

import android.app.Activity;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.TextView;

public class MainActivity extends Activity implements OnTouchListener {

  StringBuilder sb = new StringBuilder();
  TextView tv;
  int upPI = 0;
  int downPI = 0;
  boolean inTouch = false;
  String result = "";

  /** Called when the activity is first created. */
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    tv = new TextView(this);
    tv.setTextSize(30);
    tv.setOnTouchListener(this);
    setContentView(tv);
  }

  @Override
  public boolean onTouch(View view, MotionEvent event) {
    // событие
    int actionMask = event.getActionMasked();
    // индекс касания
    int pointerIndex = event.getActionIndex();
    // число касаний
    int pointerCount = event.getPointerCount();

    switch (actionMask) {
    case MotionEvent.ACTION_DOWN: // первое касание
      inTouch = true;
    case MotionEvent.ACTION_POINTER_DOWN: // последующие касания
      downPI = pointerIndex;
      break;

    case MotionEvent.ACTION_UP: // прерывание последнего касания
      inTouch = false;
      sb.setLength(0);
    case MotionEvent.ACTION_POINTER_UP: // прерывания касаний
      upPI = pointerIndex;
      break;

    case MotionEvent.ACTION_MOVE: // движение
      sb.setLength(0);

      for (int i = 0; i < 10; i++) {
        sb.append("Index = " + i);
        if (i < pointerCount) {
          sb.append(", ID = " + event.getPointerId(i));
          sb.append(", X = " + event.getX(i));
          sb.append(", Y = " + event.getY(i));
        } else {
          sb.append(", ID = ");
          sb.append(", X = ");
          sb.append(", Y = ");
        }
        sb.append("\r\n");
      }
      break;
    }
    result = "down: " + downPI + "\n" + "up: " + upPI + "\n";

    if (inTouch) {
      result += "pointerCount = " + pointerCount + "\n" + sb.toString();
    }
    tv.setText(result);
    return true;
  }
}

В onCreate мы создаем TextView, присваиваем обработчик – текущее Activity, и помещаем в Activity.

 

Разбираемся с onTouch. Если для одного касания мы использовали метод getAction, чтобы понять какое событие произошло, то с мультитачем надо использовать getActionMasked. Индекс касания определяется методом getActionIndex. Кол-во текущих касаний – getPointerCount.

Если событие - ACTION_DOWN, значит мы получили первое касание. Ставим метку inTouch = true. Она для нас будет означать, что есть касания. Обратите внимание, что в этой ветке case мы не ставим break – следующая case-ветка (ACTION_POINTER_DOWN) также выполнится при ACTION_DOWN.

Если событие ACTION_POINTER_DOWN (или ACTION_DOWN), то в переменную downPI помещаем индекс касания. Это будет индекс последнего прикоснувшегося пальца.

Если событие - ACTION_UP, значит последнее касание прервано и экрана больше ничего не касается. Ставим inTouch = false, т.е. отсутствие касаний. Очищаем StringBuilder, который содержит информацию о движениях.

Если событие - ACTION_POINTER_UP (или ACTION_UP), то в переменную upPI помещаем индекс касания. Это будет индекс последнего прерванного касания. Т.е. когда мы одно за другим прерываем касания, эта переменная будет содержать один за другим индексы последнего из прерванных.

Если событие ACTION_MOVE – мы перебираем все существующие индексы. С помощью pointerCount определяем, какие из них сейчас задействованы и содержат информацию о касаниях. Для них мы пишем номер индекса, ID (метод getPointerId) и координаты (getX и getY). Для незадействованных пишем только номер индекса. Пишем мы это все в StringBuilder.

Далее при любом событии формируем result, пишем туда индекс последнего касания и последнего завершенного касания. Если в данный момент есть касание (inTouch), то добавляем в результат содержимое StringBuilder с подробной инфой о всех касаниях. И выводим result в TextView.

 

Все сохраним и запустим. На эмуляторе я не знаю способов добиться мультитача, поэтому я тестирую на планшете. Скрин с него.

Я коснулся экрана 5-ю пальцами (последовательно от большого до мизинца, ID от 0 до 5) и потом один (указательный, ID = 1) убрал с экрана.

down показывает, что последний прикоснувшийся палец был с индексом 4

up показывает, что последний убранный с экрана палец был с индексом 1

pointerCount показывает количество действующих касаний

И строки по индексам показывают подробную информацию о касаниях.

 

Методы getActionMasked и getActionIndex доступны только с API Level 8.

Для более ранних версий (с API Level 5) используется такой способ получения типа события и индекса:

// событие
actionMask = event.getAction() & MotionEvent.ACTION_MASK;
// индекс касания
pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_ID_MASK) >> MotionEvent.ACTION_POINTER_ID_SHIFT;

Это битовые операции. В принципе, в нашем случае вовсе необязательно понимать, что они делают. Но для общего развития имеет смысл с битовыми операциями познакомиться. Тут можно немного почитать об этом.

 

На следующем уроке:

- используем фрагменты
- разбираемся в их lifecycle


Присоединяйтесь к нам в Telegram:

- в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.

- в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Compose, Kotlin, RxJava, Dagger, Тестирование, Performance 

- ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня




Language

Автор сайта

Дмитрий Виноградов

Подробнее можно посмотреть или почитать.

Никакие другие люди не имеют к этому сайту никакого отношения и просто занимаются плагиатом.

Социальные сети

 

В канале я публикую ссылки на интересные и полезные статьи по Android

В чате можно обсудить вопросы и проблемы, возникающие при разработке



Группа ВКонтакте



Поддержка проекта

Яндекс
410011180491924

WebMoney
R248743991365
Z551306702056

Paypal