onLayout(): ความหมาย, อัลกอริทึมการจัดวาง และพารามิเตอร์ของเมธอด

ผู้แต่ง: IT Sectr เผยแพร่เมื่อ: 2026-07-22 เวลาอ่าน: 9 นาที

onLayout() เป็นเมธอดของคลาส ViewGroup ที่กำหนดตำแหน่งและขนาดของ View ย่อยบนระนาบพิกัดของคอนเทนเนอร์หลัก ระบบ Android จะเรียก onLayout หลังจากขั้นตอนการวัด (onMeasure) เมื่อทราบความกว้างและความสูงที่วัดได้ของ View ย่อยแต่ละตัวแล้ว ตามเอกสาร Android Developers Documentation (2026) onLayout เป็นเมธอดที่จำเป็นต้องโอเวอร์ไรด์ใน ViewGroup ที่กำหนดเอง เนื่องจากการ implement มาตรฐานของ ViewGroup ไม่ได้ทำการจัดตำแหน่งอัตโนมัติขององค์ประกอบย่อย

ประเด็นสำคัญ

  • onLayout(boolean changed, int l, int t, int r, int b) — เมธอด ViewGroup ที่กำหนดตำแหน่งขององค์ประกอบย่อยในพิกัดหลัก
  • child.layout(l, t, r, b) — การเรียกสำหรับ View ย่อยแต่ละตัว กำหนดขอบเขตสุดท้าย
  • ขั้นตอน layout ตามหลังขั้นตอน measure และก่อนหน้าขั้นตอน draw ในวงจรชีวิตของ View
  • getWidth() และ getHeight() จะพร้อมใช้งานหลังจาก onLayout ทำงานเท่านั้น ต่างจาก getMeasuredWidth ที่พร้อมใช้งานหลังจาก onMeasure
  • requestLayout() — เมธอดที่เริ่มการเรียก onMeasure และ onLayout ซ้ำเมื่อข้อมูลที่ส่งผลต่อการจัดตำแหน่งเปลี่ยนแปลง

onLayout() คืออะไร?

onLayout(boolean changed, int l, int t, int r, int b) เป็นเมธอด protected ของคลาส ViewGroup ที่ระบบเรียกเพื่อจัดตำแหน่ง View ย่อยภายในคอนเทนเนอร์หลัก นักพัฒนาโอเวอร์ไรด์เมธอดนี้เมื่อสร้าง ViewGroup ที่กำหนดเองที่มีการจัดเรียงองค์ประกอบที่ไม่เป็นมาตรฐาน: แบบเรียงซ้อน, ตาราง, แบบกระดานหมากรุก หรือตามพิกัดที่กำหนดเอง View ย่อยแต่ละตัวจะได้รับขอบเขตสุดท้ายผ่านการเรียก child.layout()

พารามิเตอร์ changed บ่งชี้ว่าตำแหน่งหรือขนาดของ ViewGroup เองเปลี่ยนแปลงไปตั้งแต่ layout ครั้งล่าสุดหรือไม่ ถ้า changed เป็น true องค์ประกอบย่อยทั้งหมดอาจจำเป็นต้องจัดตำแหน่งใหม่ พารามิเตอร์ l, t, r, b คือพิกัดของมุมบนซ้ายและมุมล่างขวาของ ViewGroup ในระบบพิกัดของหลัก ภายใน onLayout นักพัฒนาใช้ค่าเหล่านี้เป็นพิกัดเริ่มต้นสำหรับการจัดเรียงองค์ประกอบย่อย

ViewGroup เป็นคลาสเดียวที่โอเวอร์ไรด์ onLayout View ทั่วไป (ที่ไม่ใช่ ViewGroup) ไม่มีองค์ประกอบย่อยและไม่จำเป็นต้องใช้ onLayout — การจัดตำแหน่งของมันถูกจัดการโดยคอนเทนเนอร์หลัก แม้ว่า View ทั่วไปจะโอเวอร์ไรด์ onLayout ระบบจะไม่เรียกมัน นี่คือความแตกต่างพื้นฐานจาก onMeasure ซึ่งถูกเรียกสำหรับ View ใดๆ

ขั้นตอนการทำงานของเฟส Layout

ขั้นตอน layout เริ่มต้นด้วยการเรียกเมธอดสาธารณะ layout(int l, int t, int r, int b) บน View ระดับราก เมธอดนี้กำหนดพิกัดสุดท้ายของ View เองและเรียก onLayout ถ้า View เป็น ViewGroup จากนั้น onLayout จะเรียก child.layout() แบบเวียนเกิดสำหรับองค์ประกอบย่อยแต่ละตัว และกระบวนการจะทำซ้ำลงไปตามลำดับชั้น ดังนั้น layout จึงแพร่กระจายจากรากไปยังใบ

ก่อนเรียก onLayout ระบบจะตรวจสอบว่าขนาดของ View เปลี่ยนแปลงไปเมื่อเทียบกับรอบก่อนหน้าหรือไม่ ถ้าขนาดไม่เปลี่ยนแปลงและไม่ได้เรียก requestLayout onLayout อาจไม่ถูกเรียก — ระบบใช้ผลลัพธ์ของ layout ก่อนหน้า นี่คือการปรับให้เหมาะสมที่ป้องกันการคำนวณตำแหน่งซ้ำโดยไม่จำเป็นระหว่างแอนิเมชันหรือการเลื่อน เมื่อเฉพาะเนื้อหาเปลี่ยนแปลงแต่ขนาดไม่เปลี่ยน

requestLayout() เป็นเมธอดของ View ที่แจ้งระบบว่า layout ของ View ล้าสมัยและจำเป็นต้องคำนวณใหม่ การเรียก requestLayout จะเริ่มรอบที่สมบูรณ์: ก่อนอื่น onMeasure ถูกเรียก จากนั้น onLayout จากนั้น onDraw ต่างจาก invalidate ที่เริ่มเพียงการวาดใหม่ requestLayout เริ่มการคำนวณขนาดและตำแหน่งใหม่ทั้งหมด การเรียกที่มากเกินไป ไปยัง requestLayout เป็นสาเหตุทั่วไปของปัญหาประสิทธิภาพ

พารามิเตอร์ onLayout: l, t, r, b

l (left) — พิกัด X ของขอบซ้ายของ ViewGroup ในระบบพิกัดหลัก t (top) — พิกัด Y ของขอบบน r (right) — พิกัด X ของขอบขวา b (bottom) — พิกัด Y ของขอบล่าง ความกว้างของ ViewGroup คำนวณเป็น r - l ความสูงเป็น b - t พิกัดเหล่านี้รวม padding ทั้งหมดของ ViewGroup เองแล้ว

ภายใน onLayout นักพัฒนาเรียก child.layout(int childLeft, int childTop, int childRight, int childBottom) สำหรับ View ย่อยแต่ละตัว พิกัดที่ส่งไปยัง child.layout ต้องอยู่ในระบบพิกัดของ ViewGroup หลัก โดยปกติ childLeft และ childTop จะคำนวณโดยพิจารณา padding ของหลัก: childLeft = l + paddingLeft + offsetX, childTop = t + paddingTop + offsetY

พารามิเตอร์คำอธิบายการใช้งานทั่วไป
l (left)พิกัดขอบซ้ายของ ViewGroup ในหลักจุดเริ่มต้นบนแกน X สำหรับองค์ประกอบย่อย
t (top)พิกัดขอบบนของ ViewGroup ในหลักจุดเริ่มต้นบนแกน Y สำหรับองค์ประกอบย่อย
r (right)พิกัดขอบขวาของ ViewGroup ในหลักขอบเขตบนของความกว้าง, r - l = getWidth()
b (bottom)พิกัดขอบล่างของ ViewGroup ในหลักขอบเขตบนของความสูง, b - t = getHeight()

การคำนวณพิกัดสำหรับองค์ประกอบย่อย

พิกัดย่อย คำนวณโดยสูตร: childLeft = l + paddingLeft + (marginLeft ถ้ามี), childRight = childLeft + child.getMeasuredWidth() ในทำนองเดียวกันสำหรับแกนตั้ง: childTop = t + paddingTop + (marginTop), childBottom = childTop + child.getMeasuredHeight() หลังจากคำนวณค่าทั้งสี่นี้แล้ว child.layout(childLeft, childTop, childRight, childBottom) จะถูกเรียก

ตัวอย่าง ViewGroup ที่กำหนดเองกับ onLayout ใน Kotlin

มาสร้าง FlowLayout — ViewGroup ที่กำหนดเองซึ่งจัดเรียง View ย่อยเป็นแถว ย้ายองค์ประกอบไปยังแถวใหม่เมื่อแถวปัจจุบันเต็ม นี่คือสิ่งที่เทียบเท่ากับ Flexbox แบบ wrap ในระนาบเดียว onLayout วนซ้ำผ่าน View ย่อยทั้งหมด คำนวณตำแหน่งสำหรับแต่ละตัว และเรียก child.layout() ด้วยขอบเขตที่ถูกต้อง

kotlin
class FlowLayout(context: Context)
    : ViewGroup(context) {

    private val horizontalSpacing = 12
    private val verticalSpacing = 8

    override fun onMeasure(widthMeasureSpec: Int,
                       heightMeasureSpec: Int) {
        val parentWidth =
            MeasureSpec.getSize(widthMeasureSpec)
        var rowX = paddingLeft
        var rowY = paddingTop
        var maxRowHeight = 0

        for (i in 0 until childCount) {
            val child = getChildAt(i)
            measureChildWithMargins(child,
                widthMeasureSpec, 0,
                heightMeasureSpec, 0)

            if (rowX + child.measuredWidth >
                parentWidth - paddingRight) {
                rowX = paddingLeft
                rowY += maxRowHeight + verticalSpacing
                maxRowHeight = 0
            }

            rowX += child.measuredWidth +
                horizontalSpacing
            maxRowHeight = maxOf(maxRowHeight,
                child.measuredHeight)
        }

        val totalHeight = rowY + maxRowHeight +
            paddingBottom
        setMeasuredDimension(
            resolveSize(parentWidth, widthMeasureSpec),
            resolveSize(totalHeight, heightMeasureSpec))
    }

    override fun onLayout(changed: Boolean,
                       l: Int, t: Int,
                       r: Int, b: Int) {
        val parentWidth = r - l
        var rowX = paddingLeft
        var rowY = paddingTop
        var maxRowHeight = 0

        for (i in 0 until childCount) {
            val child = getChildAt(i)
            val cw = child.measuredWidth
            val ch = child.measuredHeight

            if (rowX + cw >
                parentWidth - paddingRight) {
                rowX = paddingLeft
                rowY += maxRowHeight + verticalSpacing
                maxRowHeight = 0
            }

            child.layout(rowX, rowY,
                rowX + cw, rowY + ch)
            rowX += cw + horizontalSpacing
            maxRowHeight =
                maxOf(maxRowHeight, ch)
        }
    }

    override fun generateLayoutParams(attrs: AttributeSet?)
        : LayoutParams =
        MarginLayoutParams(context, attrs)
}

ความแตกต่างระหว่าง onLayout และ onMeasure

onMeasure และ onLayout เป็นสองขั้นตอนต่อเนื่องของวงจรชีวิต View ที่ทำงานแตกต่างกันโดยพื้นฐาน onMeasure กำหนดขนาดที่ต้องการ (ที่วัดได้) ของ View ในขณะที่ onLayout กำหนดพิกัดและขนาดจริง (สุดท้าย) ความแตกต่างสำคัญ: ใน onMeasure ขนาดอาจเป็นชั่วคราวและสามารถปรับเปลี่ยนได้ภายหลังโดยหลัก ในขณะที่ onLayout กำหนดตำแหน่งสุดท้ายของ View ย่อยแต่ละตัว

onMeasure ถูกเรียกสำหรับทุก View รวมถึง View ปลายทาง (TextView, ImageView, Button) onLayout ถูกเรียกสำหรับ ViewGroup เท่านั้น ทั้งนี้เพราะการจัดตำแหน่งเป็นความรับผิดชอบของคอนเทนเนอร์หลัก ไม่ใช่ของ View เอง View ปลายทางได้รับตำแหน่งผ่าน layout() ที่เรียกจาก onLayout ของหลัก

getMeasuredWidth() และ getMeasuredHeight() พร้อมใช้งานหลังจาก onMeasure ในขณะที่ getWidth() และ getHeight() พร้อมใช้งานหลังจาก onLayout เท่านั้น ถ้าคุณเข้าถึง getWidth() ภายใน onMeasure มันจะคืนค่าจากรอบก่อนหน้าหรือศูนย์ ดังนั้น สำหรับการคำนวณขนาดใน onMeasure คุณควรใช้ MeasureSpec และองค์ประกอบย่อยตามลำดับ

ข้อผิดพลาดทั่วไปเมื่อทำงานกับ onLayout

การจัดตำแหน่งโดยไม่คำนึงถึง padding — ข้อผิดพลาดแรกเมื่อ implement onLayout นักพัฒนามักลืมเพิ่ม paddingLeft และ paddingTop ของหลักไปยังพิกัดเริ่มต้นของ View ย่อย ผลลัพธ์คือองค์ประกอบย่อยแสดงที่ขอบของ ViewGroup โดยไม่สนใจ padding ที่กำหนดผ่าน setPadding() หรือในมาร์กอัป XML การคำนวณที่ถูกต้อง: childLeft = paddingLeft + offsetX

การเรียก layout สำหรับองค์ประกอบย่อยที่มองไม่เห็น — ปัญหาทั่วไปที่สอง ถ้า ViewGroup มี View ย่อยที่มีการมองเห็นเป็น GONE ไม่จำเป็นต้องจัดตำแหน่ง — มันไม่กินพื้นที่ อย่างไรก็ตาม onLayout ควรจัดการกรณีนี้อย่างถูกต้อง โดยข้ามองค์ประกอบย่อย GONE สำหรับองค์ประกอบย่อย INVISIBLE ยังคงต้องเรียก layout — มันคงพื้นที่ไว้แม้จะไม่แสดง

การละเลยพารามิเตอร์ changed — ข้อผิดพลาดที่สาม พารามิเตอร์ changed บ่งชี้ว่าขนาดหรือตำแหน่งของ ViewGroup เปลี่ยนแปลงหรือไม่ ถ้า changed == false สามารถใช้พิกัดที่แคชไว้ได้โดยไม่ต้องคำนวณ layout ขององค์ประกอบย่อยทั้งหมดใหม่ อย่างไรก็ตาม การแคช layout ทั้งหมดเป็นงานที่ซับซ้อน และในการ implement ส่วนใหญ่ onLayout เพียงคำนวณ องค์ประกอบทั้งหมด ใหม่ทุกครั้ง ซึ่งยอมรับได้เมื่อมีจำนวนองค์ประกอบย่อยน้อย

คำถามที่พบบ่อย

เป็นไปได้ไหมที่จะไม่โอเวอร์ไรด์ onLayout ใน ViewGroup?

ใช่ เป็นไปได้ ถ้า ViewGroup ใช้ LayoutParams มาตรฐานและไม่เพิ่มตรรกะการจัดตำแหน่งที่กำหนดเอง อย่างไรก็ตาม การ implement มาตรฐานของ onLayout ใน ViewGroup ไม่ดำเนินการใดๆ — องค์ประกอบย่อยจะไม่ถูกจัดตำแหน่ง ในทางปฏิบัติ ViewGroup ทั้งหมด (LinearLayout, RelativeLayout, FrameLayout) โอเวอร์ไรด์ onLayout

ความแตกต่างระหว่าง layout และ onLayout คืออะไร?

layout() เป็นเมธอด public final ของ View ที่ถูกเรียกโดยระบบหรือ ViewGroup หลัก มันกำหนดพิกัดของ View เองและเรียก onLayout ถ้า View เป็น ViewGroup onLayout() เป็นเมธอด protected ที่นักพัฒนาโอเวอร์ไรด์สำหรับการจัดเรียงองค์ประกอบย่อยที่กำหนดเอง

onLayout สามารถเรียก requestLayout ได้หรือไม่?

ในทางเทคนิค — ได้ แต่ไม่แนะนำอย่างยิ่ง เพราะนำไปสู่การเรียกซ้ำไม่สิ้นสุด: requestLayout → onMeasure → onLayout → requestLayout ถ้า requestLayout ถูกเรียกภายใน onLayout ระบบจะโยนข้อยกเว้น StackOverflowError การเปลี่ยนแปลงขนาดทั้งหมดควรดำเนินการก่อน onLayout

onLayout ทำงานกับแอนิเมชันอย่างไร?

แอนิเมชัน layout (LayoutTransition) สกัดกั้นการเปลี่ยนแปลงตำแหน่งของ View ย่อยและใช้แอนิเมชันการเปลี่ยนภาพ เมื่อเปิดใช้ LayoutTransition onLayout จะกำหนดตำแหน่งสุดท้ายก่อน จากนั้น LayoutTransition จะทำแอนิเมชันการเคลื่อนที่จากตำแหน่งเก่าไปยังตำแหน่งใหม่ ซึ่งต้องการการ implement onLayout ที่ถูกต้องด้วยพิกัดสุดท้ายที่เหมาะสม

ทำไม onLayout ถึงไม่ถูกเรียกหลังจาก invalidate?

invalidate() เริ่มเฉพาะขั้นตอน draw (การวาดใหม่) โดยไม่ส่งผลต่อ measure และ layout ในการเริ่ม onLayout คุณต้องเรียก requestLayout() ซึ่งเริ่มรอบที่สมบูรณ์: measure → layout → draw invalidate มีประสิทธิภาพมากกว่าสำหรับการอัปเดตลักษณะที่ปรากฏเมื่อขนาดและตำแหน่งไม่เปลี่ยนแปลง

สรุป

  • onLayout() — เมธอด ViewGroup ที่กำหนดตำแหน่งสุดท้ายของ View ย่อยหลังจากขั้นตอนการวัดเสร็จสมบูรณ์
  • child.layout(l, t, r, b) — กลไกหลักในการกำหนดพิกัดสำหรับองค์ประกอบย่อยแต่ละตัว
  • พารามิเตอร์ l, t, r, b — พิกัดขอบของ ViewGroup ในระบบหลัก, ความกว้าง = r - l, ความสูง = b - t
  • ขั้นตอน layout แพร่กระจายแบบเวียนเกิดจาก View ระดับรากไปยังองค์ประกอบย่อย เรียก onLayout บน ViewGroup แต่ละตัว
  • requestLayout() เริ่มรอบการคำนวณขนาดและตำแหน่งใหม่ทั้งหมด ต่างจาก invalidate ที่เริ่มเฉพาะการวาดใหม่
  • การคำนึงถึง padding ใน onLayout เป็นสิ่งจำเป็น — พิกัดเริ่มต้นขององค์ประกอบย่อยต้องรวม paddingLeft และ paddingTop ของหลัก

เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร

IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ

ปรึกษาโครงการ

อ่านเพิ่มเติม