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().