Onmeasure android что это

Android курс в Технополисе 2019

В этом уроке мы научимся создавать собственные View .

Custom View

Обычно термин Custom View обозначает View , которого нет в sdk Android. Или другими словами — это View которое мы сделали сами.

Когда может понадобиться реализация собственного View :

  • специфичная отрисовка;
  • специфичная обработка жестов;
  • оптимизация существующих элементов;
  • правка багов в существующем элементе.

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

Для начала, давайте вспомним о том, как выглядит иерархия базовых компонентов:

Все ui компоненты наследуются от View , а лейауты от ViewGroup . В свою очередь ViewGroup наследуется от View .

Прежде чем наследоваться от базового класса View посмотрите, может быть вам ближе функциональность уже какого-то существующего элемента. Например Button , это не написанный с нуля компонент, а наследник TextView .

Жизненный цикл View

Первостепенно давайте разберемся с жизненным циклом View .

Constructor

Каждый элемент начинает свое существование с конструктора. У View их целых четыре штуки:

Создание View из кода:

Создание View из XML:

Создание View из XML со стилем из темы:

Создание View из XML со стилем из темы и/или с ресурсом стиля:

Последний конструктор добавлен в sdk версии 21. Каждый из конструктор каскадно вызывает следующий.

onAttachedToWindow

После того как родитель View вызовет метод addView(View) , наш View будет прикреплён к окну. На этой стадии наш View-компонент попадает в иерархию родителя.

onMeasure

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

Давайте посмотрим на сам метод:

Метод onMeasure() принимает 2 аргумента: widthMeasureSpec и heightMeasureSpec . Это значения, которые содержат в себе информацию о том, каким размером хочет видеть ваше View родительский элемент.

Каждое из значений на самом деле содержит 2 параметра:

  • mode . Указывает на то, какие правила применяются ко второму параметру size;
  • size . Непосредственно размер View .

Получить эти параметры можно при помощи методов класса MeasureSpec :

mode может принимать следующие значения:

  • MeasureSpec.EXACTLY . Означает, что размер задан жёстко. Независимо от размера вашего View , вы должны установить определённую ширину и высоту;
  • MeasureSpec.AT_MOST . Означает что View может быть любого размера, которого пожелает, но, не больше чем размер родителя. Это значение match_parent ;
  • MeasureSpec.UNSPECIFIED . Означение что View может само решить какой размер ему нужен не взирая ни на какие ограничения. Это значение wrap_content .

В коде это можно описать следующим образом:

где wrapWidth , это наша желаемая ширина. Аналогичный подход применяется и к высоте View .

Конечно же не нужно каждый раз писать эту конструкцию из условий. Для упрощения работы у View есть метод

Читайте также:  Не могу отправить письмо по электронной почте с андроида

который уже включает в себя все необходимые условия.

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

Расчет размера можно разделить на 4 стадии:

  1. Родитель узнает “пожелания”, каким размером View хочет быть, определение LayoutParams наследника. Это может быть сделано как через xml, так и кодом:

  1. Родитель начинает измерять свои дочерние View и просит рассчитать их размеры.

  1. Дочерняя View рассчитывает свои размеры и устанавливает значение.

  1. Родитель сообщает о том, что расчет закончен и можно получить финальные значения.

onLayout

Этот метод позволяет присваивать позицию и размер дочерним элементам ViewGroup . В случае, если мы наследовались от View , нам не нужно переопределять этот метод.

onDraw

Это основной метод при разработки собственной View . В onDraw вы можете рисовать все что вам нужно. Метод имеет следующую сигнатуру:

На полученном Canvas вам требуется непосредственно изобразить саму View . Рисование на Canvas происходит при мощи объекта Paint . Paint отвечает за то, как именно будет отрисован контент вашего View и имеет множество параметров.

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

Обновление View

Из диаграммы жизненного цикла видно, что существуют два метода, которые заставляют View перерисовываться:

invalidate() . Используется когда нужно только перерисовать ваш элемент. Когда изменился цвет или текст или нужно сделать какие-то еще визуальные изменения;

requestLayout() . Используется когда нужно изменить размеры вашего View . Вызов requestLayout не только заставит View заново измериться, но и перерисует элемент.

Иерархия

Вызовы всех методов View проходят от базового View к потомкам, сверху вниз.

Во время расчета размера View потомок принимает “пожелания” от родителя, рассчитывает свои размеры, а также размеры своих потомков. (Measure pass)

После того как размеры известны, родитель проставляет размеры и расположение своим потомкам. (Layout pass)

Последним этапом является отрисовка. Она также происходит от родителя к потомку

Источник

Как реализовать метод View.onMeasure()?

onMeasure() задает размеры view и является важной частью контракта между view и layout. onMeasure() вызывается после вызова метода measure(). Обычно это делает layout для всех дочерних view, чтобы определить их размеры.
В некоторых случаях при реализации кастомной view требуется переопределить этот метод.

Для правильной реализации метода onMeasure() нужно сделать следующее:
1. onMeasure() вызывается с аргументами widthMeasureSpec и heightMeasureSpec . Это целочисленные значения в которых закодирована информация о предпочтениях layout к размерам view. На этом шаге вам нужно декодировать measure spec и получить значение размера и режим, определяющий как этот размер применять.
В следующем посте разберем measure spec подробнее.

2. Вычислить ширину и высоту view. При вычислении размеров необходимо учитывать значения паддингов и measure spec. Если вычисленный размер превышает measure spec, то layout выбирает что делать. Layout может обрезать view, добавить скроллинг, бросить исключение или вызвать onMeasure() еще раз с новыми значениями measure specs.

3. После вычисления ширины и высоты необходимо вызвать метод setMeasuredDimension(width: Int, height: Int). Если не вызвать этот метод, то будет брошен IllegalStateException .

Читайте также:  Как разблокировать андроид самсунг м21

Для правильной реализации метода onMeasure() необходимо учитывать значения параметров widthMeasureSpec и heightMeasureSpec , с которыми вызван onMeasure() .
Эти параметры имеют тип int , и представляют собой целые числа, в которых с помощью битового сдвига закодировано два значения: размер в пикселях и режим (mode) применения этого размера. Для значений measure specs используется тип int , а не специальный класс, чтобы сэкономить память, используемую при отрисовки UI.

Для получения значений размера и режима используются статические методы MeasureSpec.getSize(measureSpec: Int) и MeasureSpec.getMode(measureSpec: Int) соответственно.

Режим может иметь одно из трех значений:

UNSPECIFIED – у родителя нет предпочтений к размеру view, размер может быть произвольным. Иногда это значение используется лэйаутом при первом проходе для определения желаемых размеров каждой из view. После чего measure() вызывается еще раз, но уже с другим режимом.

EXACTLY – родитель определил и передал точный размер view. View будет иметь этот размер независимо от того, какого размера view хочет быть.

AT_MOST – родитель определил и передал верхнюю границу размера view. View может быть любого размера в пределах этой границы.

На втором шаге, описанном в первом посте, нужно определить желаемые размеры view. Тут нет универсального решения. Размер зависит от целей view и от того, что нужно отобразить. При определении размера не забывайте учитывать заданные паддинги. Паддинги получают методами getPadding. () .

После определения желаемого размера нужно сопоставить его с требуемыми measure specs. Для этого удобно использовать статический метод resolveSize(size: Int, measureSpec: Int). resolveSize() принимает параметрами желаемый размер и measure spec и возвращает новое значение размера. Если значение measuare spec EXACTLY , то resolveSize() возвращает размер, переданный в measure spec. Если AT_MOST , то возвращается минимальное значение из желаемого размера и размера measure spec. Если UNSPECIFIED , то resolveSize() возвращает желаемый размер.

На скриншоте реализация onMeasure() с использованием resolveSize() . calculateHeight() и calculateWidth() – это ваши методы, которые считают желаемые высоту и ширину.

Статья с более подробной информацией и примерами реализации onMeasure() .

Источник

Android компонент с нуля

Задание:

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

Пользователь зажимает стрелку и переводит её в право, по мере отвода, стрелка вытягивает цветные квадратики:

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

Подготовка

Для создания нового компонента создадим новый проект. Далее создаём новый класс с именем «CustomButton», в качестве предка используем класс «View». Далее создадим конструктор класса и в итоге наш будущий компонент будет иметь вид:

Теперь приступаем к написанию кода класса. Прежде чем начать писать код, скиньте в папку /res/drawable-hdpi, изображение разноцветной ленты. В конструкторе нужно перво наперво инициализировать все объекты и сделать все предварительные настройки. Делаем следующее:
1 — Копируем ссылку на контекст основной активности;
2 — Загружаем подготовленную заготовку-полоску разделённую цветными квадратиками;
3 — Настраиваем компонент необходимый для рисования на поверхности/

Читайте также:  Apple carplay для андроида

Также объявим объекты в начале класса:

Теперь нам необходимо переопределить процедуру настройки размеров компонента — onMeasure. Я специально сделал постоянные размеры для компонента (300*50) чтобы не усложнять пример. Процедура будет иметь вид:

Теперь переопределим процедуру перерисовки компонента «onDraw». Данная процедура вызывается каждый раз когда необходимо перерисовать компонент. Процедура будет иметь вид:

Заготовка для нашего нового компонента готова, давайте поместим её на главную активность. Во первых разместим на главной поверхности новый LinearLayout, под именем «LinearLayout1». Далее в конструкторе класса создадим класс для новой кнопки, создадим класс реализации«LinearLayout1» и добавим кнопку на поверхность. Класс активности будет иметь вид:

Если вы запустите проект на выполнение то на устройстве (эмуляторе) вы увидите примерно следующее:

Функционал

Теперь приступим к реализации анимации и реакции на внешние события. Когда пользователь нажимает на компонент интерфейса, предком которого является View, то автоматически генерируются события, в частности можно отследить координаты нажатия на компонент, и этапы нажатия (нажали, подвигали, отжали). Поэтому требуется переопределить процедуру onTouchEvent, отвечающую за внешние события. Процедура имеет один аргумент «MotionEvent event», он содержит в себе все параметры текущего события. Извлекаем эти параметры следующим образом:

Приводим процедуру к следующему виду:

Каждую строчку расписывать не буду, определю только главную идею. Пользователь нажимает на стрелку компонента, это действие фиксируется в переменной _Last_Action = 1, также фиксируем что пользователь не вытянул ни одного кубика из ленты — _X = 0. Далее отслеживаем перемещение пальца по компоненту и вычисляем сколько кубиков должно показаться на экране, для этого вычисляем _X. Принудительная перерисовка происходит с помощью команды invalidate(). В конце фиксируем отжатие пальца и запускаем таймер, если пользователь вытянул хотя бы один кубик. Таймер необходим чтобы возвращать полоску в исходное состояние не резко, а постепенно.

Теперь реализуем сам таймер, который будет возвращать полоску в исходное положение. Код таймера будет иметь вид:

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

Теперь осталось изменить процедуру перерисовки виджета, а именно строку позиционирования ленты на поверхности (ширина одного квадратика на ленте, равна 60 pix, а общая длинна составляет 300 pix):

Добавим все переменные в начало реализации класса.
В итоге класс будет меть вид:

Внешние сообщения

Сильно мудрить не будем, реализуем событие что «лента спрятана» с помощью широковещательных сообщений. В реализации таймера добавим строки отправки сообщений:

В переменной «Name» хранится имя нашего компонента. Для сохранения имени, создадим дополнительную процедуру:

Добавим в блок объявления объектов имя компонента — public String Name.
Теперь в конструкторе нашей активности добавим перехватчик широковещательных сообщений:

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

Источник

Оцените статью