Главная » Программирование игр под Android » Keyboard Handler: вверх, вверх, вниз, вниз, влево, вправо…

0

Обработчик событий клавиатуры KeyboardHandler используется для выполнения целого ряда задач. Прежде всего он должен быть связан с View, так как от него KeyboardHandlег получает информацию о событиях клавиатуры. Далее ему необходимо сохранить текущее состояние каждой клавиши для предъявления этого состояния в ходе опроса. Ему также понадобится список экземпляров класса KeyEvent. И наконец, все это нужно правильно синхронизировать, так как он будет принимать события из потока пользовательского интерфейса, которые будут обрабатываться в главном игровом цикле, запущенном в другом потоке. Достаточно много работы. В качестве небольшого напоминания еще раз посмотрим на класс KeyEvent, который мы определили как часть интерфейса ввода Input:

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

Листинг 5.7 показывает реализацию обработчика с помощью нашего нового класса Pool и Android API, который мы обсуждали ранее.

Листинг 5.7. KeyboardHandler.Java: обрабатывает клавиатуру package com.badlogic.androidgames.framework.Imp.;

Класс KeyboardHandl er реализует интерфейс OnKeyLi stener, так что он может получать клавиатурные события из View. Переходим к нашим элементам.

Первый элемент – это массив, содержащий 128 булевых значений. Мы сохраним текущее состояние (нажата или не нажата) каждой клавиши в данном массиве. Это состояние индексируется кодом клавиши. Удобно, что константы android.view. KeyEvent. KEYCODEXXX, которые задают коды клавиш, варьируются в промежутке от 0 до 127, так что мы можем сохранить их в форме, удобной для сборщика мусора. Обратите внимание, что, к сожалению, имя нашего класса KeyEvent совпадает с классом KeyEvent Android, экземпляры которого будут передаваться методу OnKeyEventListener.onKeyEventO. Такая путаница возникает только с кодом этого обработчика. Поскольку вряд ли клавиатурное событие можно поименовать лучше, чем KeyEvent, я предпочитаю оставить все как есть.

Следующий член PooI хранит экземпляры наших классов KeyEvent. Поскольку мы не хотим перегружать сборщик мусора работой, будем многократно использовать все объекты KeyEvent, которые создали.

Третий элемент хранит KeyEvent, которые пока не были обработаны классом Game. Каждый раз, когда мы получаем новое событие клавиатуры в потоке пользовательского интерфейса, оно добавляется в этот список.

Последний элемент хранит KeyEvent, которые мы вернем при вызове KeyboardHandl ег. getKeyEvents. Чуть ниже будет показано, почему мы дважды буферизуем клавиатурные события.

Данный конструктор имеет только один параметр, обозначающий тот вид (View), от которого мы хотим получать клавиатурные события. Мы создаем экземпляр класса Pool с соответствующим PoolObjectFactory, регистрируем обработчик как OnKeyl I stener во View и, наконец, убеждаемся, что View получает события клавиатуры, поместив этот вид в фокус.

Далее реализуем метод интерфейса OnKeyL i stener. onKey , который вызывается каждый раз, когда View обрабатывает новое клавиатурное событие. Однако таким образом игнорируются все клавиатурные события с типом KeyEvent. ACTION MULTIPLE. Чтобы это исправить, используем блок синхронизации. Не забывайте, что все события поступают из потока пользовательского интерфейса и затем обрабатываются потоком основного цикла, поэтому нам необходимо убедиться, что ни один из элементов не может быть параллельно доступен для нескольких потоков.

Внутри блока синхронизации мы сначала получаем экземпляр класса KeyEvent (нашей реализации KeyEvent) из Pool. Это поможет нам получить или переработанный, или новый экземпляр класса в зависимости от состояния Pool. Далее устанавливаем элементы KeyEvent, а именно keyCode и keyChar, которые берутся из KeyEvent Android, переданного в этот метод. Далее декодируем тип KeyEvent Android и устанавливаем соответствующие значения для типа нашего события KeyEvent и элемента в массиве pressedKey. В конце добавляем KeyEvent в список keyEventBuf f ег, который определили ранее.

Далее переходим к методу isKeyPres sed, который обычно реализует семантику Input. i sKeyPressed. Мы передаем в него целое число, которое соответствует коду клавиши (одной из констант KeyEvent.KEYCODE XXX Android) и возвращаем информацию о том, нажата данная клавиша или нет. Статус клавиши можно получить из массива pressedKey, предварительно проверив границы диапазона. Обратите внимание, что мы установили элементы данного массива в прошлом методе, вызываемом в потоке пользовательского интерфейса. Поскольку мы снова работаем с простейшими типами, необходимости в синхронизации нет.

Последний метод нашего обработчика называется getKeyEvents. Он реализует семантику метода Input. getKeyEvents. Мы снова запускаем блок синхронизации, так как данный метод вызывается из другого потока.

Теперь начинаются интересное™. Мы проходим в цикле по массиву keyEvents и вставляем все KeyEvent,хранящиеся в нем, в наш Pool. Помните, как мы получали экземпляры KeyEvent из класса Pool в методе опКеуО в потоке пользовательского интерфейса? Здесь мы вставляем их назад в Pool. Но разве список KeyEvent не пуст? Данный список будет пуст только во время первого вызова данного метода. Чтобы понять, почему возникает такая ситуация, необходимо изучить данный метод до конца.

После нашего загадочного цикла вставки в Pool мы очищаем список KeyEvent и заполняем его событиями из списка keyEventsBuf fer. И наконец, очищаем список keyEventsBuffer и возвращаем свежезаполненный список KeyEvent вызывающей стороне. Что же происходит здесь?

Проще всего это объясняется следующим простым примером. Мы проверяем, что происходит со списками KeyEvent и keyEventsBuffer, а также с Pool каждый раз, когда новое событие появляется в потоке пользовательского интерфейса или когда игра запрашивает новое событие из основного потока:

1. Сначала получаем новое событие в потоке пользовательского интерфейса. В Pool пока ничего нет, поэтому создается новый экземпляр класса KeyEvent (KeyEventl). Он помещается в список keyEventsBuffer.

2. Вызываем getKeyEvents  в основном потоке. Он использует KeyEventl из списка keyEventsBuffer и помещает его в список keyEvent, который возвращается вызывающей стороне.

3. Получаем еще одно событие в потоке UI. В нашем Pool по-прежнему ничего нет, поэтому создается еще один экземпляр класса KeyEvent (KeyEvent2). Он также помещается в список keyEventsBuffer.

4. Основной поток снова вызывает getKeyEvents. Здесъначинается самое интересное. При входе в данный метод список KeyEvent по-прежнему содержит KeyEventl. Цикл вставки поместит данное событие в наш Pool. Затем он очистит список KeyEvent и вставит другой KeyEvent в keyEventsBuffer. В нашем случае это KeyEvent2. Мы только что переработали клавиатурное событие.

5. Наконец, получаем еще одно событие в потоке UI. На этот раз у нас есть свободный KeyEvent в Pool, который мы, разумеется, используем еще один раз. И никаких сборщиков мусора.

Однако у данного механизма есть серьезный недостаток: нам придется достаточно часто вызывать KeyboardHandler.getKeyEvents, иначе список быстро заполнится и нельзя будет возвратить объекты в Pool. Однако если мы будем помнить об этом, все должно быть в порядке.

Обработчики касаний

А вот и последствия разделения на старые и новые версии.

Все константы, которые мы использовали в коде мультитач (например, MotionEvent.ACTI0N P0INTER ID MASK), недоступны в версиях Android 1.5 или 1.6. Мы вполне можем применять их в нашем коде, если нашей конечной целью является запуск программы на той версии Android, которая поддерживает данный API. Однако тогда наше приложение не будет работать на устройствах, на которых установлена версия Android 1.5 или 1.6. Но мы ведь с вами хотим, чтобы наша игра была доступна на всех версиях Android. Как же нам решить данную проблему?

Для этого воспользуемся небольшой хитростью. Напишем два обработчика, один из которых будет использовать API Android 1.5 для обработки одиночных касаний (single-touch), а другой – мультитач API Android 2.0 и выше. До тех пор пока мы не будем использовать мультитач-обработчик на устройствах с версией Android ниже 2.0, мы можем быть абсолютно спокойны. Виртуальная машина не будет загружать код, поэтому можно не волноваться о том, что появятся исключения. Все, что нам нужно, – выяснить, какая версия Android установлена на устройстве, и выбрать для нее соответствующий обработчик. Вы увидите, как это работает, когда мы будем обсуждать класс Androidlnput. А сейчас сконцентрируемся на наших двух обработчиках.

Источник: Mario Zechner / Марио Цехнер, «Программирование игр под Android», пер. Егор Сидорович, Евгений Зазноба, Издательство «Питер»

По теме:

  • Комментарии