Объяснение пользовательского представления onMeasure
Я пытался сделать пользовательский компонент. Я продлил View
класс и сделать некоторые рисунки в onDraw
переопределенный метод. Почему мне нужно переопределить onMeasure
? Если бы я этого не сделал, все было бы правильно. Может кто-нибудь объяснить это? Как я должен написать свой onMeasure
метод? Я видел пару уроков, но каждый немного отличается от другого. Иногда они называют super.onMeasure
в конце концов, иногда они используют setMeasuredDimension
и не звонил. Где разница?
Ведь я хочу использовать несколько абсолютно одинаковых компонентов. Я добавил эти компоненты в свой XML
файл, но я не знаю, насколько они должны быть большими. Я хочу установить его положение и размер позже (почему мне нужно установить размер в onMeasure
если в onDraw
когда я это рисую, тоже работает) в классе пользовательских компонентов. Когда именно мне нужно это сделать?
2 ответа
onMeasure()
это ваша возможность сообщить Android, насколько вы хотите, чтобы ваш пользовательский вид зависел от ограничений макета, предоставленных родителем; это также возможность вашего пользовательского представления узнать, каковы эти ограничения макета (если вы хотите вести себя по-другому в match_parent
ситуация, чем wrap_content
ситуация). Эти ограничения упакованы в MeasureSpec
значения, которые передаются в метод. Вот грубая корреляция значений режима:
- ТОЧНО означает
layout_width
или жеlayout_height
значение было установлено на конкретное значение. Вы, вероятно, должны сделать ваш вид этого размера. Это также может быть вызвано, когдаmatch_parent
используется, чтобы точно установить размер для родительского представления (это зависит от компоновки в платформе). - AT_MOST обычно означает
layout_width
или жеlayout_height
значение было установлено вmatch_parent
или жеwrap_content
где требуется максимальный размер (это зависит от макета в платформе), а размер родительского измерения является значением. Вы не должны быть больше этого размера. - НЕУТОЧНЕННЫЙ обычно означает
layout_width
или жеlayout_height
значение было установлено вwrap_content
без ограничений. Вы можете быть любого размера, который вам нравится. Некоторые макеты также используют этот обратный вызов, чтобы определить желаемый размер, прежде чем определить, какие спецификации фактически передают вас снова во втором запросе измерения.
Контракт, который существует с onMeasure()
в том, что setMeasuredDimension()
ДОЛЖЕН быть вызван в конце с размером, который вы хотели бы видеть. Этот метод вызывается всеми реализациями платформы, включая реализацию по умолчанию, найденную в View
поэтому звонить можно super
вместо этого, если это соответствует вашему варианту использования.
Конечно, поскольку платформа действительно применяет реализацию по умолчанию, вам может не потребоваться переопределять этот метод, но вы можете увидеть отсечение в тех случаях, когда пространство просмотра меньше вашего контента, если вы этого не сделаете, и если вы выложите свой пользовательский вид с wrap_content
в обоих направлениях ваше представление может вообще не отображаться, потому что фреймворк не знает, насколько он большой!
Как правило, если вы переопределяете View
а не другой существующий виджет, это, вероятно, хорошая идея, чтобы обеспечить реализацию, даже если это так просто, как это:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int desiredWidth = 100;
int desiredHeight = 100;
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height;
//Measure Width
if (widthMode == MeasureSpec.EXACTLY) {
//Must be this size
width = widthSize;
} else if (widthMode == MeasureSpec.AT_MOST) {
//Can't be bigger than...
width = Math.min(desiredWidth, widthSize);
} else {
//Be whatever you want
width = desiredWidth;
}
//Measure Height
if (heightMode == MeasureSpec.EXACTLY) {
//Must be this size
height = heightSize;
} else if (heightMode == MeasureSpec.AT_MOST) {
//Can't be bigger than...
height = Math.min(desiredHeight, heightSize);
} else {
//Be whatever you want
height = desiredHeight;
}
//MUST CALL THIS
setMeasuredDimension(width, height);
}
Надеюсь, это поможет.
На самом деле ваш ответ не является полным, так как значения также зависят от контейнера переноса. В случае относительного или линейного расположения значения ведут себя так:
- Ровно match_parent - это РОВНО + размер родителя
- AT_MOST wrap_content приводит к AT_MOST MeasureSpec
- НЕУТОЧНЕННЫЙ, никогда не срабатывал
В случае горизонтальной прокрутки ваш код будет работать.
Если вам не нужно что-то менять на Memeasure - вам совершенно не нужно переопределять это.
Devunwired код (выбранный и получивший наибольшее количество ответов ответ здесь) практически идентичен тому, что реализация SDK уже делает для вас (и я проверил - это было сделано с 2009 года).
Вы можете проверить метод onMeasure здесь:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
Переопределение кода SDK для замены на тот же самый код не имеет смысла.
Часть этого официального документа, в которой утверждается, что "onMeasure() по умолчанию всегда будет устанавливать размер 100x100" - неверна.