Главная » Разработка для Android » Продвинутые способы подключения: фокус и поточность – программирование Android

0

 

Как было показано в примере 7.7 и в разделе «Слушание событий касания», события MotіonEvent направляются к тому виджету, к рабочему прямоугольнику которого относится точка координат, где произошло касание, сгенерировавшее данное событие. Не так просто определить, какой именно виджет должен получать событие KeyEvent. Чтобы это делать, фреймворк пользовательского интерфейса Android, как и другие подобные фреймворки, поддерживает концепцию «выделенной области» (selection), которая также называется термином «фокус».

Чтобы виджет мог попасть в фокус, его атрибут f ocusabl е должен иметь значение true. Этого можно добиться либо при помощи атрибутов макета (layout attributes) в XML (у видов EditText в примере 7.3 атрибут focusablе имеет значение false), либо при помощи метода setFocusabl е, как показано в первой строке кода в примере 7.10. Пользователь перемещает фокус с одного объекта View на другой, работая с клавишами крестовины или нажимая на экран – если он сенсорный.

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

Чтобы получать уведомления, когда вид попадает в фокус или выходит из него, нужно установить OnFocusChangeListener. В примере 7.12 показан слушатель, при помощи которого в демонстрационную программу можно добавить функционал, связанный с фокусом. Этот код автоматически добавляет в DotView случайно расположенные точки через случайные интервалы, когда данный вид оказывается в фокусе.

Пример 7.12. Работа с фокусом

В OnFocusChangeListener нет ничего необычного. Когда виджет DotView попадает в фокус, он создает DotGenerator и порождает поток для его запуска. Когда виджет выходит из фокуса, DotGenerator останавливается и освобождается. Новое поле данных dotGenerator (его объявление в примере не показано) является ненулевым лишь тогда, когда DotView располагается в фокусе. Есть еще один важный и мощный инструмент, применяемый при реализации DotGenerator, чуть ниже мы его рассмотрим.

Чтобы поместить тот или иной виджет в фокус, нужно вызвать один из методов его класса View – requestFocus. Когда requestFocus вызывается применительно к новому виджету, запрос отправляется вверх по дереву, от одного родительского, элемента к другому, вышестоящему родительскому элементу, до самого корня дерева. Корень запоминает, какой виджет находится в фокусе, и передает последующие события, связанные с нажатиями клавиш, непосредственно ему.

Именно так фреймворк пользовательского интерфейса переводит фокус на новый виджет в ответ на нажатия клавиш крестовины. Фреймворк идентифицирует виджет, который должен оказаться в фокусе следующим, и вызывает метод requestFocus этого виджета. В результате виджет, который находился в фокусе предыдущим, выходит из фокуса, а целевой виджет – попадает в фокус.

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

Рассмотрим, например, что произойдет, если нажата правая клавиша крестовины и фреймворк пытается перевести фокус на виджет, вплотную прилегающий справа к тому виджету, который находится в фокусе в настоящий момент. При взгляде на экран может быть совершенно очевидно, что это за виджет, но ситуация совсем не так ясна для дерева видов. Целевой виджет может находиться на другом уровне дерева и отстоять на несколько «ветвей» от виджета, который находится в фокусе сейчас. Идентификация такого виджета зависит от точных параметров виджетов, располагающихся совсем в других, отдаленных частях дерева. К счастью, несмотря на немалую сложность, реализация этого механизма во фреймворке пользовательского интерфейса Android обычно работает так, как ожидается.

Если что-то идет не так, то можно воспользоваться четырьмя свойствами – они задаются либо методом приложения, либо XML-атрибутом, – которые принудительно вызывают желаемое навигационное поведение при переводе фокуса. Эти свойства – nextFocusDown, nextFocusLeft, nextFocusRight и nextFocusUp. Если задать одно из этих свойств со ссылкой на конкретный виджет, то можно гарантировать, что при нажатии клавиш крестовины в определенном направлении фокус будет переходить именно на тот виджет, на который указывает ссылка.

Еще одна сложность, связанная с механизмом фокус, заключается в том, что пользовательский интерфейс Android различает фокус крестовины и фокус, возникающий в результате касаний сенсорного экрана (если такой экран есть на устройстве). Чтобы понять, зачем необходимо такое различие, напомним, что на экране, не допускающем сенсорного ввода, единственный способ нажать кнопку – навести на нее фокус при помощи крестовины, а потом сделать щелчок центральной клавишей крестовины. Но на экране, который принимает сенсорный ввод, вообще не приходится наводить фокус на кнопку. Чтобы нажать кнопку, достаточно легонько ударить по ней пальцем, независимо от того, какой именно виджет находится в фокусе в данный момент. Тем не менее даже на сенсорном экране сохраняется необходимость наводить фокус на виджет, который может воспринимать нажатия клавиш. Таков, например, виджет EditText. Его нужно однозначно определять как целевой элемент, к которому будут относиться последующие события, связанные с клавишами. Чтобы правильно обрабатывать обе разновидности фокуса, нужно разобраться с тем, как класс View обрабатывает FOCUSABLE_IN_TOUCH_MODE, а также изучить методы іsFocusabl elnTouchMode и іslnTouchMode, относящиеся к View.

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

Допустим, вам нужно добавить в справочник номер телефона вашего друга. И при этом вы сразу же переходите в приложение «Телефон», чтобы уточнить последние несколько цифр этого номера. Конечно, вам не понравится, если при перезапуске адресной книги нужно будет снова вручную наводить фокус на то поле EditText, куда вы только что вводили текст. Ожидается, что приложение должно быть именно в том состоянии, в котором вы его оставили.

С другой стороны, у такого поведения могут быть необычные побочные эффекты. В частности, при реализации функции автоматического добавления точек из примера 7.12 точки продолжают добавляться в вид DotView, даже если в настоящий момент он накрыт другим окном! Если фоновая задача должна выполняться только тогда, когда конкретный виджет находится на виду, то эту задачу нужно удалять при выходе виджета из фокуса, то есть когда Window оказывается вне фокуса и работа Activity приостанавливается или прекращается.

Реализация механизма фокусировки осуществляется в основном в классе ViewGroup, при помощи методов вроде requestFocus и requestChi 1 dFocus. Если потребуется реализовать совершенно новый механизм фокусировки, нужно будет подробно изучить методы и правильно их переопределить.

В примере 7.13 мы отвлечемся от темы фокусировки и вернемся к реализации недавно добавленной функции автоматического добавления точек. Здесь представлена реализация DotGenerator.

Пример 7.13. Обработка потоков

Вот пояснения к выделенным строкам кода.

Создается объект android. os. Handler.

Создается новый поток, который будет запускать makeDot в элементе 4. © В основном потоке запускается DotGenerator.

makeDot запускается обработчиком Handler, созданным в элементе 1.

Упрощенная реализация DotGenerator могла бы просто вызывать makeDot напрямую из метода run. Тем не менее такая операция небезопасна, поскольку makeDot не является потокобезопасным, так же как и Dots и DotView. Такую ситуацию будет сложно правильно организовать и еще сложнее – поддерживать. Фреймворк пользовательского интерфейса Android запрещает доступ к объекту View от нескольких потоков. При запуске упрощенной реализации работа приложения аварийно завершится, выдав исключение Runti meException такого плана:

С такой проблемой мы уже сталкивались, когда создавали обработчик Handler. Чтобы обойти такое ограничение, DotGenerator создает объект Handler в своем конструкторе. Объект Handler ассоциирован с потоком, в котором он был создан, и этот поток получает безопасный конкурентный доступ к базовой очереди событий.

Поскольку DotGenerator создает Handler в ходе собственного процесса конструкции, Handler ассоциируется с основным потоком. Теперь DotGenerator может использовать Handler для постановки в очередь объекта Runnablе, созданного в другом потоке, причем такой объект вызывает makeDot из потока пользовательского интерфейса. Оказывается, как вы уже и сами догадались, что базовая очередь событий, на которую указывает Handler, – та самая, с которой работает фреймворк пользовательского интерфейса. Вызов makeDot удаляется из очереди и обрабатывается, как и любое другое событие пользовательского интерфейса, в обычном порядке. В данном случае в результате запускается Runnablе, относящийся к makeDot. makeDot вызывается из основного потока, и пользовательский интерфейс остается однопоточным.

Стоит повторить, что это – важный паттерн написания основного кода для пользовательского интерфейса Android. Если обработка действий, запущенная по инициативе пользователя, продлится более нескольких миллисекунд, то при выполнении этого действия в основном потоке можно замедлить работу всего пользовательского интерфейса или, что еще хуже, надолго его «подвесить». Если основной поток приложения не обслужит свою очередь событий за пару секунд, операционная система Android принудительно завершит приложение, так как оно не отвечает. Классы Handler и AsyncTask позволяют программисту избегать таких опасных ситуаций, делегируя медленные или подолгу выполняемые задачи другим потокам так, чтобы основной поток мог продолжать обслуживать пользовательский интерфейс. В этом примере демонстрируется использование потока Thread с обработчиком Handler, который периодически ставит в очередь события обновления пользовательского интерфейса.

Демонстрационное приложение здесь немного упрощено. Оно ставит операции создания новой точки и добавления ее к модели в очередь основного потока. Более сложное приложение могло бы при создании модели передавать ей Handler от основного потока, а пользовательскому интерфейсу предоставить возможность получить Handler, созданный в потоке модели. Модель, работающая в собственном потоке, использовала бы класс Looper для удаления из очереди и диспетчирования сообщений, поступающих от пользовательского интерфейса. Но прежде, чем приступать к построению чего-то настолько сложного, попробуйте использовать Serviсе или ContentProvider.

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

Источник: Android. Программирование на Java для нового поколения мобильных устройств

По теме:

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