Уверенный способ переопределить 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);
}
}