OnMeasure(): wrap_content, как узнать размер обертывания?

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

По умолчанию базовый класс меры измеряет размер фона, если только MeasureSpec не разрешен размер большего размера. Подклассы должны переопределять onMeasure (int, int), чтобы обеспечить лучшие измерения их содержимого.

Хорошо, так что я знаю, что мне нужно переопределить onMeasure() и работать с MeasureSpec. В соответствии с этим ответом

UNSPECIFIED означает, что для параметра layout_width или layout_height установлено значение wrap_content. Вы можете быть любым размером, который вы хотели бы.

Теперь я перехожу к своей проблеме, как я могу onMeasure() измерить растровое изображение, которое еще не создано, и измерить/обернуть его? Я знаю, что другие Android-представления ДОЛЖНЫ что-то делать, потому что они не блокируют весь экран, если он установлен в wrap_content. Спасибо заранее!

Ответ 1

Если вы не можете измерить растровое изображение до вызова onMeasure, вы можете вернуть нулевой размер до тех пор, пока битмап не будет загружен. Как только он будет загружен, недействительность родительской ViewGroup, чтобы принудительно выполнить другую меру (не могу вспомнить, если invalidate() в самом представлении заставит onMeasure).

Ответ 2

Это порядок, в котором используются эти обычно используемые методы просмотра:

1. Constructor    // choose your desired size 
2. onMeasure      // parent will determine if your desired size is acceptable
3. onSizeChanged
4. onLayout       
5. onDraw         // draw your view content at the size specified by the parent

Выбор желаемого размера

Если ваш взгляд может быть любого размера, который он хотел, какой размер он выберет? Это будет ваш размер wrap_content и будет зависеть от содержимого вашего пользовательского представления. Примеры:

  • Если ваш пользовательский вид является изображением, то ваш желаемый размер, вероятно, будет размером пикселя растрового изображения плюс любое дополнение. (Вы несете ответственность за то, чтобы подбирать размер в ваших расчетах при выборе размера и рисовании содержимого.)
  • Если пользовательский вид является аналоговым часом, то желаемый размер может быть некоторым размером по умолчанию, который будет хорошо смотреться. (Вы всегда можете получить dp до px размер для устройства.)

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

Согласование конечного размера

onMeasure - это место, где ребенок сообщает родителям, насколько он хотел бы быть, и родитель решает, допустимо ли это. Этот метод часто вызывается несколько раз, каждый раз проходящий в разных требованиях размера, видя, можно ли достигнуть какого-либо компромисса. В конце концов, однако, ребенок должен уважать требования к размеру родителя.

Я всегда возвращаюсь к этому ответу, когда мне нужно переосмыслить, как настроить мой onMeasure:

@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);
}

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

Использование выбранного размера

После onMeasure размер вашего представления известен. Этот размер может быть или не быть тем, что вы просили, но с этим вы должны работать. Используйте этот размер, чтобы нарисовать содержимое в вашем представлении в onDraw.

Примечания

  • В любое время, когда вы делаете изменения в своем представлении, которые влияют на внешний вид, но не на размер, вызовите invalidate(). Это приведет к вызову onDraw снова (но не ко всем другим предыдущим методам).
  • В любое время, когда вы внесете изменения в свое представление, которое повлияет на размер, затем вызовите requestLayout(). Это запустит процесс измерения и рисования снова с onMeasure. Обычно это в сочетании с вызовом invalidate().
  • Если по какой-то причине вы действительно не можете заранее определить подходящий желаемый размер, то я полагаю, что вы можете сделать, как предлагает @nmw, и просто запросить нулевую ширину, нулевую высоту. Затем запросите макет (а не только invalidate()) после того, как все будет загружено. Это похоже на немного отходов, потому что вам требуется, чтобы вся иерархия представлений была выложена дважды подряд.