มาทำความเข้าใจว่า ConstraintLayout คืออะไร — ระบบการวางตำแหน่งที่ยืดหยุ่นสำหรับ Android ที่ช่วยให้สร้างลำดับชั้นวิวแบบราบโดยใช้ข้อจำกัด (constraints) แทน LinearLayout และ RelativeLayout ที่ซ้อนกัน ConstraintLayout แก้ปัญหา "การซ้อนเลย์เอาต์ที่ยุ่งยาก" (layout nesting hell) โดยลดความลึกของลำดับชั้นเหลือเพียงระดับเดียวและเร่งการเรนเดอร์หน้าจอ ไลบรารีเป็นส่วนหนึ่งของ Jetpack และสามารถใช้งานได้ตั้งแต่ Android 2.3 (API 9) ผ่าน support-library กลไกหลักได้อธิบายไว้ใน เอกสารทางการของ Android
ประเด็นสำคัญ
ConstraintLayout คือ ViewGroup จากไลบรารี AndroidX ConstraintLayout ที่ออกแบบมาเพื่อสร้างอินเทอร์เฟซที่ยืดหยุ่นและมีประสิทธิภาพผ่านข้อจำกัดแบบประกาศ แตกต่างจาก LinearLayout ที่จัดเรียงองค์ประกอบเป็นเส้นเดียว หรือ RelativeLayout ที่วางตำแหน่งองค์ประกอบสัมพันธ์กับเพื่อนบ้าน ConstraintLayout ช่วยให้ยึดแต่ละองค์ประกอบสัมพันธ์กับองค์ประกอบอื่นใดและพาเรนต์พร้อมกัน
ไลบรารีถูกประกาศที่ Google I/O 2016 เพื่อเป็นโซลูชันสำหรับเร่งการเรนเดอร์หน้าจอที่ซับซ้อน ปัญหาหลักที่ ConstraintLayout แก้ไขคือการซ้อนเลย์เอาต์ ViewGroup ที่ซ้อนกันแต่ละตัวเพิ่มอย่างน้อยสอง measure pass และหนึ่ง layout pass หน้าจอที่มี 4 ระดับการซ้อนทำงาน 8 measure pass; ConstraintLayout ที่มีความสามารถเหมือนกันทำงานเพียง 2 pass ตามข้อมูลของ Google (Android Performance Blog, 2017) การแทนที่ LinearLayout ที่ซ้อนกันสามตัวด้วย ConstraintLayout หนึ่งตัวช่วยลดเวลา onMeasure ได้ 40%
เวอร์ชันปัจจุบัน ConstraintLayout 2.1.4 ทำงานได้อย่างเสถียรบน Android 2.3+ (API 9) ผ่าน AndroidX เวอร์ชัน 2.0 นำเสนอการวางตำแหน่งแบบวงกลม Flow (การตัดบรรทัดองค์ประกอบอัตโนมัติ) และการสนับสนุน MotionLayout ConstraintLayout จำเป็นต่อการเข้าใจการพัฒนา Android สมัยใหม่ — มันถูกใช้ใน Jetpack Compose เป็นแนวคิดพื้นฐานของตัวปรับแต่ง ในเทมเพลตเริ่มต้นของ Android Studio และใน Material Design 3
ลำดับชั้นแบบราบของ ConstraintLayout หมายถึงวิวลูกทั้งหมดอยู่ในระดับการซ้อนเดียวกัน แทนที่จะวางองค์ประกอบ A ใน LinearLayout และ LinearLayout ใน RelativeLayout องค์ประกอบทั้งหมดจะถูกผูกโดยตรงกับ ConstraintLayout พาเรนต์หรือซึ่งกันและกันผ่านแอตทริบิวต์ สิ่งนี้ให้: การใช้หน่วยความจำน้อยลง (ViewGroup แต่ละตัวเป็นออบเจกต์ใน Java heap), layout pass ที่เร็วขึ้น (การเรียกซ้ำน้อยลง) และพฤติกรรมที่คาดเดาได้มากขึ้นเมื่อขนาดหน้าจอเปลี่ยนแปลง
ข้อจำกัด (constraint) คือการเชื่อมต่อระหว่างขอบของวิวหนึ่ง (หรือศูนย์กลาง) กับขอบของวิวอื่นหรือพาเรนต์ แต่ละวิวสามารถมีข้อจำกัดได้สูงสุด 8 รายการ: left, top, right, bottom, start, end, baseline และ center อย่างน้อยที่สุด ข้อจำกัดตั้งฉากสองรายการก็เพียงพอสำหรับการวางตำแหน่ง (เช่น top + left)
รูปแบบแอตทริบิวต์: app:layout_constraint[Source]_to[Target]Of="[id]" — โดยที่ Source คือขอบที่ผูก (Left, Right, Top, Bottom, Start, End, Baseline) และ Target คือขอบเป้าหมาย ตัวอย่าง: app:layout_constraintTop_toBottomOf="@+id/header" หมายถึง "ขอบบนขององค์ประกอบปัจจุบันผูกกับขอบล่างขององค์ประกอบ header" การผูกกับพาเรนต์ใช้ id parent
bias (ความเอนเอียง) คือพารามิเตอร์ที่ทำงานเมื่อมีข้อจำกัดตรงข้าม (left + right หรือ top + bottom) ค่าตั้งแต่ 0 ถึง 1: 0 — ไปทางขอบซ้าย/บน, 0.5 — กึ่งกลาง, 1 — ไปทางขอบขวา/ล่าง แอตทริบิวต์: layout_constraintHorizontal_bias (0.0–1.0) และ layout_constraintVertical_bias ระยะขอบถูกกำหนดด้วย android:layout_margin* มาตรฐาน แต่ข้อจำกัดและระยะขอบทำงานอย่างอิสระ: ระยะขอบคือออฟเซตจากข้อจำกัด ไม่ใช่จากวิวข้างเคียง
ตั้งแต่ ConstraintLayout 1.1+ การสนับสนุนขนาดตามเปอร์เซ็นต์ถูกเพิ่มผ่าน layout_constraintWidth_percent และ layout_constraintHeight_percent ค่า 0.3 หมายถึง 30% ของความกว้าง/สูงของพาเรนต์ เมื่อรวมกับ bias สิ่งนี้ช่วยให้สร้างเลย์เอาต์ที่ปรับตัวได้โดยไม่ต้องเขียนโปรแกรม
Chain (ห่วงโซ่) คือกลุ่มของวิวสองตัวขึ้นไปที่เชื่อมต่อด้วยข้อจำกัดแบบสองทิศทาง (A ผูกกับ B, B ผูกกับ A) ห่วงโซ่กระจายพื้นที่ระหว่างองค์ประกอบโดยอัตโนมัติตามโหมดใดโหมดหนึ่ง: spread (เท่าเทียม โดยคำนึงถึงระยะขอบ), spread_inside (เท่าเทียม องค์ประกอบภายนอกไม่มีระยะขอบขอบ), packed (องค์ประกอบประกบกันด้วย bias ร่วม) โหมดถูกกำหนดผ่านแอตทริบิวต์ app:layout_constraintHorizontal_chainStyle หรือ layout_constraintVertical_chainStyle
Guideline (เส้นชี้นำ) คือวิวเสริมที่มองไม่เห็นในขณะทำงาน ซึ่งกำหนดเส้นสำหรับการผูก Guideline สามารถเป็นแนวนอนหรือแนวตั้ง วางตำแหน่งใน dp, เปอร์เซ็นต์ (app:layout_constraintGuide_percent) หรือด้วยออฟเซตจากขอบ (app:layout_constraintGuide_begin/end) เส้นชี้นำเป็นสิ่งที่ขาดไม่ได้สำหรับเลย์เอาต์ที่ปรับตัวได้ — ตัวอย่างเช่น การแบ่งหน้าจอออกเป็นสองส่วนเท่า ๆ กันโดยไม่ขึ้นกับขนาดอุปกรณ์
ตามข้อมูลของ Google I/O 2017 ห่วงโซ่กับ spread_inside มีประสิทธิภาพมากกว่า LinearLayout ที่ซ้อนกันกับน้ำหนัก 15–20% เนื่องจากหลีกเลี่ยง measure pass สองเท่าที่จำเป็นสำหรับการคำนวณน้ำหนัก
Barrier (สิ่งกีดขวาง) คือวิวเสมือนที่ปรับตำแหน่งของมันแบบไดนามิกตามขนาดของกลุ่มองค์ประกอบ แตกต่างจาก Guideline ที่มีตำแหน่งคงที่ Barrier จะถูก "ผลัก" โดยองค์ประกอบที่กว้างที่สุดในกลุ่ม ตัวอย่างเช่น หากคุณมีหัวเรื่องและคำอธิบายที่มีความยาวไม่ทราบค่า Barrier ที่ผูกกับขอบขวาของข้อความที่กว้างที่สุดช่วยให้คุณวางไอคอนได้ทันทีหลังจากนั้น แอตทริบิวต์: app:barrierDirection (left, right, top, bottom, start, end) และ app:constraint_referenced_ids (รายการ id คั่นด้วยจุลภาค)
Group คือคอนเทนเนอร์เสมือนที่จัดการการมองเห็นของหลายวิวพร้อมกัน แทนที่จะเรียก setVisibility สำหรับแต่ละองค์ประกอบแยกกัน ก็เพียงแค่เปลี่ยนการมองเห็นของ Group เดียว Group ไม่ส่งผลต่อการวางตำแหน่ง — เฉพาะการมองเห็นเท่านั้น Flow คือตัวช่วยเสมือนสำหรับสร้างเลย์เอาต์ "ที่ไหล": องค์ประกอบจะถูกย้ายไปยังแถว/คอลัมน์ใหม่โดยอัตโนมัติเมื่อพื้นที่ไม่เพียงพอ เช่น ข้อความในย่อหน้า Flow รองรับ wrapMode: none, chain และ aligned
เครื่องมือเหล่านี้ (Barrier, Group, Flow, Guideline) ถูกเรียกว่าตัวช่วยเสมือนเพราะมันไม่ใช่วิวในความหมายดั้งเดิม — มันไม่ได้ใช้พื้นที่ในลำดับชั้นและไม่มีส่วนร่วมในเหตุการณ์โฟกัสหรือสัมผัส จุดประสงค์ของมันคือทำให้การบำรุงรักษาเลย์เอาต์ที่ซับซ้อนง่ายขึ้นโดยไม่ต้องเพิ่มคอนเทนเนอร์ที่ซ้อนกัน
ฟอร์มเข้าสู่ระบบอย่างง่ายพร้อมช่องอีเมล ช่องรหัสผ่าน และปุ่ม องค์ประกอบทั้งหมดผูกกับพาเรนต์ ยกเว้นปุ่ม — มันอยู่ใต้ช่องรหัสผ่าน มีการใช้ ลำดับชั้นแบบราบ — ทั้งสามองค์ประกอบอยู่ในระดับเดียวกัน
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/email_input"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="32dp"
android:layout_marginHorizontal="16dp" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/password_input"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/email_input"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="16dp"
android:layout_marginHorizontal="16dp" />
<Button
android:id="@+id/login_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/password_input"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="24dp"
android:layout_marginHorizontal="16dp"
android:text="เข้าสู่ระบบ" />
</androidx.constraintlayout.widget.ConstraintLayout>
องค์ประกอบทั้งหมดมีความกว้าง 0dp (match_constraint) หมายถึงมันยืดจากข้อจำกัด start ถึง end โดยคำนึงถึงระยะขอบแนวนอน ซึ่งเทียบเท่ากับ match_parent ที่มีระยะขอบ แต่ไม่มีการซ้อน
ปุ่มสามปุ่มกระจายอย่างเท่าเทียมในแนวนอนพร้อมระยะขอบขอบ ห่วงโซ่ spread_inside วางปุ่มด้านนอกที่ขอบและปุ่มกลางอยู่กึ่งกลางระหว่างพวกมัน
<Button
android:id="@+id/btn_left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/btn_center"
android:text="ซ้าย" />
<Button
android:id="@+id/btn_center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toRightOf="@+id/btn_left"
app:layout_constraintRight_toLeftOf="@+id/btn_right"
android:text="กลาง" />
<Button
android:id="@+id/btn_right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toRightOf="@+id/btn_center"
app:layout_constraintRight_toRightOf="parent"
android:text="ขวา" />
ห่วงโซ่ถูกสร้างขึ้นโดยอัตโนมัติเมื่อองค์ประกอบมีข้อจำกัดแบบสองทิศทาง โหมด spread_inside ถูกตั้งค่าบนองค์ประกอบใดก็ได้ของห่วงโซ่ผ่าน app:layout_constraintHorizontal_chainStyle="spread_inside" สิ่งนี้ขจัดความจำเป็นในการใช้ LinearLayout กับ weightSum และ layout_weight
การสร้างสองคอลัมน์เท่ากันโดยใช้ Guideline แนวตั้งที่ 50% องค์ประกอบด้านซ้ายผูกกับพาเรนต์ซ้ายและขอบขวาของมันกับ guideline องค์ประกอบด้านขวาผูกกับขอบซ้ายของมันกับ guideline และกับพาเรนต์ขวา
<androidx.constraintlayout.widget.Guideline
android:id="@+id/gl_midpoint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5" />
<TextView
android:id="@+id/left_card"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/gl_midpoint"
android:layout_margin="8dp"
android:background="@color/card_background" />
<TextView
android:id="@+id/right_card"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@+id/gl_midpoint"
app:layout_constraintRight_toRightOf="parent"
android:layout_margin="8dp"
android:background="@color/card_background" />
Guideline ที่มีเปอร์เซ็นต์ 0.5 ปรับตามความกว้างหน้าจอโดยอัตโนมัติ ทั้งบนแท็บเล็ตและโทรศัพท์ อัตราส่วนคอลัมน์ยังคงอยู่ที่ 50/50 สำหรับการตั้งชื่อซ้าย/ขวา ให้ใช้แอตทริบิวต์ start/end สำหรับความเข้ากันได้กับ RTL
ตารางเปรียบเทียบ ViewGroup หลักสามตัวสำหรับการพัฒนา Android: ConstraintLayout, LinearLayout และ RelativeLayout เกณฑ์: ความยืดหยุ่น, ประสิทธิภาพ, ความซับซ้อนของโค้ด และกรณีการใช้งาน
| คุณลักษณะ | ConstraintLayout | LinearLayout | RelativeLayout |
|---|---|---|---|
| การซ้อน | แบบราบ (ระดับเดียว) | ต้องใช้การซ้อนสำหรับเลย์เอาต์ที่ซับซ้อน | ระดับเดียว แต่ความยืดหยุ่นจำกัด |
| ประสิทธิภาพ measure | 2 pass (~40% เร็วกว่า) | 4+ pass เมื่อใช้น้ำหนัก | 2 pass |
| ขนาดเปอร์เซ็นต์ | ใช่ (guide_percent, width_percent) | ผ่าน weight/frame เท่านั้น | ไม่ |
| การสนับสนุน RTL | ในตัว (start/end) | ในตัว | ผ่าน start/end (API 17+) |
| Barrier/Group/Flow | ใช่ (ตัวช่วยเสมือน) | ไม่ | ไม่ |
| แอนิเมชัน MotionLayout | ใช่ | ไม่ | ไม่ |
| เมื่อใดควรใช้ | เลย์เอาต์ซับซ้อนทั้งหมด, หน้าจอที่มี >5 องค์ประกอบ | รายการทิศทางเดียวธรรมดา, แถวที่มีปุ่ม | เลย์เอาต์สัมพัทธ์ธรรมดา (โค้ดเก่า) |
ตามข้อมูลของ Android Vitals (Google, 2025) แอปที่ใช้ ConstraintLayout เป็นคอนเทนเนอร์หลักแสดงเฟรม jank โดยเฉลี่ย 18% น้อยกว่าเมื่อเรนเดอร์หน้าจอที่ซับซ้อนเมื่อเทียบกับแอปที่ใช้ LinearLayout ที่ซ้อนกัน ที่ IT Sectr เราเปลี่ยนไปใช้ ConstraintLayout เป็นมาตรฐานสำหรับเลย์เอาต์ XML ทั้งหมดในปี 2018 — ซึ่งลดความลึกเฉลี่ยของลำดับชั้นหน้าจอจาก 4.2 เป็น 1.8 ระดับและเร่งการพัฒนาฟอร์มใหม่ขึ้น 25%
คำถามที่พบบ่อย
match_parent ใน ConstraintLayout ทำงานตามปกติ — มันยืดวิวไปจนถึงขนาดของพาเรนต์ 0dp (match_constraint) หมายถึงขนาดของวิวถูกคำนวณจากข้อจำกัด: หากตั้งค่าข้อจำกัด left และ right ด้วยระยะขอบ ความกว้าง = parent — marginLeft — marginRight ความแตกต่างในพฤติกรรม: match_parent ไม่สนใจ bias และอาจล้นขอบระหว่างแอนิเมชัน; match_constraint เคารพข้อจำกัดทั้งหมดอย่างถูกต้องและ Google แนะนำให้ใช้เป็นโหมดหลักสำหรับ ConstraintLayout
ใช้การผสมผสาน: ขนาดเปอร์เซ็นต์ (layout_constraintWidth_percent) สำหรับองค์ประกอบที่ควรกินพื้นที่ส่วนหนึ่งของหน้าจอ; Guideline พร้อมเปอร์เซ็นต์ สำหรับแบ่งหน้าจอเป็นโซน; Barrier สำหรับการวางตำแหน่งสัมพันธ์กับเนื้อหาแบบไดนามิก; Flow กับ wrapMode สำหรับตัดบรรทัดการ์ดไปยังแถวใหม่ วิธีการทางเลือกคือใช้ SlidingPaneLayout ร่วมกับ ConstraintLayout สำหรับอินเทอร์เฟซแบบ master-detail บนแท็บเล็ต
Jetpack Compose ไม่ใช้ ConstraintLayout เป็น ViewGroup แต่มี เวอร์ชัน Compose ของ ConstraintLayout (androidx.constraintlayout:constraintlayout-compose) ที่มี API เดียวกันใน Kotlin DSL: createRefFor(), constrainAs(), linkTo(), chain(), guideFrom() สิ่งนี้มีประโยชน์สำหรับเลย์เอาต์ที่ซับซ้อนซึ่งง่ายต่อการอธิบายผ่านข้อจำกัดมากกว่า Column/Row อย่างไรก็ตาม ใน Compose แนะนำให้เริ่มต้นด้วย Column/Row/Box และเปลี่ยนไปใช้ ConstraintLayout เฉพาะเมื่อจำเป็นต้องวางตำแหน่งสัมพัทธ์ที่ซับซ้อน
ใน Android Studio เปิด Layout Inspector (Tools → Layout Inspector), เลือกแอปที่กำลังทำงานและวางเมาส์เหนือองค์ประกอบที่มีปัญหา คุณจะเห็นข้อจำกัด ระยะขอบ การเติม และ bias ทั้งหมดในการแสดงผล 3 มิติ สำหรับ XML ให้ใช้แผง Design ในโปรแกรมแก้ไขเลย์เอาต์ — มันจะเน้นความขัดแย้งของข้อจำกัดเป็นสีเหลือง และข้อจำกัดที่ขาดหายไปเป็นสีแดง ในโค้ด ตรวจสอบให้แน่ใจว่าแต่ละวิวมีข้อจำกัดตั้งฉากสองรายการ มิฉะนั้นองค์ประกอบจะไปอยู่ที่ (0,0)
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม