Пользовательский вид onMeasure: как получить ширину по высоте

Предыдущая версия моего вопроса была слишком многословной. Люди не могли понять это, поэтому следующее полное переписывание. Если вы заинтересованы в старой версии, просмотрите историю изменений.

Родитель

A RelativeLayout отправляет MeasureSpec s в свой метод просмотра child onMeasure, чтобы увидеть, насколько большой будет ребенок как быть. Это происходит в нескольких проходах.

Мое пользовательское представление

У меня есть пользовательский вид. По мере увеличения содержимого представления высота представления увеличивается. Когда представление достигает максимальной высоты, которую разрешает родитель, ширина представления увеличивается для любого дополнительного контента (если для ширины выбран wrap_content). Таким образом, ширина пользовательского представления напрямую зависит от того, какой родитель говорит, что максимальная высота должна быть.

enter image description here

Беспорядочный родительский разговор с родителями

onMeasure передать 1

Родитель RelativeLayout сообщает моему представлению: "Вы можете быть любой шириной до 900 и любой высоты до 600. Что вы скажете?"

В моем представлении говорится: "Ну, на этой высоте я могу поместить все с шириной 100. Поэтому я возьму ширину 100 и высоту 600."

onMeasure пройти 2

Родитель RelativeLayout указывает мое мнение: "В прошлый раз вы сказали мне, что хотите ширину 100, поэтому поставьте это как exact Теперь, исходя из этой ширины, какой высоты вы бы хотели? Anything до 500 все в порядке. "

"Эй!" мой взгляд отвечает. "Если вы только даете мне максимальную высоту 500, то 100 слишком узкий. Мне нужна ширина 200 для этой высоты. Но отлично, сделайте это по-своему. Я не сломаю rules (пока...). Я возьму ширину 100 и высоту 500."

Конечный результат

Родитель RelativeLayout присваивает виду конечный размер 100 для ширины и 500 для высоты. Это, конечно, слишком узко для просмотра, и часть содержимого обрезается.

"Вздох", - думает мой взгляд. "Почему мой родитель не позволит мне быть шире? Существует много места. Может быть, кто-то из Qaru может дать мне несколько советов".

Ответы

Ответ 1

Обновление: измененный код для исправления некоторых вещей.

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

Кажется, что с onMeasure происходит многое, что на первый взгляд не имеет большого смысла. Так как это так, мы будем запускать onMeasure так, как он будет, и в конце вывести оценку на View в onLayout, установив mStickyWidth на новую минимальную ширину, которую мы примем. В onPreDraw, используя ViewTreeObserver.OnPreDrawListener, мы создадим другой макет (requestLayout). Из документации (выделено мной):

boolean onPreDraw ()

Метод обратного вызова, который будет вызываться, когда дерево просмотра будет нарисовано. На этом этапе все представления в дереве были измерены и с учетом кадра. Клиенты могут использовать это, чтобы настроить свои полосы прокрутки или даже запросить новый макет перед тем, как сделать чертеж.

Новая минимальная ширина, установленная в onLayout, теперь будет исполнена onMeasure, которая теперь умнее о том, что возможно.

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

CustomView.java

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewTreeObserver;

public class CustomView extends View
        implements ViewTreeObserver.OnPreDrawListener {
    private int mStickyWidth = STICKY_WIDTH_UNDEFINED;

    public CustomView(Context context) {
        super(context);
    }

    public CustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        logMeasureSpecs(widthMeasureSpec, heightMeasureSpec);
        int desiredHeight = 10000; // some value that is too high for the screen
        int desiredWidth;

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int width;
        int height;

        // Height
        if (heightMode == MeasureSpec.EXACTLY) {
            height = heightSize;
        } else if (heightMode == MeasureSpec.AT_MOST) {
            height = Math.min(desiredHeight, heightSize);
        } else {
            height = desiredHeight;
        }

        // Width
        if (mStickyWidth != STICKY_WIDTH_UNDEFINED) {
            // This is the second time through layout and we are trying renogitiate a greater
            // width (mStickyWidth) without breaking the contract with the View.
            desiredWidth = mStickyWidth;
        } else if (height > BREAK_HEIGHT) { // a number between onMeasure two final height requirements
            desiredWidth = ARBITRARY_WIDTH_LESSER; // arbitrary number
        } else {
            desiredWidth = ARBITRARY_WIDTH_GREATER; // arbitrary number
        }

        if (widthMode == MeasureSpec.EXACTLY) {
            width = widthSize;
        } else if (widthMode == MeasureSpec.AT_MOST) {
            width = Math.min(desiredWidth, widthSize);
        } else {
            width = desiredWidth;
        }

        Log.d(TAG, "setMeasuredDimension(" + width + ", " + height + ")");
        setMeasuredDimension(width, height);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        int w = right - left;
        int h = bottom - top;

        super.onLayout(changed, left, top, right, bottom);
        // Here we need to determine if the width has been unnecessarily constrained.
        // We will try for a re-fit only once. If the sticky width is defined, we have
        // already tried to re-fit once, so we are not going to have another go at it since it
        // will (probably) have the same result.
        if (h <= BREAK_HEIGHT && (w < ARBITRARY_WIDTH_GREATER)
                && (mStickyWidth == STICKY_WIDTH_UNDEFINED)) {
            mStickyWidth = ARBITRARY_WIDTH_GREATER;
            getViewTreeObserver().addOnPreDrawListener(this);
        } else {
            mStickyWidth = STICKY_WIDTH_UNDEFINED;
        }
        Log.d(TAG, ">>>>onLayout: w=" + w + " h=" + h + " mStickyWidth=" + mStickyWidth);
    }

    @Override
    public boolean onPreDraw() {
        getViewTreeObserver().removeOnPreDrawListener(this);
        if (mStickyWidth == STICKY_WIDTH_UNDEFINED) { // Happy with the selected width.
            return true;
        }

        Log.d(TAG, ">>>>onPreDraw() requesting new layout");
        requestLayout();
        return false;
    }

    protected void logMeasureSpecs(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        String measureSpecHeight;
        String measureSpecWidth;

        if (heightMode == MeasureSpec.EXACTLY) {
            measureSpecHeight = "EXACTLY";
        } else if (heightMode == MeasureSpec.AT_MOST) {
            measureSpecHeight = "AT_MOST";
        } else {
            measureSpecHeight = "UNSPECIFIED";
        }

        if (widthMode == MeasureSpec.EXACTLY) {
            measureSpecWidth = "EXACTLY";
        } else if (widthMode == MeasureSpec.AT_MOST) {
            measureSpecWidth = "AT_MOST";
        } else {
            measureSpecWidth = "UNSPECIFIED";
        }

        Log.d(TAG, "Width: " + measureSpecWidth + ", " + widthSize + " Height: "
                + measureSpecHeight + ", " + heightSize);
    }

    private static final String TAG = "CustomView";
    private static final int STICKY_WIDTH_UNDEFINED = -1;
    private static final int BREAK_HEIGHT = 1950;
    private static final int ARBITRARY_WIDTH_LESSER = 200;
    private static final int ARBITRARY_WIDTH_GREATER = 800;
}

Ответ 2

Чтобы создать пользовательский макет, вам необходимо прочитать и понять эту статью https://developer.android.com/guide/topics/ui/how-android-draws.html

Нетрудно реализовать поведение, которое вы хотите. Вам просто нужно переопределить onMeasure и onLayout в своем пользовательском представлении.

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

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