onMeasure() เรียกด้วย EXACTLY และขนาด spec 0

ขณะทำการดีบั๊กเมธอดมุมมองที่กำหนดเองแทนที่ onMeasure() ฉันพบว่ามีการเรียกเมธอดนี้หลายครั้ง
ฉันแค่จัดการกับความสูงของมุมมองเท่านั้น โดยปล่อยให้ข้อมูลจำเพาะความกว้างไม่เปลี่ยนแปลงเสมอ
เมื่อถึงจุดหนึ่ง ฉันได้รับสาย ด้วย (ความสูง) 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 กำลังทำสิ่งที่ต้องทำเมื่อวัดส่วนประกอบ UI
หากผู้ใช้ (ฉันในกรณีนี้) ไม่ปฏิบัติตามกฎง่ายๆ อาจเป็น 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