OnMeasure пользовательский вид пояснения

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

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

Ответ 1

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() MUST с размером, которым вы хотели бы видеть. Этот метод вызывается всеми реализациями фреймворка, включая реализацию по умолчанию, найденную в 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);
}

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

Ответ 2

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

  • ТОЧНО match_parent является ТОЧНО + размер родительского
  • AT_MOST wrap_content приводит к методу AT_MOST MeasureSpec
  • НЕСООТВЕТСТВУЕТ никогда не запускается

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

Ответ 3

Если вам не нужно что-то менять в измерении - вам совершенно не нужно переопределять это.

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 для замены на тот же самый код не имеет смысла.