Как реализовать метод 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.onMeasure()?
На втором шаге, описанном в первом посте, нужно определить желаемые размеры 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().