onMeasure() вызывается с ТОЧНО и размером спецификации 0

Во время отладки пользовательского метода переопределения представления onMeasure() я вижу, что есть несколько вызовов этого метода.
Я имею дело только с высотой представления, оставляя спецификацию ширины всегда неизменной.
В какой-то момент я получаю вызов с (height) MeasureSpec getMode() == EXACTLY и getSize() == 0.
Это не имеет смысла и противоречит документации Android:

MeasureSpecs are used to push requirements down the tree from parent to child.
A MeasureSpec can be in one of three modes:

UNSPECIFIED: This is used by a parent to determine the desired dimension of a child
view. For example, a LinearLayout may call measure() on its child with the height set
to UNSPECIFIED and a width of EXACTLY 240 to find out how tall the child view wants
to be given a width of 240 pixels.

EXACTLY: This is used by the parent to impose an exact size on the child. The child
must use this size, and guarantee that all of its descendants will fit within this 
size.

AT_MOST: This is used by the parent to impose a maximum size on the child. The child
must guarantee that it and all of its descendants will fit within this size.

Если я делаю, как предполагалось (Дочерний элемент должен использовать этот размер), setMeasureDimension(specWidth, sepcHeight) я получаю исключение, сообщающее, что ширина и высота представления должны быть > 0.
Я подозреваю, что этот вызов вызван тем, что в макете XML представление имеет layout_weight="1" и, согласно документации предложения:

Чтобы создать линейный макет, в котором каждый дочерний элемент использует одинаковое количество места на экране, установите для android:layout_height каждого представления значение «0dp».

Но все же, когда режим MeasureSpec равен ТОЧНО, размер должен быть > 0. Или, по крайней мере, должно быть какое-то правило, которому нужно следовать в этих случаях, в документации.

это код:

    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int specHeight = MeasureSpec.getSize(heightMeasureSpec);
    int specWidth = MeasureSpec.getSize(widthMeasureSpec);

    int desiredHeight = Math.max(BOX_MIN_HEIGHT, HSVColorPickerPreference.this.boxHeight);

    int chosenHeight = 0;

    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    if( heightMode == MeasureSpec.UNSPECIFIED ) {
        chosenHeight = desiredHeight;
    } else if( heightMode == MeasureSpec.AT_MOST ) {
        chosenHeight = Math.min(specHeight, desiredHeight);
    } else if( heightMode == MeasureSpec.EXACTLY ) {
        chosenHeight = specHeight;
    }

    setMeasuredDimension(specWidth, chosenHeight);

Это журнал, обратите внимание на последние вызовы onMeasure():

03-29 08:17:13.388: D/ValueSlider(1384): + onMeasure(widthMeasureSpec:1073742403, heightMeasureSpec:-2147483435)
03-29 08:17:13.388: W/ValueSlider(1384): MeasureSpec AT_MOST, specSize=213, desiredSize=40, chosenSize=40
03-29 08:17:13.388: D/AlphaSlider(1384): + onMeasure(widthMeasureSpec:1073742403, heightMeasureSpec:-2147483435)
03-29 08:17:13.388: W/AlphaSlider(1384): MeasureSpec AT_MOST, specSize=213, desiredSize=40, chosenSize=40
03-29 08:17:13.388: D/ValueSlider(1384): + onMeasure(widthMeasureSpec:1073742403, heightMeasureSpec:1073741824)
03-29 08:17:13.388: W/ValueSlider(1384): MeasureSpec EXACTLY, specSize=0, desiredSize=40, chosenSize=0
03-29 08:17:13.388: D/AlphaSlider(1384): + onMeasure(widthMeasureSpec:1073742403, heightMeasureSpec:1073741824)
03-29 08:17:13.388: W/AlphaSlider(1384): MeasureSpec EXACTLY, specSize=0, desiredSize=40, chosenSize=0
03-29 08:17:13.508: D/ValueSlider(1384): + onSizeChanged(w:579, h:0, oldw:0, oldh:0)
03-29 08:17:13.508: D/AndroidRuntime(1384): Shutting down VM
03-29 08:17:13.508: W/dalvikvm(1384): threadid=1: thread exiting with uncaught exception (group=0xb2fe0180)
03-29 08:17:13.518: E/AndroidRuntime(1384): FATAL EXCEPTION: main
03-29 08:17:13.518: E/AndroidRuntime(1384): java.lang.IllegalArgumentException: width and height must be > 0
03-29 08:17:13.518: E/AndroidRuntime(1384):     at android.graphics.Bitmap.createBitmap(Bitmap.java:603)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at android.graphics.Bitmap.createBitmap(Bitmap.java:585)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at com.UturpatShuPepper.lib.HSVColorPickerPreference$Slider.onSizeChanged(HSVColorPickerPreference.java:962)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at android.view.View.setFrame(View.java:11361)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at android.view.View.layout(View.java:11272)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at android.widget.FrameLayout.onLayout(FrameLayout.java:431)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at android.view.View.layout(View.java:11278)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at android.view.ViewGroup.layout(ViewGroup.java:4224)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1628)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1486)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at android.widget.LinearLayout.onLayout(LinearLayout.java:1399)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at android.view.View.layout(View.java:11278)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at android.view.ViewGroup.layout(ViewGroup.java:4224)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at android.widget.RelativeLayout.onLayout(RelativeLayout.java:925)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at android.view.View.layout(View.java:11278)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at android.view.ViewGroup.layout(ViewGroup.java:4224)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at android.widget.FrameLayout.onLayout(FrameLayout.java:431)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at android.view.View.layout(View.java:11278)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at android.view.ViewGroup.layout(ViewGroup.java:4224)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at android.widget.FrameLayout.onLayout(FrameLayout.java:431)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at android.view.View.layout(View.java:11278)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at android.view.ViewGroup.layout(ViewGroup.java:4224)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1628)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1486)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at android.widget.LinearLayout.onLayout(LinearLayout.java:1399)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at android.view.View.layout(View.java:11278)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at android.view.ViewGroup.layout(ViewGroup.java:4224)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at android.widget.FrameLayout.onLayout(FrameLayout.java:431)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at android.view.View.layout(View.java:11278)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at android.view.ViewGroup.layout(ViewGroup.java:4224)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at android.widget.FrameLayout.onLayout(FrameLayout.java:431)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at android.view.View.layout(View.java:11278)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at android.view.ViewGroup.layout(ViewGroup.java:4224)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at android.widget.FrameLayout.onLayout(FrameLayout.java:431)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at android.view.View.layout(View.java:11278)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at android.view.ViewGroup.layout(ViewGroup.java:4224)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1489)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at android.view.ViewRootImpl.handleMessage(ViewRootImpl.java:2442)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at android.os.Handler.dispatchMessage(Handler.java:99)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at android.os.Looper.loop(Looper.java:137)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at android.app.ActivityThread.main(ActivityThread.java:4424)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at java.lang.reflect.Method.invokeNative(Native Method)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at java.lang.reflect.Method.invoke(Method.java:511)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
03-29 08:17:13.518: E/AndroidRuntime(1384):     at dalvik.system.NativeStart.main(Native Method)

person ilomambo    schedule 29.03.2014    source источник


Ответы (1)


Поскольку я наконец понял, что происходит, я оставляю здесь ответ для дальнейшего использования:

Android делает то, что должен делать при измерении компонентов пользовательского интерфейса.
Если пользователь (я в данном случае) не следует простым правилам, то может получиться ТОЧНО 0.
Это можно сделать безвредным, если вы просто проверьте размер 0 в методе onSizeChanged(). Но еще лучше, если вы избегаете смешивания режимов измерения, как это сделал я. Далее следует объяснение.

Я определил взвешенные представления XML (используя layout_weight). Это были пользовательские представления, упомянутые в вопросе. Моя ошибка заключалась в том, что я также пытался требовать определенной высоты для представлений в

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

виноваты линии

int desiredHeight = Math.max(BOX_MIN_HEIGHT, HSVColorPickerPreference.this.boxHeight);

. . . 

    chosenHeight = Math.min(specHeight, desiredHeight);

. . .

Это противоречит эвристике для взвешенного макета. Почему? Возьмем для примера 3 виджета с весом=1, и один из них ведет себя плохо, как описано выше.

Когда LineraLayout делает первый проход по своим дочерним элементам, он позволяет им сходить с ума и запрашивать любой размер, который они хотят. В нашем примере 2 виджета будут просить как можно больше, пользовательский виджет будет просить что-то скромное, меньше максимума.

Второй проход — убийца, LinearLayout не знает, что один из взвешенных виджетов запросил меньше, чем положено, в общем, для него определен вес. LinearLayout просматривает общую запрошенную меру формы первого прохода и видит, что она больше, чем должна дать. Затем он вычисляет дельта-переполнение и делает еще один проход, распределяя переполнение между взвешенными виджетами. Следовательно, виджет пользовательского представления должен сократить больше, чем он запросил, оставив его в размере 0.

Ситуация аналогична походу за пивом с друзьями. Вы заказываете одно пиво, а ваши друзья заказывают пиво, картофель фри, чейзерс и многое другое. В конце вечера чек делится между всеми поровну, в итоге вы платите больше, чем выпитое пиво. Как и мой пользовательский вид.

person ilomambo    schedule 31.03.2014