คำอธิบายมุมมองที่กำหนดเอง onMeasure


316

ฉันพยายามทำส่วนประกอบที่กำหนดเอง ฉันขยายViewชั้นเรียนและวาดภาพบางส่วนด้วยonDrawวิธีการเขียนทับ ทำไมฉันถึงต้องลบล้างonMeasure? ถ้าฉันทำไม่ได้ทุกอย่างจะถูกต้อง มีคนอธิบายได้ไหม ฉันควรเขียนonMeasureวิธีการของฉันอย่างไร ฉันเคยเห็นแบบฝึกหัดคู่ แต่แต่ละอันมีความแตกต่างกันเล็กน้อย บางครั้งพวกเขาก็โทรหาsuper.onMeasureท้ายบางครั้งก็ใช้setMeasuredDimensionและไม่ได้โทรมา ความแตกต่างอยู่ที่ไหน

หลังจากทั้งหมดฉันต้องการใช้องค์ประกอบเดียวกันหลายอย่างแน่นอน ฉันเพิ่มส่วนประกอบเหล่านี้ลงในXMLไฟล์ของฉันแต่ฉันไม่ทราบว่าควรจะมีขนาดใหญ่แค่ไหน ฉันต้องการกำหนดตำแหน่งและขนาดในภายหลัง (ทำไมฉันต้องกำหนดขนาดด้วยonMeasureถ้าonDrawเมื่อฉันวาดมันทำงานได้ดี) ในคลาสคอมโพเนนต์ที่กำหนดเอง เมื่อไหร่ที่ฉันต้องทำอย่างนั้น?

คำตอบ:


735

onMeasure()เป็นโอกาสของคุณที่จะบอก Android ว่าคุณต้องการให้มุมมองที่กำหนดเองของคุณใหญ่เพียงใดขึ้นอยู่กับข้อ จำกัด ของเลย์เอาต์ที่ผู้ปกครองจัดหาให้ นอกจากนี้ยังเป็นโอกาสในมุมมองที่กำหนดเองของคุณเพื่อเรียนรู้ว่าข้อ จำกัด ของเลย์เอาต์เหล่านั้นคืออะไร (ในกรณีที่คุณต้องการทำงานในmatch_parentสถานการณ์ที่แตกต่างจากwrap_contentสถานการณ์) ข้อ จำกัด เหล่านี้ถูกรวมเป็นMeasureSpecค่าที่ส่งผ่านไปยังเมธอด นี่คือความสัมพันธ์คร่าวๆของค่าโหมด:

  • แน่นอนหมายความว่าค่าlayout_widthหรือlayout_heightถูกตั้งค่าเป็นค่าที่เฉพาะเจาะจง คุณควรทำให้ขนาดของคุณดู สิ่งนี้ยังสามารถถูกทริกเกอร์เมื่อmatch_parentใช้เพื่อตั้งขนาดให้ตรงกับมุมมองพาเรนต์ (นี่คือโครงร่างที่ขึ้นอยู่กับกรอบงาน)
  • AT_MOSTมักจะหมายถึงlayout_widthหรือlayout_heightค่าถูกกำหนดให้match_parentหรือwrap_contentที่มีขนาดสูงสุดเป็นสิ่งจำเป็น (นี้จะขึ้นอยู่ในกรอบรูปแบบ) และขนาดของมิติที่ผู้ปกครองเป็นค่า คุณไม่ควรใหญ่กว่าขนาดนี้
  • โดยทั่วไปUNSPECIFIEDหมายถึงการตั้งค่าlayout_widthหรือlayout_heightค่าเป็นwrap_contentไม่มีข้อ จำกัด คุณสามารถเป็นอะไรก็ได้ที่คุณต้องการ บางเลย์เอาต์ใช้การเรียกกลับนี้เพื่อหาขนาดที่คุณต้องการก่อนกำหนดรายละเอียดที่จะส่งให้คุณอีกครั้งในคำขอวัดครั้งที่สอง

สัญญาที่มีอยู่onMeasure()คือsetMeasuredDimension() ต้องเรียกท้ายที่สุดด้วยขนาดที่คุณต้องการให้เป็นมุมมอง เมธอดนี้ถูกเรียกใช้โดยการประยุกต์ใช้เฟรมเวิร์กทั้งหมดรวมถึงการใช้งานเริ่มต้นที่พบViewซึ่งเป็นเหตุผลว่าทำไมจึงปลอดภัยที่จะโทรออกsuperหากเหมาะสมกับกรณีการใช้งานของคุณ

ได้รับเนื่องจากเฟรมเวิร์กใช้การใช้งานเริ่มต้นจึงไม่จำเป็นสำหรับคุณที่จะแทนที่วิธีนี้ แต่คุณอาจเห็นการตัดในกรณีที่พื้นที่ดูมีขนาดเล็กกว่าเนื้อหาของคุณถ้าคุณไม่ทำและถ้าคุณวางโครงร่างของคุณ มุมมองที่กำหนดเองด้วยwrap_contentทั้งสองทิศทางมุมมองของคุณอาจไม่ปรากฏเลยเพราะกรอบงานไม่ทราบว่ามีขนาดใหญ่เพียงใด!

โดยทั่วไปหากคุณกำลังเอาชนะViewและไม่ใช่วิดเจ็ตอื่นที่มีอยู่มันอาจเป็นความคิดที่ดีที่จะให้การใช้งานแม้ว่ามันจะง่ายเหมือนอย่างนี้:

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

    int desiredWidth = 100;
    int desiredHeight = 100;

    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);

    int width;
    int height;

    //Measure Width
    if (widthMode == MeasureSpec.EXACTLY) {
        //Must be this size
        width = widthSize;
    } else if (widthMode == MeasureSpec.AT_MOST) {
        //Can't be bigger than...
        width = Math.min(desiredWidth, widthSize);
    } else {
        //Be whatever you want
        width = desiredWidth;
    }

    //Measure Height
    if (heightMode == MeasureSpec.EXACTLY) {
        //Must be this size
        height = heightSize;
    } else if (heightMode == MeasureSpec.AT_MOST) {
        //Can't be bigger than...
        height = Math.min(desiredHeight, heightSize);
    } else {
        //Be whatever you want
        height = desiredHeight;
    }

    //MUST CALL THIS
    setMeasuredDimension(width, height);
}

หวังว่าจะช่วย


1
เฮ้ @Devunwired คำอธิบายที่ดีที่สุดที่ฉันอ่านมา คำอธิบายของคุณตอบคำถามมากมายที่ฉันมีและเคลียร์ข้อสงสัยบางอย่าง แต่ยังคงมีอยู่ซึ่ง: ถ้ามุมมองที่กำหนดเองของฉันอยู่ใน ViewGroup พร้อมกับมุมมองอื่น ๆ (ไม่สำคัญว่าประเภทใด) ที่ ViewGroup จะให้ลูก ๆ สำหรับแต่ละโพรบสำหรับข้อ จำกัด LayoutParams ของพวกเขาและขอให้เด็กแต่ละคนวัดมันเองตามข้อ จำกัด ของพวกเขา?
ฟาโรห์

47
โปรดทราบว่ารหัสนี้จะไม่ทำถ้าคุณแทนที่ onMeasure ของคลาสย่อย ViewGroup ใด ๆ บทสัมภาษณ์ของคุณจะไม่ปรากฏขึ้นและทั้งหมดจะมีขนาด 0x0 หากคุณต้องการแทนที่ onMeasure ของ ViewGroup ที่กำหนดเองให้เปลี่ยน widthMode, widthSize, heightMode และ heightSize แล้วคอมไพล์พวกเขากลับไปที่ measureSpec โดยใช้ MeasureSpec.makeMeasureSpec และส่งจำนวนเต็มไปที่ super.onMeasure
Alexey

1
คำตอบที่ยอดเยี่ยม โปรดทราบว่าตามเอกสารของ Google มันเป็นความรับผิดชอบของ View ในการจัดการการขยาย
jonstaff

4
ซับซ้อนกว่า c ** p ที่ทำให้ Android เป็นระบบรูปแบบที่เจ็บปวดในการทำงาน พวกเขาอาจเพิ่งได้รับ getParent (). รับ *** () ...
Oliver Dixon

2
มีวิธีการช่วยเหลือในViewชั้นเรียนที่เรียกว่าresolveSizeAndStateและresolveSizeที่ควรทำสิ่งที่ 'ถ้า' คำสั่งทำ - ฉันพบว่าพวกเขามีประโยชน์โดยเฉพาะอย่างยิ่งถ้าคุณต้องเขียน IFs เหล่านั้นบ่อยครั้ง
stan0

5

จริงๆแล้วคำตอบของคุณยังไม่สมบูรณ์เนื่องจากค่าก็ขึ้นอยู่กับภาชนะที่ห่อด้วย ในกรณีที่มีความสัมพันธ์หรือเค้าโครงเชิงเส้นค่าทำงานเช่นนี้:

  • ตรงทั้งหมด match_parent คือขนาด + แน่นอนของผู้ปกครอง
  • AT_MOST wrap_content ให้ผลลัพธ์เป็น AT_MOST MeasureSpec
  • UNSPECIFIEDไม่เคยถูกเรียก

ในกรณีที่มีการเลื่อนดูในแนวนอนรหัสของคุณจะใช้งานได้


57
หากคุณรู้สึกว่าคำตอบบางอย่างที่นี่ไม่สมบูรณ์โปรดเพิ่มคำตอบแทนที่จะให้คำตอบบางส่วน
Michaël

1
ดี onya สำหรับการเชื่อมโยงกับการทำงานของเลย์เอาต์ แต่ในกรณีของฉัน onMeasure เรียกว่าสามครั้งสำหรับมุมมองที่กำหนดเองของฉัน มุมมองในคำถามมีความสูง wrap_content และความกว้างถ่วงน้ำหนัก (กว้าง = 0, น้ำหนัก = 1) การโทรครั้งแรกไม่ได้รับการอนุมัติ / ไม่ได้รับการยืนยันครั้งที่สองมี AT_MOST / แน่นอนและที่สามมีแน่นอน / แน่นอน
William T. Mallard

0

หากคุณไม่จำเป็นต้องเปลี่ยนบางสิ่งบางอย่างในวัด - ไม่จำเป็นต้องให้คุณแทนที่มัน

รหัส Devunwired (คำตอบที่เลือกและโหวตมากที่สุดที่นี่) เกือบจะเหมือนกับสิ่งที่การใช้งาน SDK สำหรับคุณแล้ว (และฉันตรวจสอบแล้ว - มันทำมาตั้งแต่ปี 2009)

คุณสามารถตรวจสอบวิธีการ onMeasure ได้ที่นี่ :

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);

    switch (specMode) {
    case MeasureSpec.UNSPECIFIED:
        result = size;
        break;
    case MeasureSpec.AT_MOST:
    case MeasureSpec.EXACTLY:
        result = specSize;
        break;
    }
    return result;
}

การเอาชนะรหัส SDK ที่จะถูกแทนที่ด้วยรหัสเดียวกันแน่นอนไม่สมเหตุสมผล

เอกสารของทางการฉบับนี้ที่อ้างว่า "ค่าเริ่มต้น onMeasure () จะตั้งค่าขนาด 100x100 เสมอ" - ผิด

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.