Собственные компоненты

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

Список виджетов включает в себя кнопки (Button), текстовые поля (TextView), поля ввода (EditText), списки (ListView), флажки (CheckBox), радио-кнопки (RadioButton) и многое другое, в частности, виджеты специального назначения AutoCompleteTextView, ImageSwitcher и TextSwitcher.

Среди компонентов разметки доступны LinearLayout, FrameLayout, RelativeLayout и другие. Подробнее смотрите в разделе Разметка.

Если стандартные компоненты не могут решить вашу задачу, вы можете создать ваши собственные подклассы View. Если вам нужно лишь немного изменить характеристики существующих компонентов, вы можете просто создать наследник класса соответствующего компонента и переопределить его методы.

Создание подкласса View позволяет тонко контролировать внешний вид и функции элемента. Вот некоторые примеры компонентов, которые вы можете создать:

  • Вы можете создать полностью настраиваемый компонент, вроде “контроля громкости”, используя для отрисовки 2D графику, сделав его похожим на аналоговое электронное устройство.
  • Вы можете поместить группу визуальных компонентов в новый компонент, возможно создав что-то вроде выпадающего списка, селектора с двумя панелями (со списками слева и справа, элементы которых могут повторно использоваться).
  • Вы можете переопределить способ отображения компонента EditText на экране (в демо приложении Notepad используется хороший эффект для создания расчерченной страницы).
  • Вы можете захватывать любые события, вроде нажатия клавиши или выполнять действия любым способом (например в играх).

В следующем параграфе мы рассмотрим как создавать собственные View и использовать их в ваших приложениях. Также смотрите подробности в описании класса View.

Основные вещи

Здесь на высоком уровне описано что вам нужно сделать, чтобы начать создавать ваш собственный визуальный компонент:

  1. Расширить существующий класс View или его подклассы.
  2. Переопределить некоторые методы супер-класса. Например, методы, которые начинаются с ‘on‘: onDraw(), onMeasure(), onKeyDown(). Это похоже на управление событиями Activity, когда вы переопределяете методы жизненного цикла.
  3. Использовать ваш новый класс. После создания класса вы можете использовать его вместо компонента на котором он основан.

Примечание: расширенные классы могут быть объявлены как внутренние классы внутри явлений и использоваться ими. Это удобно, поскольку позволяет контролировать доступ к компоненту, но не является обязательным (возможно вы захотите создать новый публичный класс для широко использования внутри приложения).

Полная настройка компонента

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

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

Для создания такого компонента:

  1. Неудивительно, что наиболее общий класс, который вы можете расширить это View. Обычно с него начинают для создания своего нового супер-компонента.
  2. Вы можете создать конструктор, который может принимать атрибуты и параметры из XML, при этом вы можете использовать ваши собственные атрибуты.
  3. Вероятно вам понадобятся ваши собственные слушатели событий, доступ к свойствам и модификаторы свойств, и возможно просто более сложное поведение компонента.
  4. Почти наверняка понадобится переопределить метод onMeasure() и вероятно onDraw(), чтобы создать внешний вид компонента. Несмотря на то, что оба метода имеют стандартную реализацию, onDraw() не делает ничего, а onMeasure() всегда устанавливает размер 100х100 – это скорее всего не то, что вам нужно.
  5. Другие методы on... могут быть также переопределены при необходимости.

Расширение методов onDraw() и onMeasure()

Метод onDraw() предоставляет вам Canvas, поверх которого вы можете поместить все, что захотите: 2D графику, другие стандартные или собственные компоненты, текст, и.т.д.

Примечание: увы здесь нельзя применить 3D графику. Если вам нужно 3D, расширьте класс SurfaceView вместо View и рисуйте в отдельном потоке. Смотрите GLSurfaceViewActivity в качестве примера.

С методом onMeasure() немного сложнее. Этот метод является критической точкой между вашим компонентом и контейнером, в котором он находится. onMeasure() должен быть переопределен, чтобы эффективно и точно сообщать о размерах компонента. Это сделать несколько сложнее из-за ограничений, которые накладывает родительский компонент (который передается в метод onMeasure()) и из-за требований вызывать метод setMeasuredDimension(), передав в него высоту и ширину, как только они были рассчитаны. Если вы не вызвали этот метод из переопределенного метода onMeasure(), это приведет к исключению.

На высоком уровне метод onMeasure() должен выглядеть примерно так:

  1. Перегруженный метод onMeasure() вызывается с мерой высоты и ширины (целочисленные параметры widthMeasureSpec и heightMeasureSpec), которые должны восприниматься как ограничение по высоте и ширине. Полное руководство смотрите в документации по View.onMeasure(int, int).
  2. Ваш метод onMeasure() должен вычислять высоту и ширину, которая требуется для отрисовки компонента. Нужно попытаться не выйти за пределы ограничений, переданных в метод, однако вы можете превысить их (в таком случае родительский контейнер может решить что делать: обрезать ваш компонент, добавить скролл, вбросить исключение или еще раз вызвать onMeasure(), возможно с другими параметрами ограничения).
  3. После подсчета ширины и высоты вы должны вызвать метод setMeasureDimension(int width, int height). Если вы забудете это сделать, будет выброшено исключение.

Ниже приведен список стандартных методов, которые система вызывает для компонентов:

Категория Методы Описание
Создание Конструкторы Есть две формы конструкторов, первые вызываются при создании компонента в коде, а вторые при загрузке из файла разметки. Второй тип должен принимать все параметры и атрибуты, определенные в файле разметки.
onFinishInflate() Вызывается после того, как компонент и всего его дочерние элементы были загружены из XML.
Разметка onMeasure(int, int) Вызывается, чтобы определить размер компонента и его дочерних элементов.
onLayout(boolean, int, int, int, int) Вызывается, когда компонент должен назначить размеры и позицию его дочерних элементов.
onSizeChanged(int, int, int, int) Вызывается при изменении размера компонента.
Рисование onDraw(Canvas) Вызывается, когда компонент должен быть отрисован.
Работа с событиями onKeyDown(int, KeyEvent) Вызывается при нажатии клавиши
onKeyUp(int, KeyEvent) Вызывается при отпускании клавиши
onTrackballEvent(MotionEvent) Вызывается при движении трекболла
onTouchEvent(MotionEvent) Вызывается при касании сенсорного экрана
Фокус onFocusChanged(boolean, int, Rect) Вызывается, когда компонент получает или теряет фокус.
onWindowFocusChanged(boolean) Вызывается, когда окно, содержащее компонент получает или теряет фокус.
Прикрепление onAttachedToWindow() Вызывается, когда компонент прикрепляется к окну
onDetachedFromWindow() Вызывается, когда компонент открепляется от окна
onWindowVisibilityChanged(int) Вызывается при изменении видимости окна, содержащего компонент.

Пример собственного компонента

Демонстрационный пример CustomView из API Demos содержит новый компонент LabelView.

LabelView раскрывает несколько различных аспектов разработки собственных компонентов:

  • Наследование от класса View для создания полностью нового компонента.
  • Конструктор с параметрами, который принимает параметры XML. Некоторые из них передаются через супер-класс, но более важно то, что некоторые собственные атрибуты описываются и используются для LabelView.
  • Реализация стандартных публичные методов setText(), setTextSize(), setTextColor() и так далее.
  • Перегруженный метод onMeasure для установки размера компонента. (Обратите внимание, что в LabelView реальную работу выполняет приватный метод measureWidth()).
  • Перегруженный метод onDraw(), рисующий надпись на предоставленной области.

Вы можете посмотреть пример использования LabelView в файле custom_view_1.xml. В частности, вы можете увидеть использование двух пространств имен android: и app:. Параметры app:... являются нестандартными и объявлены во встроенном классе внутри класса R.

Объединение компонентов

Если вы не хотите создавать полностью новый компонент, а желаете собрать компонент из нескольких существующих, тогда создание объединенного компонента (Compound component) это то, что нужно. В двух словах, это объединение нескольких отдельных компонентов в логическую группу, которую можно рассматривать как одну вещь. Например, выпадающий список можно рассматривать как набор из отдельного текстового поля ввода, кнопки и всплывающего списка. Если вы нажимаете на кнопку и выбираете что-то из списка, этот пункт попадает в текстовое поле, но пользователь может также что-то напрямую написать в этом поле.

В Android уже есть два готовых компонента выпадающих списков: Spinner и AutoCompleteTextView, просто концепцию выпадающего списка проще привести в качестве примера.

Для создания объединенного компонента требуется:

  1. Обычно точкой старта является разметка (Layout), поэтому нужно создать наследник класса разметки. Возможно, в случае выпадающего списка мы можем использовать LinearLayout с горизонтальной ориентацией. Помните, что другие виды разметки могут быть вложены внутрь, объединенный компонент может быть произвольно сложных и структурированным. Обратите внимание, что как и в случае явлений, вы можете использовать либо XML, либо программный подход.
  2. В конструкторе нового класса сначала примите все параметры, которые требуются супер-классу и первым делом вызовите конструктор супер-класса с этими параметрами. Затем вы можете настроить остальные компоненты; это то место, где вы должны создать поле ввода и всплывающий список. Обратите внимание, что вы также можете включить ваши собственные атрибуты и параметры в XML, которые могут использоваться в вашем конструкторе.
  3. Вы также можете создать слушатели событий, например слушатель для события нажатия по списку для обновления содержимого текстового поля.
  4. Вы можете также создать ваши собственные свойства с методами доступа к ним, например для установки текста в текстовом поле и его получения.
  5. В случае расширения класса разметки (Layout), вам не требуется перегружать методы onDraw() и onMeasure(), поскольку разметка имеет собственную реализацию. Однако, при желании вы все еще можете это сделать.
  6. Вы можете перегрузить методы on..., вроде onKeyDown(), возможно для выбора подходящих значений во всплывающем списке при нажатии кнопки.

Таким образом, использование класса разметки в качестве основы для собственных компонентов имеет несколько преимуществ, включая:

  • Вы можете создавать разметку, используя XML файлы, как будто просто создаете новое явление, вы так же можете создавать разметку программно.
  • Методы onDraw() и onMeasure() (плюс наиболее популярные методы on...) уже реализованы и вам не нужно их создавать.
  • В конце концов, вы можете просто очень быстро собирать сложные объединенные компоненты и использовать их повторно как-будто это отдельный компонент.

Примеры объединенных компонентов

В демонстрационных проектах вы найдете два списка примеров – Example 4 и Example 6, которые демонстрируют SpeachView и расширяют LinearLayout для отображения цитат. Классы находятся в файлах List4.java и List6.java.

Изменение существующего компонента View

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

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

Если у вас его еще нет, скачайте пример NotePad и импортируйте его в Eclipse. В частности взгляните на описание MyEditText в файле NoteEditor.java.

Вот на что следует обратить внимание:

  1. Определение

    Класс начинается со следующей строки:
    public static class MyEditText extends EditText

    • Он создан как вложенный класс в явлении NoteEditor, но он публичный, а значит доступен как NoteEditor.MyEditText из других классов.
    • Этот класс статичный (static), а это значит, что он не создает так называемые “синтетические методы”, которые позволяют получить доступ к данным из родительского класса, что в свою очередь означает, что он ведет себя как отдельный класс и не имеет сильной связи с классом NoteEditor.
    • Он наследуется от класса EditText, который мы будем настраивать в этом случае. После завершения настройки, новый класс сможет заменить обычный EditText.
  2. Инициализация класса

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

  3. Перегруженные методы

    В данном примере, только один метод перегружен: onDraw() – но при необходимости могут быть перегружены и другие.

    В примере NotePad, перегрузка onDraw() позволяет нам рисовать синие линии в области (canvas) EditText (canvas передается в метод onDraw()). Метод super.onDraw() вызывается перед завершением этого метода. В данном случае мы вызываем его после создания линий, которые мы хотим включить в компонент.

  4. Использование собственного компонента

    Теперь у нас есть собственный компонент, но как мы можем его использовать? В примере NotePad он используется напрямую, взгляните на note_editor.xml в папке res/layout.

    • Наш компонент создается в XML; название класса включает полное название пакета. Обратите внимание, что внутренний класс, который мы объявили использует нотацию NoteEditor$MyEditText, которая стандартна для обозначения внутренних классов в языке Java.

      Если ваш компонент объявлен не как внутренний класс, вы можете исключить атрибут class:

      Помните, что класс MyEditText помещен в отдельном файле. Если класс является вложенным, такой способ не сработает.

    • Прочие атрибуты и параметры в описании передаются в конструктор компонента и через него в конструктор супер-класса EditText. Обратите внимание, что вы можете добавить ваши собственные параметры.

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

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

Добавить комментарий