Авторский способ переопределить onMeasure ()?


88

Какой правильный способ переопределить onMeasure ()? Я видел разные подходы. Например, профессиональная разработка для Android использует 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 (), макет, размеры дочерних представлений и т. Д.?


15
Вы нашли ответ полезным / правильным? почему бы не отметить его как в ответ?
superjos 01

Ответы:


68

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

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

  1. MeasureSpec.UNSPECIFIED - Ты можешь быть таким большим, как хочешь
  2. MeasureSpec.AT_MOST- Насколько вы хотите (до размера спецификации), это parentWidthв вашем примере.
  3. 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, int measureSpec, int childMeasuredState)

int resolveSize (int размер, int measureSpec)

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

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


39

Документация является авторитетом по этому вопросу: 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. Разметка происходит за второй проход после измерения.


Мой опыт показывает, что если я переопределяю onMeasure без вызова super.onMeasure, OnLayout не вызывается для дочерних представлений.
William Jockusch

2

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

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

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

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

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

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


1

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

@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


3
Это неправильное решение. super.onMeasureочистит размеры, установленные с помощью setMeasuredDimension.
tomrozb

@tomrozb, это не humptydevelopers.com/2013/05/… и вы можете проверить исходники Android
Юлий Рейри

@YuliReiri: Отличное решение!
Шаян Табатабаи

0

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


Вы по-прежнему можете вызывать setMinWidth () и setMaxWidth () и обрабатывать их стандартным onMeasure. Я обнаружил, что лучше не вызывать setLayoutParams изнутри настраиваемого класса View. Он действительно используется для ViewGroups или Activity, чтобы переопределить поведение дочернего представления.
Доррин

0

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

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


-1

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

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


-5

вы можете взять этот фрагмент кода в качестве примера 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);
    }
}

6
Этот код в корне неверен. Во-первых, он не учитывает режим макета (см. Ответ Гриммэйса). Это плохо, но вы не увидите эффекта от этого, потому что вы также вызываете super.onMeasure () в самом конце, который отменяет установленные вами значения (см. Ответ satur9nine) и вообще сводит на нет цель переопределения onMeasure ().
spaaarky21
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.