Объяснение пользовательского представления 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" - неверна.

Другие вопросы по тегам