Android предлагает сложную и мощную компонентную модель для построения пользовательского интерфейса, основанную на базовых классах компоновки View и ViewGroup . Платформа включает в себя множество предварительно созданных подклассов View и ViewGroup — называемых виджетами и компоновками соответственно — которые вы можете использовать для построения своего пользовательского интерфейса.
Частичный список доступных виджетов включает в себя Button , TextView , EditText , ListView , CheckBox , RadioButton , Gallery , Spinner , а также более специализированные AutoCompleteTextView , ImageSwitcher и TextSwitcher .
Среди доступных макетов — LinearLayout , FrameLayout , RelativeLayout и другие. Дополнительные примеры см. в разделе «Распространенные макеты» .
Если ни один из готовых виджетов или макетов не соответствует вашим потребностям, вы можете создать собственный подкласс View . Если вам нужно лишь внести небольшие изменения в существующий виджет или макет, вы можете создать подкласс этого виджета или макета и переопределить его методы.
Создание собственных подклассов View позволяет точно контролировать внешний вид и функциональность элемента экрана. Чтобы дать представление о возможностях управления с помощью пользовательских представлений, приведем несколько примеров того, что с ними можно сделать:
- Вы можете создать полностью настраиваемый тип
View— например, регулятор громкости, отображаемый с помощью 2D-графики, который напоминает аналоговый электронный регулятор. - Вы можете объединить группу компонентов
Viewв один новый компонент, например, чтобы создать что-то вроде комбинированного списка (сочетание всплывающего списка и текстового поля для свободного ввода), двухпанельного элемента управления выбора (левая и правая панели со списком в каждой, где можно переназначать, какой элемент в каком списке находится) и так далее. - Вы можете изменить способ отображения компонента
EditTextна экране. В примере приложения NotePad это хорошо используется для создания страницы блокнота с разлиновкой. - Вы можете перехватывать и другие события, например, нажатия клавиш, и обрабатывать их индивидуальным способом, например, для игры.
В следующих разделах объясняется, как создавать пользовательские представления и использовать их в вашем приложении. Подробную справочную информацию см. в классе View .
Основной подход
Ниже представлен краткий обзор того, что вам нужно знать для создания собственных компонентов View :
- Расширьте существующий класс
Viewили его подкласс, добавив свой собственный класс. - Переопределите некоторые методы из суперкласса. Методы суперкласса, которые следует переопределить, начинаются с
on— например,onDraw(),onMeasure()иonKeyDown(). Это похоже на событияonвActivityилиListActivity, которые вы переопределяете для управления жизненным циклом и другими функциональными механизмами. - Используйте свой новый класс расширения. После завершения разработки вы можете использовать свой новый класс расширения вместо представления, на котором он был основан.
Полностью настраиваемые компоненты
Вы можете создавать полностью настраиваемые графические компоненты, которые будут выглядеть так, как вы пожелаете. Возможно, вам нужен графический индикатор уровня звука, похожий на старый аналоговый прибор, или текстовое окно для караоке, где прыгающий шарик движется вдоль слов, пока вы подпеваете караоке-машине. Возможно, вам захочется чего-то, чего не могут сделать встроенные компоненты, как бы вы их ни комбинировали.
К счастью, вы можете создавать компоненты, которые выглядят и ведут себя так, как вам угодно, ограничиваясь лишь вашей фантазией, размером экрана и доступной вычислительной мощностью, при этом следует помнить, что ваше приложение может работать на устройстве со значительно меньшей мощностью, чем ваша настольная рабочая станция.
Для создания полностью настраиваемого компонента следует учитывать следующее:
- Наиболее универсальным компонентом, который можно расширить, является
View, поэтому обычно начинают с его расширения для создания нового родительского компонента. - Вы можете предоставить конструктор, который будет принимать атрибуты и параметры из XML-файла, а также использовать собственные атрибуты и параметры, такие как цвет и диапазон индикатора уровня звука или ширина и затухание стрелки.
- Вероятно, вам захочется создать собственные обработчики событий, методы доступа к свойствам и модификаторы, а также более сложные механизмы поведения в вашем классе компонента.
- Вам почти наверняка потребуется переопределить
onMeasure(), а также, вероятноonDraw()если вы хотите, чтобы компонент что-то отображал. Хотя оба метода имеют поведение по умолчанию, методonDraw()по умолчанию ничего не делает, а методonMeasure()по умолчанию всегда устанавливает размер 100x100, что вам, вероятно, не нужно. - При необходимости вы также можете переопределить другие
on.
Расширьте функции onDraw() и onMeasure()
Метод onDraw() предоставляет Canvas , на котором вы можете реализовать все, что захотите: 2D-графику, другие стандартные или пользовательские компоненты, стилизованный текст или что угодно еще, что вы можете себе представить.
onMeasure() немного сложнее. Он является критически важной частью контракта рендеринга между вашим компонентом и его контейнером. Метод onMeasure() onMeasure() необходимо переопределить, чтобы эффективно и точно сообщать размеры содержащихся в нем частей. Это немного усложняется требованиями к ограничениям от родительского компонента, которые передаются в метод onMeasure() , а также необходимостью вызывать метод setMeasuredDimension() с измеренными шириной и высотой после их вычисления. Если вы не вызовете этот метод из переопределенного метода onMeasure() , это приведет к исключению во время измерения.
В общих чертах, реализация onMeasure() выглядит примерно так:
- Переопределенный метод
onMeasure()вызывается с указанием ширины и высоты, которые рассматриваются как требования к ограничениям на создаваемые вами измерения ширины и высоты. ПараметрыwidthMeasureSpecиheightMeasureSpecпредставляют собой целочисленные коды, обозначающие размеры. Полное описание ограничений, которые могут потребоваться при использовании этих параметров, можно найти в справочной документации в разделеView.onMeasure(int, int)В этой справочной документации также объясняется вся операция измерения. - Метод
onMeasure()вашего компонента вычисляет ширину и высоту, необходимые для его отображения. Компонент должен стараться оставаться в пределах переданных параметров, хотя и может их превышать. В этом случае родительский компонент может выбрать, что делать, включая обрезку, прокрутку, генерацию исключения или повторную попытку вызова методаonMeasure(), возможно, с другими параметрами измерения. - После вычисления ширины и высоты вызовите метод
setMeasuredDimension(int width, int height)с полученными значениями. В противном случае возникнет исключение.
Вот краткое описание других стандартных методов, которые фреймворк вызывает в представлениях:
| Категория | Методы | Описание |
|---|---|---|
| Творение | Строители | Существует форма конструктора, которая вызывается при создании представления из кода, и форма, которая вызывается при загрузке представления из файла макета. Вторая форма анализирует и применяет атрибуты, определенные в файле макета. |
| Вызывается после представления, и все его дочерние элементы создаются на основе XML-данных. | |
| Макет | | Призваны определить требования к размерам этого вида и всех его дочерних элементов. |
| Вызывается, когда этому представлению необходимо присвоить размер и положение всем его дочерним элементам. | |
| Вызывается при изменении размера этого представления. | |
| Рисунок | | Вызывается, когда представление должно отобразить свое содержимое. |
| Обработка событий | | Вызывается при нажатии клавиши. |
| Вызывается при нажатии клавиши. | |
| Вызывается при возникновении события движения трекбола. | |
| Вызывается при возникновении события движения на сенсорном экране. | |
| Фокус | | Вызывается при получении или потере фокуса на объекте. |
| Вызывается, когда окно, содержащее представление, получает или теряет фокус. | |
| Прикрепление | | Вызывается, когда представление привязано к окну. |
| Вызывается, когда представление отсоединяется от своего окна. | |
| Вызывается при изменении видимости окна, содержащего представление. |
Составные элементы управления
Если вы не хотите создавать полностью настраиваемый компонент, а вместо этого стремитесь создать многоразовый компонент, состоящий из группы существующих элементов управления, то лучше всего подойдет создание составного компонента (или составного элемента управления). Вкратце, это объединяет ряд более атомарных элементов управления или представлений в логическую группу элементов, которые можно рассматривать как единое целое. Например, комбинированный список может представлять собой комбинацию однострочного текстового поля EditText и расположенной рядом кнопки с прикрепленным всплывающим списком. Если пользователь нажимает на кнопку и выбирает что-либо из списка, поле EditText заполняется, но он также может ввести текст непосредственно в EditText если ему так удобнее.
В Android для этого есть еще два доступных элемента: Spinner и AutoCompleteTextView . В любом случае, этот пример с выпадающим списком является хорошим образцом.
Для создания составного компонента выполните следующие действия:
- Как и в случае с
Activity, используйте либо декларативный (на основе XML) подход для создания содержащихся компонентов, либо вкладывайте их программно из своего кода. Обычно отправной точкой является какой-либоLayout, поэтому создайте класс, который расширяетLayout. В случае с выпадающим списком вы можете использоватьLinearLayoutс горизонтальной ориентацией. Вы можете вкладывать другие Layout внутрь, поэтому составной компонент может быть произвольно сложным и структурированным. - В конструкторе нового класса сначала передайте в конструктор суперкласса все параметры, которые он ожидает. Затем можно настроить другие представления для использования в новом компоненте. Здесь вы создадите поле
EditTextи всплывающий список. Вы можете добавить свои собственные атрибуты и параметры в XML-файл, которые ваш конструктор сможет извлечь и использовать. - При желании можно создать обработчики событий, которые могут генерировать ваши встроенные представления. Например, можно создать метод-обработчик для события клика по элементу списка, чтобы обновлять содержимое поля
EditTextпри выборе элемента из списка. - При желании можно создать собственные свойства с методами доступа и изменениями. Например, можно задать значение
EditTextизначально в компоненте и запрашивать его содержимое при необходимости. - При желании можно переопределить
onDraw()иonMeasure(). Обычно это не требуется при расширенииLayout, поскольку у Layout есть поведение по умолчанию, которое, скорее всего, работает нормально. - При желании можно переопределить другие
on, например,onKeyDown(), чтобы, скажем, выбирать определенные значения по умолчанию из всплывающего списка комбинированного списка при нажатии определенной клавиши.
Использование Layout в качестве основы для пользовательского элемента управления имеет ряд преимуществ, в том числе следующие:
- Вы можете задать макет, используя декларативные XML-файлы, как и в случае с экраном активности, или же создавать представления программно и вкладывать их в макет из своего кода.
- Методы
onDraw()иonMeasure(), а также большинство других методовon(), имеют подходящее поведение, поэтому вам не нужно их переопределять. - Вы можете быстро создавать произвольно сложные составные представления и повторно использовать их, как если бы они были единым компонентом.
Изменить существующий тип представления
Если существует компонент, похожий на то, что вам нужно, вы можете расширить этот компонент и переопределить поведение, которое хотите изменить. Вы можете делать все то же самое, что и с полностью настраиваемым компонентом, но, начав с более специализированного класса в иерархии View , вы можете получить поведение, которое делает то, что вам нужно, совершенно бесплатно.
Например, демонстрационное приложение «Блокнот» показывает многие аспекты использования платформы Android. Среди них — расширение представления EditText для создания блокнота в линейку. Это не идеальный пример, и API для этого могут измениться, но он демонстрирует основные принципы.
Если вы еще этого не сделали, импортируйте пример NotePad в Android Studio или посмотрите исходный код, используя предоставленную ссылку. В частности, обратите внимание на определение LinedEditText в файле NoteEditor.java .
Вот несколько моментов, на которые следует обратить внимание в этом файле:
- Определение
Класс определяется следующей строкой:
public static class LinedEditText extends EditTextLinedEditTextопределен как внутренний класс внутри активностиNoteEditor, но он является публичным, поэтому к нему можно получить доступ какNoteEditor.LinedEditTextизвне классаNoteEditor.Кроме того,
LinedEditTextявляетсяstatic, то есть он не генерирует так называемые «синтетические методы», позволяющие ему получать доступ к данным из родительского класса. Это означает, что он ведет себя как отдельный класс, а не как нечто тесно связанное сNoteEditor. Это более чистый способ создания внутренних классов, если им не требуется доступ к состоянию из внешнего класса. Это позволяет сохранить сгенерированный класс небольшим и легко использовать его из других классов.LinedEditTextнаследуетEditText, который в данном случае является элементом интерфейса, подлежащим настройке. После завершения настройки новый класс может заменить обычный классEditText. - Инициализация класса
Как всегда, сначала вызывается конструктор суперкласса. Это не конструктор по умолчанию, но он параметризованный.
EditTextсоздается с этими параметрами при загрузке из XML-файла разметки. Таким образом, конструктор должен принимать их и передавать конструктору суперкласса. - Переопределенные методы
В этом примере переопределяется только метод
onDraw(), но вам может потребоваться переопределить и другие методы при создании собственных пользовательских компонентов.В этом примере переопределение метода
onDraw()позволяет нарисовать синие линии на холсте представленияEditText. Холст передается в переопределенный методonDraw(). Методsuper.onDraw()вызывается перед завершением основного метода. Необходимо вызвать метод суперкласса. В данном случае вызовите его в конце, после того как нарисуете нужные линии. - Пользовательский компонент
Теперь у вас есть собственный компонент, но как его использовать? В примере с Блокнотом пользовательский компонент используется непосредственно из декларативного макета, поэтому посмотрите файл
note_editor.xmlв папкеres/layout:<view xmlns:android="http://schemas.android.com/apk/res/android" class="com.example.android.notepad.NoteEditor$LinedEditText" android:id="@+id/note" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/transparent" android:padding="5dp" android:scrollbars="vertical" android:fadingEdge="vertical" android:gravity="top" android:textSize="22sp" android:capitalize="sentences" />
Пользовательский компонент создается как универсальное представление в XML, а класс указывается с использованием полного пакета. На определенный вами внутренний класс ссылаются с помощью обозначения
NoteEditor$LinedEditText, которое является стандартным способом обращения к внутренним классам в языке программирования Java.Если ваш пользовательский компонент представления не определен как внутренний класс, вы можете объявить компонент представления, указав имя XML-элемента и исключив атрибут
class. Например:<com.example.android.notepad.LinedEditText id="@+id/note" ... />
Обратите внимание, что класс
LinedEditTextтеперь находится в отдельном файле класса. Если этот класс вложен в классNoteEditor, данный метод не работает.Остальные атрибуты и параметры в определении передаются в конструктор пользовательского компонента, а затем в конструктор
EditText, поэтому это те же параметры, которые вы используете для представленияEditText. Также можно добавить свои собственные параметры.
Создание пользовательских компонентов настолько сложно, насколько это необходимо.
Более сложный компонент может переопределять еще больше on и вводить собственные вспомогательные методы, существенно настраивая свои свойства и поведение. Единственное ограничение — ваша фантазия и то, что вам нужно от компонента.
