объяснение пользовательского представления onMeasure

316

Я пытался сделать пользовательский компонент. Я расширил Viewкласс и делаю некоторые рисунки в onDrawпереопределенном методе. Почему мне нужно переопределить onMeasure? Если бы я этого не сделал, все было бы правильно. Может кто-нибудь объяснить это? Как мне написать свой onMeasureметод? Я видел пару уроков, но каждый немного отличается от другого. Иногда они звонят super.onMeasureв конце, иногда используют setMeasuredDimensionи не звонят. Где разница?

Ведь я хочу использовать несколько абсолютно одинаковых компонентов. Я добавил эти компоненты в свой XMLфайл, но не знаю, насколько они должны быть большими. Я хочу установить его положение и размер позже (зачем мне нужно устанавливать размер, onMeasureесли onDrawкогда я его рисую, тоже работает) в классе пользовательских компонентов. Когда именно мне нужно это сделать?

Сеннин
источник

Ответы:

735

onMeasure()это ваша возможность сообщить Android, насколько вы хотите, чтобы ваш пользовательский вид зависел от ограничений макета, предоставляемых родителем; это также возможность вашего пользовательского представления узнать, каковы эти ограничения макета (в случае, если вы хотите вести себя иначе в match_parentситуации, чем в wrap_contentситуации). Эти ограничения упакованы в MeasureSpecзначения, которые передаются в метод. Вот грубая корреляция значений режима:

  • Точно означает, что значение layout_widthили layout_heightбыло установлено на конкретное значение. Вы должны, вероятно, сделать ваш вид этого размера. Это также может быть вызвано при match_parentиспользовании, чтобы точно установить размер для родительского представления (это зависит от компоновки в платформе).
  • AT_MOST обычно означает, что значение layout_widthили layout_heightбыло установлено в match_parentилиwrap_content где требуется максимальный размер (это зависит от компоновки в платформе), а размер родительского измерения является значением. Вы не должны быть больше этого размера.
  • UNSPECIFIED обычно означает, что значение layout_widthor 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);
}

Надеюсь, это поможет.

Devunwired
источник
1
Привет @Devunwired хорошее объяснение лучшее, что я прочитал до сих пор. Ваше объяснение ответило на множество вопросов, которые у меня были, и сняло некоторые сомнения, но все же остается один: если мой пользовательский вид находится внутри ViewGroup вместе с некоторыми другими представлениями (неважно, какие типы), эта ViewGroup получит всех своих детей. для каждого исследования их ограничения LayoutParams и попросить каждого потомка самостоятельно измерить его в соответствии со своими ограничениями?
фараон
47
Обратите внимание, что этот код не будет работать, если вы переопределите onMeasure любого подкласса ViewGroup. Ваши подпредставления не будут отображаться и будут иметь размер 0x0. Если вам нужно переопределить onMeasure пользовательской ViewGroup, измените widthMode, widthSize, heightMode и heightSize, скомпилируйте их обратно в measureSpecs с помощью MeasureSpec.makeMeasureSpec и передайте полученные целые числа в super.onMeasure.
Алексей
1
Фантастический ответ. Обратите внимание, что в соответствии с документацией Google, View отвечает за заполнение.
Джонстафф
4
Из-за сложного c ** p, который делает Android сложной системой разметки для работы. Они могли бы просто получить getParent (). Get *** () ...
Оливер Диксон
2
В Viewклассе есть вспомогательные методы с именем resolveSizeAndStateand resolveSize, которые должны делать то же, что и предложения 'if' - я нашел их полезными, особенно если вам приходится часто писать эти IF.
17
5

на самом деле ваш ответ не является полным, так как значения также зависят от контейнера переноса. В случае относительного или линейного расположения значения ведут себя так:

  • ТОЧНО match_parent ТОЧНО + размер родителя
  • AT_MOST wrap_content приводит к AT_MOST MeasureSpec
  • НЕУТОЧНЕННЫЙ, никогда не срабатывал

В случае горизонтальной прокрутки ваш код будет работать.

Florian
источник
57
Если вы считаете, что какой-то ответ здесь неполный, добавьте к нему, а не дайте частичный ответ.
Михаэль
1
Хорошо, что он связывает это с тем, как работают макеты, но в моем случае onMeasure трижды вызывается для моего пользовательского представления. У рассматриваемого представления была высота wrap_content и взвешенная ширина (ширина = 0, вес = 1). Первый вызов был НЕУТОЧНЕННЫМ / НЕУТОЧНЕННЫМ, второй имел AT_MOST / ТОЧНО, а третий - ТОЧНО / ТОЧНО.
Уильям Т. Маллард
0

Если вам не нужно что-то менять на 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 по умолчанию () всегда будет устанавливать размер 100х100» - это неправильно.

Давид Рафаэли
источник