On measure android view

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 есть метод

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

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

Читайте также:  Easylink android auto wifi

Расчет размера можно разделить на 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)

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

Источник

Custom View: mastering onMeasure

Jan 23, 2017 · 4 min read

Custom views are not just about onDraw. onMeasure can be equally important and here’s how…

If you have ever built a custom view on Android before, you probably know that there i s often no need to override onMeasure, but it’s not a bad idea to do it anyway: the default implementation is not aware of how much space your view actually takes (since it takes into account only a combination of the layout params, usually specified in the XML, and the minWidth / minHeight of our view), hence you could end up with your view taking up way more space than it actually needs to.

Let’s get started

Since we’re going to handle the measurement of the view we don’t need the call to super.onMeasure : when we decided to override onMeasure it becomes our duty to call setMeasuredDimension(int width, int height) so we don’t need the default implementation of the View class. While implementing our onMeasure we should keep in mind:

  • The padding
  • The minimum width and minimum height of our view (use getSuggestedMinimumWidth() and getSuggestedMinimumHeight() to get those values)
  • The widthMeasureSpec and heightMeasureSpec which are the requirements passed to us by the parent

Note: getSuggestedMinimumWidth() differs from getMinimumWidth() in that it also accounts for the minimum width of the background drawable of the view, if it’s not null, and it returns the maximum of the two

So, the parameters we receive in our onMeasure , widthMeasureSpec and heightMeasureSpec, are compound bit shifted integer variables composed by a mode and a size (the structure was made this way to reduce objects allocation). We can make them explicit using:

Читайте также:  Minergate android как пользоваться

While the size is simply the number of pixel, the mode is a more abstract concept. There are three possible modes:

  • MeasureSpec.EXACTLY means our view should be exactly the size specified. This could happen when we use a fixed size (like android:layout_width=»64dp» ) or even match_parent (though the behaviour is the layout responsibility)
  • MeasureSpec.AT_MOST means that our view can be as big as it wants up to specified size. This could happen when we use wrap_content or also match_parent
  • MeasureSpec.UNSPECIFIED means that this view can take as much space as it wants. Sometimes this is used when the parent is trying to determine how big every child wants to be before calling measure again.

ResolveSize

So, until now we looked at the constraints provided by the parent. But how big do you want your view to be? You should take your time and ask yourself what does the size depend on. If you’re drawing text for example you could measure it (if you want to know more about it read this piece by Chris Banes). Once you obtained a desired width and height (don’t forget to take into account the padding), use the resolveSize(int size, int measureSpec) method which reconciles your demand with the parent constraint.

What resolveSize does is calling resolveSizeAndState and then deletes the optional bit (which can be MEASURED_STATE_TOO_SMALL ) from the result.

How does resolveSize / resolveSizeAndState pick the best option for you? It will return the size you calculated if mode is MeasureSpec.UNSPECIFIED , the size contained in measureSpec if MeasureSpec.EXACTLY , or the minimum between the two if MeasureSpec.AT_MOST .

If you think the resolveSize default behaviour does not suit you, you can always change it: the example method below behaves as resolveSize but also logs if the final result is smaller than the desiredSize.

As you can see, if specMode is equal to EXACTLY then we just set the specSize as result, else we use our minWidth summed with the padding. That’s basically the UNSPECIFIED case, but if size is AT_MOST we keep the minimum between this value and the specSize.

Remember that the onMeasure contract requires you to call the setMeasuredDimension(int width, int height) once you know how big your view should be. If you don’t, an IllegalStateException will be thrown.

So your onMeasure would look like this:

Additional tip

While debugging your custom view you can put this code at the beginning of your onMeasure :

Which will print something like this:

So you will always be aware of the constraint passed in from the parent and you’ll be able to see if your view is behaving accordingly.

Читайте также:  Почему не работает передача данных андроид

Источник

Как реализовать метод 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 .

Для правильной реализации метода 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() .

Источник

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