Авторский способ переопределить 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. Разметка происходит за второй проход после измерения.

satur9nine
источник
Мой опыт показывает, что если я переопределяю 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);достаточно вызова .

YON
источник
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

Aviral
источник
-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