Главная » Разработка для Android » Фреймворк Android

0

 

СОЗДАНИЕ ВИДА

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

Будут рассмотрены приемы реализации графического пользовательского интерфейса в Android. Здесь будет объяснена архитектура инструментария для написания пользовательских интерфейсов Android, на практических примерах будет показано, как используются простейшие элементы интерфейса, в частности кнопки и текстовые поля. Кроме того, здесь мы поговорим об обработке событий, использовании нескольких потоков для разгрузки долговременных задач, чтобы пользовательский интерфейс не зависал, и коснемся других вопросов, помогающих сделать пользовательский интерфейс красивым и эффективным.

АРХИТЕКТУРА ГРАФИЧЕСКОГО ПОЛЬЗОВАТЕЛЬСКОГО ИНТЕРФЕЙСА В ANDROID

Среда Android добавляет в экосистему Java еще один инструментарий для создания графических пользовательских интерфейсов, дополнительно к AWT, Swing, SWT, LWUIT и др. Если вы работали с какими-либо из этих технологий, фреймворк пользовательского интерфейса Android покажется вам знакомым. Как и вышеперечисленные системы, он однопоточный, событийно-управляемый и основан на библиотеке вкладываемых друг в друга компонентов.

Фреймворк пользовательского интерфейса Android, как и другие фреймворки пользовательских интерфейсов Java, организован на базе распространенного паттерна «Модель-вид-контроллер», который схематически изображен на рис. 7.1. Здесь предоставляются инструменты и обеспечивается структура для построения контроллера, обрабатывающего пользовательский ввод (например, нажатия клавиш или прикосновения к экрану), а также вида, который отображает на экране графическую информацию.

Рис. 7.1. Концепция «Модель-вид-контроллер»

Модель

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

В то время как вид и контроллер отдельно взятого приложения обязательно будут отражать свойства модели, с которой они работают, отдельно взятая модель может использоваться несколькими приложениями. Рассмотрим, например, МР3-плеер и приложение, преобразующее файлы формата МР3 в формат WAV. Модель обоих приложений включает в себя формат файлов МР3. Однако в первом приложении есть привычные элементы управления в виде кнопок «Стоп», «Пуск» и «Пауза», и оно воспроизводит звуки. Второе может не издавать вообще никаких звуков. Зато у него будут элементы для управления таким показателем, как скорость передачи информации (битрейт). Модель – это, в первую очередь, сущность для работы с данными.

Вид

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

Например, изобразительным компонентом гипотетического МР3-плеера может быть эскиз обложки альбома, композиция из которого сейчас воспроизводится. Другой вид может отображать название песни, воспроизводимой в данный момент, а третий – содержать более мелкие виды, например кнопки «Стоп», «Пуск» и «Пауза».

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

На практике фреймворк пользовательского интерфейса Android более эффективен, чем можно представить себе по данному упрощенному описанию. Он не отрисовывает область родительского вида, если есть гарантия, что позже эту область отрисует какой-нибудь из дочерних элементов. Было бы напрасной тратой времени отрисовывать фон под непрозрачным объектом. Кроме того, напрасной была бы работа по перерисовке частей вида, которые не изменяются.

Контроллер

Контроллер – это часть приложения, отвечающая на внешние воздействия, например на нажатие клавиши, на прикосновение к экрану, на входящий вызов и т. д. Контроллер реализуется в виде очереди событий (event queue). Каждое внешнее действие представляется как уникальное событие в очереди. Фреймворк по порядку удаляет события из очереди и распределяет (диспетчирует) их.

Например, когда пользователь нажимает какую-либо клавишу телефона, система Android генерирует событие KeyEvent и добавляет его в очередь событий. После того как закончится обработка событий, попавших в очередь ранее, KeyEvent удаляется из очереди и передается в качестве параметра вызова методу dispatchKey Event того вида View, который выбран в данный момент.

После того как событие отправлено в компонент, который находится в фокусе, этот компонент может совершить действие, необходимое для изменения внутреннего состояния программы. Например, в приложении, представляющем собой МРЗ-плеер, когда пользователь нажимает на экране кнопку «Пуск/Пауза» и событие направляется к объекту этой кнопки, метод-обработчик может обновить модель для возобновления проигрывания какого-то выбранного ранее звукового файла.

Все вместе

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

Практически любое изменение состояния модели требует соответствующего изменения в виде. Например, в ответ на нажатие клавиши компонент EditText должен отобразить только что введенный символ в точке вставки. Аналогично в приложении «Телефонный справочник» при нажатии контакта этот контакт будет подсвечен, а контакт, который был подсвечен ранее, – померкнет.

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

Рано или поздно событие перерисовки удаляется из очереди и направляется обработчику. Обработчиком событий перерисовки является View. Дерево видов перерисовывается; каждый вид отвечает за отображение собственного состояния в тот момент, когда он отрисовывается.

Чтобы конкретизировать все сказанное, проследим описанный цикл в гипотетической программе для воспроизведения МР3.

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

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

3. Поскольку данный виджет представляет собой кнопку «Пуск/Пауза», код приложения, обрабатывающий нажатие кнопки, сообщает модели, что следует возобновить воспроизведение звукового файла.

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

5. Запрос на перерисовку добавляется в очередь событий и, наконец, обрабатывается, как это описано в подразделе «Вид» выше.

6. Экран перерисовывается. При этом кнопка переходит в состояние «Пуск» и все снова оказывается синхронизированным.

Объекты, являющиеся компонентами пользовательского интерфейса, например кнопки и текстовые поля, на самом деле реализуют и методы вида, и методы контроллера. Только так и нужно делать. Когда вы добавляете к пользовательскому интерфейсу вашей программы кнопку (Button), то хотите, чтобы она появилась на экране, а также чтобы она выполняла какое-то действие, когда пользователь ее нажимает. Даже притом, что два логических элемента пользовательского интерфейса, и вид и контроллер, реализуются на одном объекте, нужно позаботиться, чтобы между ними не было непосредственного контакта. Например, методы контроллера никогда не должны напрямую изменять изображение. Пусть код, который изменяет состояние, сначала посылает запрос на перерисовку, а потом вызывает методы отображения, которые позволят компоненту отразить новое состояние. Если писать код таким образом, то сводятся к минимуму проблемы с синхронизацией, программа остается надежной и уберегается от ошибок.

Необходимо заострить внимание еще на одном важном аспекте пользовательского интерфейса Android: он однопоточный. Единственный поток удаляет события из очереди, чтобы делать обратные вызовы контроллера и отображать вид. Это важно по нескольким причинам.

Простейшее следствие применения однопоточного пользовательского интерфейса заключается в том, что при координации состояний вида и контроллера можно обойтись без блоков synchronized. Это очень ценная оптимизация.

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

Третья причина, по которой не следует забывать, что только один поток занимается изъятием и распределением событий пользовательского интерфейса из их очереди, заключается в том, что, если ваш код по каким-то причинам остановит выполнение этого потока, пользовательский интерфейс зависнет! Если отклик компонента на событие прост (например, сводится к изменению состояния переменных, созданию новых объектов и т. д.), то совершенно оправданно обрабатывать такие действия в основном потоке событий. Если же, напротив, обработчик должен получить ответ от какой-нибудь удаленной сетевой службы или запустить сложный запрос к базе данных, то весь пользовательский интерфейс застынет, пока не будет получен ответ на запрос. Определенно, работать с таким интерфейсом будет неудобно!

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

По теме:

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