Android on touch time

Полный список

Раньше мы для View-компонентов использовали OnClickListener и ловили короткие нажатия. Теперь попробуем ловить касания и перемещения пальца по компоненту. Они состоят из трех типов событий:

— нажатие (палец прикоснулся к экрану)
— движение (палец движется по экрану)
— отпускание (палец оторвался от экрана)

Все эти события мы сможем ловить в обработчике OnTouchListener, который присвоим для View-компонента. Этот обработчик дает нам объект MotionEvent, из которого мы извлекаем тип события и координаты.

На этом уроке рассмотрим только одиночные касания. А мультитач – на следующем уроке.

Project name: P1021_Touch
Build Target: Android 2.3.3
Application name: Touch
Package name: ru.startandroid.develop.p1021touch
Create Activity: MainActivity

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

MainActivity реализует интерфейс OnTouchListener для того, чтобы выступить обработчиком касаний.

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

Интерфейс OnTouchListener предполагает, что Activity реализует его метод onTouch. На вход методу идет View для которого было событие касания и объект MotionEvent с информацией о событии.

Методы getX и getY дают нам X и Y координаты касания. Метод getAction дает тип события касания:

ACTION_DOWN – нажатие
ACTION_MOVE – движение
ACTION_UP – отпускание
ACTION_CANCEL – практически никогда не случается. Насколько я понял, возникает в случае каких-либо внутренних сбоев, и следует трактовать это как ACTION_UP.

В случае ACTION_DOWN мы пишем в sDown координаты нажатия.

В случае ACTION_MOVE пишем в sMove координаты точки текущего положения пальца. Если мы будем перемещать палец по экрану – этот текст будет постоянно меняться.

В случае ACTION_UP или ACTION_CANCEL пишем в sUp координаты точки, в которой отпустили палец.

Все это в конце события выводим в TextView. И возвращаем true – мы сами обработали событие.

Теперь мы будем водить пальцем по экрану (курсором по эмулятору) в приложении, и на экране увидим координаты начала движения, текущие координаты и координаты окончания движения.

Все сохраним и запустим приложение.

Ставим палец (курсор) на экран

Если вчерашний вечер не удался, голова не болит, рука тверда и не дрожит :), то появились координаты нажатия.

Если же рука дрогнула, то появится еще и координаты перемещения.

Продолжаем перемещать палец и видим, как меняются координаты Move.

Теперь отрываем палец от экрана и видим координаты точки, в которой это произошло

В целом все несложно. При мультитаче процесс немного усложнится, там уже будем отслеживать до 10 касаний.

Если вы уже знакомы с техникой рисования в Android, то вполне можете создать приложение выводящее на экран геометрическую фигуру, которую можно пальцем перемещать. Простейший пример реализации можно посмотреть тут: http://forum.startandroid.ru/viewtopic.php?f=28&t=535.

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

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

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

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

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

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

— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме

Источник

Разработка мультитач веб-приложений

Мобильные устройства такие как сматрфоны или планшеты обычно имеют емкостный экран, чувствительный к прикосновениям для расширенного взаимодействия с пользователем.

С каждым днем мобильные приложения становятся все сложнее и сложнее. Веб-разработчикам тоже нужен API для использования всех возможностей тачскрина. Например, какой-нибудь аркаде или файтингу нужна необходимость нажатья нескольких кнопок, эту возможность может предоставить экран с поддержкой мультитач.

Apple ввел свое touch events API в iOS 2.0, вскоре устройства на Android тоже получили такую возможность и touch events API стал стандартом де-факто. Недавно была собрана рабочая группа W3C для работы над touch events specification.
В этой статье я рассмотрю touch events API, которое нам предоставляют устройства на iOS и Android, мы изучим какие приложения можно создавать, используя touch events API. В статье куча полезных примеров и техник, которые позволяют упростить написание приложений с touch events API.

Читайте также:  Кошелек для дисконтных карт для андроид

События

Эти три основных события представлены в спецификации и поддерживаются многими устройствами:
touchstart: прикосновение к DOM element (аналог mousedown).
touchmove: движение пальца по DOM element (аналог mousemove).
touchend: палец убран с DOM element (аналог mouseup).

Каждое событие включает в себя три списка точек прикосновения (списки пальцев):
touches: список всех точек прикосновения на экране.
targetTouches: список точек на текущем элементе.
changedTouches: список пальцев, участвующих в текущем событии. Например, в событии touchend это тот палец, который был убран.

Каждый элемент списка представляет из себя объект формата:
identifier: уникальный идентификатор пальца, который сейчас на течскрине
target: DOM element, который является целью события
координаты client/page/screen: точка возникновения события на экране
radius и rotationAngle: эллипс, который описывает форму пальца

Приложения с поддержкой прикосновений

События touchstart, touchmove и touchend предоставляют достаточно мощный API для создания любых взаимодействий, основанных на прикосновении включая все обычные мультитач жесты — зум, вращение и так далее.

Этот пример позволяет вам перетаскивать DOM element, используя одноточечное прикосновение:

Ниже есть пример, который отображает все прикосновения на экране.

Картинка кликабильна
Вот его код:

Демки

Появилось уже большое количество приложений с поддержкой мультитач, одно из них рисовалка, основанная на canvas, созданная Paul Irish

И демка Browser Ninja — клон Fruit Ninja, использующая CSS3 transforms, transitions и canvas:

Важные Моменты

Предотвращение зума

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

Для отключения зума вам необходимо приписать следующий мета-тег:

Предотвращение скролла

Некоторые мобильные устройства имеют поведение по умолчанию для жеста «скролл» (touchmove), такие как классический оверскролл в iOS, что приводит к возвращению страницы назад, если скролл превысил допустимые рамки. Это сбивает с толку в мультитач приложениях, но может быть легко отключено:

Рисуйте аккуратно

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

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

Таймеры на setInterval — это не очень хороший способ для создания анимаций, т.к. он не синхронизируется с циклом перерисовки браузера. Современные десктопные браузеры предоставляют функцию requestAnimationFrame, которая лучше подходит для цикличных анимаций — она производительней и тратит меньше батареи. Как только она будет поддерживаться всеми современными браузерами использование setInterval будет плохим тоном.

Использование targetTouches и changedTouches

Важно понять, что event.touches это массив всех пальцев, которые контактируют с экраном, не только те, что касаются DOM element’а. Наиболее подходящим обычно является event.targetTouches или event.changedTouches.

Наконец, если вы разрабатываете под мобильные устройства, вы должны знать важные моменты, которые описаны в статье Eric Bidelman и в документе W3C.

Поддержка устройств

К сожалению не все устройство поддерживают события прикосновения в должном качестве. Я написал скрипт для диагностики, который отображает информацию о поддержке touch API тем или иным устройством, а также точность touchmove. Я протестировал Android 2.3.3 на Nexus One и Nexus S, Android 3.0.1 на Xoom, и iOS 4.2 на iPad и iPhone.

В двух словах, все эти браузеры поддерживают touchstart, touchend, и touchmove.
В спецификации описаны ещё 3 события, но ни один из браузеров не поддерживает их:
touchenter: палец входи в DOM element.
touchleave: палец покидает DOM element.
touchcancel: касание прервано (зависит от реализации).

Все протестированные браузеры также предоставляют списки прикосновений — touches, targetTouches и changedTouches. Однако, ни один из браузеров не поддерживает ни radiusX ни radiusY ни rotationAngle, которые определяют форму пальца.

Событие touchmove, срабатывает порядка 60 раз в секунду на всех протестированных устройствах.

Android 2.3.3 (Nexus)

В браузере Android Gingerbread (Nexus One и Nexus S) отсутствует поддержка multi-touch. Это известная проблема.

Android 3.0.1 (Xoom)

В браузере Xoom есть поддержка мультитача, но она работает только на одном DOM элементе. Браузер не может корректно обработать два параллельных события на разных DOM элементах. Другими словами, следующий код будет обрабатывает два параллельных события:

Читайте также:  Тв для андроид с порноканалами
iOS 4.x (iPad, iPhone)

Устройства на iOS в полной мере поддерживают Touch API.

Инструменты разработчика

В мобильной разработке всегда проще прототипировать на компьютере и по необходимости использовать живое мобильное устройство для теста. Мультитач это одна из всех вещей, которую сложно протестировать на обычном ПК, т.к многие ПК не поддерживают мультитач.

Необходимость тестирования на мобильных устройствах может увеличить цикл разработки, т.к. каждое изменение вы должны залить на сервер, а затем на устройство (прим. пер. Очень сомнительное затруднение). На мобильных устройствах отсутствуют необходимые инструменты разработчика.

Решение этой проблемы — симулирование события прикосновения на вашем ПК. Одноточечное прикосновение можно симулировать мышкой. Мультитач может быть симулирован на устройствах с поддержкой мулититач, например на новых Apple MacBook.

Одноточечные события

Если вы желаете симулировать одноточечное событие на вашем ПК, то попробуйте Phantom Limb, которая симулирует события прикосновения на страницах.

Также существует плагин Touchable для jQuery, который обединяет события мыши и прикосновения.

Мультитач события (решение только для MAC)

/Library/Internet Plug-Ins/.
2. Скачайте приложение TongSeng TUIO для MagicPad и запустите сервер.
3. Скачайте JavaScript библиотеку MagicTouch.js для симуляции событий прикосновения, совместимых со стандартом.
4. Подключите magictouch.js и плагин npTuioClient:

Я тестировал этот метод только в Chrome 10, но он должен работать и в других современных браузерах.

Если ваш ПК не поддерживает мультитач вы можете симулировать события прикосновения, используетя другие TUIO трекеры такие как reacTIVision. Подробная информация доступна на сайте проекта TUIO.

Ваши жесты могут пересекаться с жестами OS. На OS X вы можете настроить системные события в System Preferences > Trackpad

Мультитач события поддерживаются все большим числом мобильных браузеров, Я очень рад, что новые веб-приложения используют этот API в полную силу.

От переводчика
В комментариях к оригинальной статье также проскакивала ссылка: Touching and Gesturing on the iPhone
Предложения, пожелания, критика приветствуется!

Источник

По следам бага и немного о событиях MotionEvent в Android

Думаю, многие из нас писали код вида:

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

Год назад я с друзьями разрабатывал приложение, где очень многое упиралось в обработку касаний. Однажды, загрузив новые исходники из репозитория и собрав приложение, я обнаружил, что вертикальная координата касания определяется неверно. Просматривая последние коммиты команды, я наткнулся на интересную строку, где внезапно от y-координаты отнималось 100. То есть, что-то вроде «y -= 100;», причем, это число не было вынесено как константа и вообще было непонятно почему именно 100. На мой очевидный вопрос я получил ответ «Ну, мы опытным путем определили, что в этом месте y-координата всегда на 100 (пикселей) больше, чем должна быть». Здесь, конечно, стоило бы перечитать документацию по обработке касаний и, просмотрев код проекта, найти ошибку, но я решил пойти более интересным путем – проследить по исходникам Android за MotionEvent от его получения до утилизации.

Если я смог кого-то заинтриговать историей в стиле «По следам полосатого бага» — добро пожаловать под кат.

Мораль

Для начала убедимся, что хранить MotionEvent, который пришел к нам с onTouch – плохо. Я использовал небольшое тестовое приложение со следующим кодом:

Запускаем приложение, несколько раз тапаем в одну точку под ActionBar-ом и смотрим в логи. Лично я получил следующую картину: «32.0», «41.0 41.0», «39.0 39.0 39.0», «39.0 39.0 39.0 39.0». То есть, после первого вызова мы сохранили в истории объект с y=32, но уже после следующего нажатия y этого объекта равен 41, а в историю заносится объект с таким же y. На самом деле это все один и тот же объект, который был использован при первом вызове onTouch и повторно использован при втором его вызове. Поэтому мораль проста: не храните MotionEvent, полученный в onTouch! Используйте этот объект только в рамках метода onTouch, а для остальных нужд извлекайте из него координаты и храните их в PointF, например.

Читайте также:  Prado 150 android auto

Исходники Android – пул MotionEvent

А теперь предлагаю заглянуть в кроличью нору исходников Android и определить почему MotionEvent ведет себя именно таким образом.

Во-первых, уже по поведению тестового приложения понятно, что объекты MotionEvent не создаются при каждом касании, а повторно используются. Сделано это потому, что касаний может быть много за короткий промежуток времени и создание множества объектов ухудшило бы производительность. Как минимум за счет учащения сборки мусора. Представьте, сколько объектов создавалось бы за минуту игры в Fruit Ninja, ведь события – это не только DOWN, UP и CANCEL, но и MOVE.

Логика работы с пулом объектов MotionEvent находится классе MotionEvent — grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.2_r1.1/android/view/MotionEvent.java. С пулом здесь связаны статические методы и переменные. Максимальное количество одновременно хранимых объектов определяет константа MAX_RECYCLED (и равна она 10), счетчик хранимых объектов – gRecyclerUsed, для синхронизации и обеспечения работы в асинхронном режиме используется gRecyclerLock. gRecyclerTop – голова списка объектов, оставленных на утилизацию. И еще есть не статическая переменная mNext, а также mRecycledLocation и mRecycled.

Когда системе нужен объект, вызывается статический метод obtain(). Если пул пуст (gRecyclerTop == null), создается и возвращается новый объект. В противном же случае возвращается последний утилизированный объект (gRecyclerTop), а его место занимает предпоследний (gRecyclerTop = gRecyclerTop.mNext).

Для утилизации вызывается recycle() на утилизируемом объекте. Он занимает место «последнего добавленного» (gRecyclerTop), а ссылка на текущий «последний» сохраняется в mNext (mNext = gRecyclerTop). Это все происходит после проверки на переполнение пула.

Исходники Android – обработка MotionEvent

Нырять слишком глубоко не будем и начнем с метода handleMessage(Message msg) — grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.2_r1.1/android/view/ViewRoot.java?av=f#1712 – класса ViewRoot. Сюда приходит уже готовый MotionEvent (полученный системой через MotionEvent.obtain()), обернутый в Message. Метод, кстати, служит для обработки не только касаний, но и других событий. Поэтому тело метода – большой switch, в котором нас интересуют строки с 1744 по 1847. Здесь происходит предварительная обработка события, затем mView.dispatchTouchEvent(event), затем же событие добавляется в пул: event.recycle(). Метод dispatchTouchEvent(…) вызывает событие слушателя, если таковой имеется, и пытается делегировать обработку события внутренним View.

Следы бага

И теперь вкратце о том, в чем заключался баг.

Для начала немного о том, что конкретно делали с MotionEvent в том проекте. Получив объект, приложение сохраняло его в переменную, ждало некоторое количество миллисекунд и обрабатывало его. Связано такое поведение было с жестами: грубо говоря, если пользователь коснулся экрана и задержал палец на секунду – показать ему определенный диалог. Приложение получало событие ACTION_DOWN и, не получив в течение секунды событий ACTION_UP или ACTION_CANCEL, реагировало. Причем, реагировало исходя из инициирующего MotionEvent. Таким образом, ссылка на него жила некоторое время, за которое могло произойти несколько других событий касания.

Последовательно происходило следующее:
1. Пользователь касался экрана.
2. Система получала новый объект методом MotionEvent.obtain() и наполняла его данными о касании.
3. Объект события попадал в handleMessage(…), там он предобрабатывался и, несколько методов спустя, попадал в метод onTouch() слушателя.
4. Метод onTouch() сохранял ссылку на объект. Здесь же запускается таймер.
5. В методе handleMessage(…) объект помещался в пул — event.recycle(). То есть, система теперь считает этот объект свободным для повторного использования.
6. Пока таймер тикает, пользователь коснулся экрана еще несколько раз, при этом для обработки этих касаний использовался один и тот же объект.
7. Таймер завершил отсчет, вызывается некий метод, который обращается по ссылке к объекту MotionEvent, полученному при первом касании. Объект тот же, а вот x и y уже успели поменяться.

В тестовом же примере все тоже было просто:
1. Первое касание. Запрашивается объект MotionEvent. Поскольку вызов первый – объект создается.
2. Объект наполняется информацией о касании.
3. Объект приходит в onTouch() и мы сохраняем ссылку на него в списке-истории.
4. Объект утилизируется.
5. Второе касание. Запрашивается объект MotionEvent. Поскольку в пуле уже есть один – он и возвращается.
6. У полученного из пула объекта меняются координаты.
7. Объект приходит в onTouch(), мы добавляем его в историю, но это тот же объект, что и уже есть в истории, а координаты первого касания утеряны – их заменили координаты второго касания.

Источник

Оцените статью