Уверенный способ переопределить onMeasure()?

Каков правильный способ переопределения onMeasure()? Я видел разные подходы. Например, Professional Android Development использует MeasureSpec для расчета размеров, а затем заканчивается вызовом setMeasuredDimension(). Например:

@Override 
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
this.setMeasuredDimension(parentWidth/2, parentHeight);
}

С другой стороны, согласно этому сообщению, "правильным" способом является использование MeasureSpec, вызов setMeasuredDimensions(), , за которым следует вызов setLayoutParams() и завершение вызовом super.onMeasure(). Например:

@Override 
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
this.setMeasuredDimension(parentWidth/2, parentHeight);
this.setLayoutParams(new *ParentLayoutType*.LayoutParams(parentWidth/2,parentHeight));
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

Итак, что это правильный путь? Ни один из подходов не работал у меня на 100%.

Я думаю, действительно, что я спрашиваю, знает ли кто-нибудь из учебника, объясняющего onMeasure(), макет, размеры детских представлений и т.д.?

Ответ 1

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

При вызове onMeasure вы можете или не иметь права изменять размер. Значения, переданные вашему onMeasure (widthMeasureSpec, heightMeasureSpec), содержат информацию о разрешении вашего дочернего представления. В настоящее время существует три значения:

  • MeasureSpec.UNSPECIFIED - вы можете быть как можно больше
  • MeasureSpec.AT_MOST - как можно больше (до размера спецификации), это parentWidth в вашем примере.
  • MeasureSpec.EXACTLY - Нет выбора. Родитель выбрал.

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

Если вы не соблюдаете эти правила, ваш подход не гарантирует работу.

Например, если вы хотите проверить, разрешено ли вообще изменить размер, вы можете сделать следующее:

final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
boolean resizeWidth = widthSpecMode != MeasureSpec.EXACTLY;
boolean resizeHeight = heightSpecMode != MeasureSpec.EXACTLY;

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

int resolveSizeAndState (int size, int measureSpec, int childMeasuredState)

int resolveSize (int size, int measureSpec)

В то время как первое доступно только для Honeycomb, второе доступно для всех версий.

Примечание. Вы можете обнаружить, что resizeWidth или resizeHeight всегда ложны. Я обнаружил, что это так, если я запрашиваю MATCH_PARENT. Я смог исправить это, запросив WRAP_CONTENT на моем родительском макете, а затем во время этапа UNSPECIFIED с запросом размера Integer.MAX_VALUE. Это дает максимальный размер, который позволяет ваш родитель на следующем прохождении через onMeasure.

Ответ 2

Документация является авторитетом по этому вопросу: http://developer.android.com/guide/topics/ui/how-android-draws.html и http://developer.android.com/guide/topics/ui/custom-components.html

Подводя итог: в конце вашего переопределенного метода onMeasure вы должны вызвать setMeasuredDimension.

Не следует вызывать super.onMeasure после вызова setMeasuredDimension, который просто удалит все, что вы установили. В некоторых ситуациях вы можете сначала вызвать super.onMeasure, а затем изменить результаты, вызвав setMeasuredDimension.

Не звоните setLayoutParams в onMeasure. Макет происходит во втором проходе после измерения.

Ответ 3

Если изменить размер просмотров внутри onMeasure, вам нужен только вызов setMeasuredDimension. Если вы изменяете размер за пределами onMeasure, вам нужно позвонить setLayoutParams. Например, изменение размера текстового вида при изменении текста.

Ответ 4

Зависит от используемого элемента управления. Инструкции в документации работают для некоторых элементов управления (TextView, Button,...), но не для других (LinearLayout,...). Способ, который работал очень хорошо для меня, состоял в том, чтобы позвонить супер, как только я закончу. На основе статьи в приведенной ниже ссылке.

http://humptydevelopers.blogspot.in/2013/05/android-view-overriding-onmeasure.html

Ответ 5

вот как я решил проблему:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        ....

        setMeasuredDimension( measuredWidth, measuredHeight );

        widthMeasureSpec = MeasureSpec.makeMeasureSpec( measuredWidth, MeasureSpec.EXACTLY );
        heightMeasureSpec = MeasureSpec.makeMeasureSpec( measuredHeight, MeasureSpec.EXACTLY);

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

}

Кроме того, это необходимо для компонента ViewPager

Ответ 6

Я думаю, что это зависит от родителя, что вы переопределяете.

Например, если вы расширяете ViewGroup (например, FrameLayout), когда вы измеряете размер, вы должны позвонить, как показано ниже

super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));

потому что вы можете захотеть, чтобы ViewGroup выполнял работу по отдыху (делайте некоторые вещи на детском представлении)

Если вы расширяете представление (например, ImageView), вы можете просто вызвать this.setMeasuredDimension(width, height);, потому что родительский класс просто сделает что-то вроде обычного.

Одним словом, если вам нужны некоторые функции, предлагаемые вашим родительским классом, вы должны вызвать super.onMeasure() (обычно пропустите параметр измерения MeasureSpec.EXACTLY mode), в противном случае достаточно вызвать this.setMeasuredDimension(width, height);.

Ответ 7

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

Однако, это редко работает правильно (по какой-либо причине...), лучше вызывать measureChildren (при получении ViewGroup) или попробовать что-то подобное при необходимости.

Ответ 8

вы можете взять этот кусок кода в качестве примера onMeasure()::

public class MyLayerLayout extends RelativeLayout {

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

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
        int parentHeight = MeasureSpec.getSize(heightMeasureSpec);

        int currentChildCount = getChildCount();
        for (int i = 0; i < currentChildCount; i++) {
            View currentChild = getChildAt(i);

            //code to find information

            int widthPercent = currentChildInfo.getWidth();
            int heightPercent = currentChildInfo.getHeight();

//considering we will pass height & width as percentage

            int myWidth = (int) Math.round(parentWidth * (widthPercent / 100.0));
            int myHeight = (int) Math.round(parentHeight * (heightPercent / 100.0));

//Considering we need to set horizontal & vertical position of the view in parent

            AlignmentTraitValue vAlign = currentChildInfo.getVerticalLocation() != null ? currentChildlayerInfo.getVerticalLocation() : currentChildAlignmentTraitValue.TOP;
            AlignmentTraitValue hAlign = currentChildInfo.getHorizontalLocation() != null ? currentChildlayerInfo.getHorizontalLocation() : currentChildAlignmentTraitValue.LEFT;
            int topPadding = 0;
            int leftPadding = 0;

            if (vAlign.equals(currentChildAlignmentTraitValue.CENTER)) {
                topPadding = (parentHeight - myHeight) / 2;
            } else if (vAlign.equals(currentChildAlignmentTraitValue.BOTTOM)) {
                topPadding = parentHeight - myHeight;
            }

            if (hAlign.equals(currentChildAlignmentTraitValue.CENTER)) {
                leftPadding = (parentWidth - myWidth) / 2;
            } else if (hAlign.equals(currentChildAlignmentTraitValue.RIGHT)) {
                leftPadding = parentWidth - myWidth;
            }
            LayoutParams myLayoutParams = new LayoutParams(myWidth, myHeight);
            currentChildLayoutParams.setMargins(leftPadding, topPadding, 0, 0);
            currentChild.setLayoutParams(myLayoutParams);
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}