เราอธิบายสาระสำคัญของ RelativeLayout — ViewGroup ใน Android SDK สำหรับจัดตำแหน่งองค์ประกอบย่อยโดยสัมพันธ์กันหรือสัมพันธ์กับคอนเทนเนอร์พาเรนต์ แตกต่างจาก LinearLayout ซึ่งจัดเรียงองค์ประกอบตามลำดับอย่างเคร่งครัด RelativeLayout ช่วยให้คุณวางองค์ประกอบใดๆ ทางซ้าย ขวา บน ล่าง หรือกึ่งกลางโดยสัมพันธ์กับองค์ประกอบอื่นใดๆ ตาม ID ของมัน ทำให้ RelativeLayout สะดวกสำหรับเค้าโครงที่ซับซ้อนและทับซ้อนกันโดยไม่ต้องซ้อนกัน กฎพื้นฐานอธิบายไว้ใน เอกสารประกอบของ Android Developers
ประเด็นสำคัญ
RelativeLayout — ViewGroup ที่ถูกนำมาใช้ใน Android SDK ตั้งแต่ API Level 1 ซึ่งจัดตำแหน่งองค์ประกอบย่อยผ่านกฎแบบประกาศ (layout rules) โดยสัมพันธ์กับพาเรนต์หรือองค์ประกอบย่อยอื่นๆ แต่ละกฎถูกกำหนดด้วยแอตทริบิวต์ XML เช่น android:layout_above, android:layout_below, android:layout_toLeftOf โดยมีค่า ID ขององค์ประกอบเป้าหมาย
ก่อนที่ RelativeLayout จะปรากฏ นักพัฒนาต้องใช้ LinearLayout ที่ซ้อนกันสำหรับทุกตำแหน่งที่ไม่ได้มาตรฐาน — สิ่งนี้นำไปสู่การซ้อนกันลึกและการเรนเดอร์ที่ช้า RelativeLayout แก้ปัญหานี้โดยเสนอการกำหนดตำแหน่งแบบสัมพัทธ์ในระดับลำดับชั้นเดียว อย่างไรก็ตาม ความยืดหยุ่นของมันถูกจำกัดเมื่อเทียบกับ ConstraintLayout สมัยใหม่: RelativeLayout ไม่รองรับเชน, แบริเออร์, ไกด์ไลน์ หรือขนาดแบบเปอร์เซ็นต์
RelativeLayout มีคุณสมบัติหลักสองประการ: onMeasure ผ่านครั้งเดียว (แตกต่างจาก LinearLayout ที่มี weight ซึ่งต้องผ่านสองครั้ง) และ การขึ้นอยู่กับลำดับการประกาศ — หากองค์ประกอบ A อ้างอิงถึงองค์ประกอบ B และ B ถูกประกาศหลังจาก A B ต้องมีขนาดที่ทราบในเวลาที่ทำการวัดของ A ตามคู่มือนักพัฒนา Android (2024) RelativeLayout ทำการวัดในครั้งเดียวหากกฎทั้งหมดอ้างอิงถึงองค์ประกอบที่วัดแล้ว มิฉะนั้น จะต้องมีการวัดครั้งที่สอง ซึ่งลดประสิทธิภาพ
RelativeLayout เหมาะสมสำหรับเค้าโครงง่ายๆ ที่มี 3–7 องค์ประกอบซึ่งต้องการการกำหนดตำแหน่งแบบสัมพัทธ์ (ป้ายกำกับเหนือปุ่ม, ไอคอนทางขวาของข้อความ, แผงด้านล่างของหน้าจอ) สำหรับโปรเจกต์ใหม่ แนะนำให้ใช้ ConstraintLayout แต่ RelativeLayout ยังคงใช้อย่างแพร่หลายในโคดเก่าและไลบรารี
RelativeLayout ใช้กฎสองประเภท: กฎ relative (การกำหนดตำแหน่งสัมพัทธ์กับองค์ประกอบอื่น) และกฎ align (การยึดกับพาเรนต์) องค์ประกอบย่อยแต่ละตัวสามารถมีกฎได้ไม่จำกัดจำนวน แต่กฎที่ขัดแย้งกัน (เช่น alignParentTop และ below ของอีกองค์ประกอบหนึ่ง) จะถูกแก้ไขโดยให้ความสำคัญกับแอตทริบิวต์ที่ประกาศล่าสุด
กฎ relative:
- android:layout_above="@id/target" — ขอบล่างขององค์ประกอบถูกยึดกับขอบบนของเป้าหมาย
- android:layout_below="@id/target" — ขอบบนขององค์ประกอบถูกยึดกับขอบล่างของเป้าหมาย
- android:layout_toLeftOf="@id/target" — ขอบขวาถูกยึดกับขอบซ้ายของเป้าหมาย
- android:layout_toRightOf="@id/target" — ขอบซ้ายถูกยึดกับขอบขวาของเป้าหมาย
- android:layout_toStartOf="@id/target" — คล้ายกับ toLeftOf สำหรับตำแหน่งที่ตั้ง RTL
- android:layout_toEndOf="@id/target" — คล้ายกับ toRightOf สำหรับ RTL
กฎ align (กับพาเรนต์):
- android:layout_alignParentTop="true" — ขอบบนขององค์ประกอบตรงกับขอบบนของพาเรนต์
- android:layout_alignParentBottom="true" — ขอบล่างตรงกับขอบล่างของพาเรนต์
- android:layout_alignParentLeft="true" — ขอบซ้ายตรงกับขอบซ้ายของพาเรนต์
- android:layout_alignParentRight="true" — ขอบขวาตรงกับขอบขวาของพาเรนต์
- android:layout_alignParentStart และ android:layout_alignParentEnd — สำหรับ RTL
กฎ align กับองค์ประกอบอื่น:
- android:layout_alignTop="@id/target" — ด้านบนขององค์ประกอบตรงกับด้านบนของเป้าหมาย
- android:layout_alignBottom, android:layout_alignLeft, android:layout_alignRight, android:layout_alignStart, android:layout_alignEnd — คล้ายกัน
- android:layout_alignBaseline="@id/target" — การจัดแนวตามเส้นฐานข้อความ (มีประโยชน์สำหรับสตริงที่มีขนาดฟอนต์ต่างกัน)
การจัดกึ่งกลางใน RelativeLayout ถูกกำหนดโดยสามแอตทริบิวต์: android:layout_centerInParent="true" (กึ่งกลางของพาเรนต์บนทั้งสองแกน), android:layout_centerHorizontal="true" (กึ่งกลางแนวนอน) และ android:layout_centerVertical="true" (กึ่งกลางแนวตั้ง) การจัดกึ่งกลางเข้ากันได้กับกฎอื่นๆ: องค์ประกอบสามารถถูกจัดกึ่งกลางแนวนอนในขณะที่ถูกยึดกับขอบล่างของพาเรนต์
ลำดับ Z (ลำดับการซ้อนทับ) ใน RelativeLayout ถูกกำหนดโดยลำดับการประกาศองค์ประกอบใน XML องค์ประกอบที่ประกาศแรกจะถูกวาดก่อน (จะอยู่ด้านล่าง) องค์ประกอบสุดท้าย — สุดท้าย (จะอยู่ด้านบน) หากคุณต้องการเปลี่ยนลำดับการซ้อนทับโดยไม่เปลี่ยน XML ให้ใช้ android:elevation (ตั้งแต่ API 21) — องค์ประกอบที่มี elevation สูงกว่าจะถูกวาดไว้ด้านบน สำหรับเวอร์ชันต่ำกว่า API 21 ลำดับการซ้อนทับจะถูกควบคุมโดยลำดับการประกาศเท่านั้น
สำคัญ: RelativeLayout ไม่รองรับแอตทริบิวต์ android:elevation สำหรับพาเรนต์ — แต่ละองค์ประกอบควบคุม elevation ของตัวเอง เมื่อกฎการกำหนดตำแหน่งและ elevation ขัดแย้งกัน (เช่น องค์ประกอบที่ทับซ้อนกัน) elevation มีลำดับความสำคัญสำหรับการเรนเดอร์ แต่ไม่ใช่สำหรับการกำหนดตำแหน่ง — องค์ประกอบที่มี elevation สูงกว่าสามารถทับซ้อนอีกองค์ประกอบหนึ่งทางสายตา แม้ว่ากฎเค้าโครงของมันจะวางไว้ "ด้านหลัง"
รูปประจำตัวกึ่งกลางด้านบน ชื่อใต้รูปประจำตัว สถานะใต้ชื่อ ปุ่มดำเนินการด้านล่างของหน้าจอ องค์ประกอบทั้งหมดใช้กฎ relative สำหรับการยึดตามลำดับ
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/avatar"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_centerHorizontal="true"
android:layout_marginTop="32dp"
android:src="@drawable/avatar_placeholder" />
<TextView
android:id="@+id/user_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/avatar"
android:layout_centerHorizontal="true"
android:layout_marginTop="12dp"
android:text="อีวาน อีวานอฟ"
android:textSize="20sp"
android:textStyle="bold" />
<TextView
android:id="@+id/user_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/user_name"
android:layout_centerHorizontal="true"
android:text="ออนไลน์"
android:textColor="@color/green" />
<Button
android:id="@+id/edit_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="24dp"
android:text="แก้ไขโปรไฟล์" />
</RelativeLayout>
รูปประจำตัวถูกยึดกับด้านบนผ่าน marginTop + centerHorizontal ชื่ออยู่ใต้รูปประจำตัวผ่าน layout_below สถานะอยู่ใต้ชื่อ และปุ่มถูกยึดกับขอบล่างของพาเรนต์ผ่าน alignParentBottom องค์ประกอบทั้งหมดเป็นอิสระต่อกัน — ไม่มีเชนหรือการซ้อนกัน
ไอคอนที่มีป้ายสีแดง (วงกลมพร้อมตัวเลข) ที่มุมขวาบน ป้ายถูกวางตำแหน่งโดยสัมพันธ์กับไอคอนผ่านการจัดแนวกับขอบของมันด้วยมาร์จินติดลบสำหรับการซ้อนทับ
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/icon"
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_notification" />
<TextView
android:id="@+id/badge"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_alignTop="@+id/icon"
android:layout_alignRight="@+id/icon"
android:layout_marginTop="-6dp"
android:layout_marginRight="-6dp"
android:gravity="center"
android:background="@drawable/badge_background"
android:text="3"
android:textColor="@android:color/white"
android:textSize="10sp" />
</RelativeLayout>
ป้ายถูกยึดกับขอบบนและขวาของไอคอน มาร์จินติดลบ (-6dp) เลื่อนป้ายออกไปด้านนอก สร้างเอฟเฟกต์การทับซ้อนของขอบไอคอน นี่เป็นรูปแบบที่นิยมสำหรับการแจ้งเตือน ซึ่งหากไม่มี RelativeLayout จะต้องใช้ FrameLayout หรือการวาดแบบกำหนดเอง
ฟอร์มเข้าสู่ระบบ: โลโก้กึ่งกลางด้านบน ช่องอีเมลด้านล่าง ช่องรหัสผ่านด้านล่างอีเมล ปุ่ม "เข้าสู่ระบบ" ด้านล่างรหัสผ่าน ลิงก์ "ลงทะเบียน" ยึดติดกับด้านล่าง องค์ประกอบทั้งหมดถูกยึดตามลำดับผ่าน layout_below
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="24dp">
<TextView
android:id="@+id/logo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="48dp"
android:text="MyApp"
android:textSize="32sp"
android:textStyle="bold" />
<EditText
android:id="@+id/email_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/logo"
android:layout_marginTop="32dp"
android:hint="Email" />
<EditText
android:id="@+id/password_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/email_input"
android:layout_marginTop="16dp"
android:hint="รหัสผ่าน"
android:inputType="textPassword" />
<Button
android:id="@+id/login_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/password_input"
android:layout_marginTop="24dp"
android:text="เข้าสู่ระบบ" />
<TextView
android:id="@+id/register_link"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="16dp"
android:text="ยังไม่มีบัญชี? ลงทะเบียน"
android:textColor="@color/primary" />
</RelativeLayout>
หน้าจอใช้องค์ประกอบ 5 รายการในระดับลำดับชั้นเดียว โฟลว์จากบนลงล่าง (โลโก้ → อีเมล → รหัสผ่าน → ปุ่ม) ทำได้ผ่าน layout_below และลิงก์ถูกดันลงด้านล่างผ่าน alignParentBottom สำหรับฟังก์ชันการทำงานเดียวกัน LinearLayout จะต้องใช้คอนเทนเนอร์ที่ซ้อนกันสองตัว (หนึ่งตัวสำหรับกลุ่มบน, หนึ่งตัวสำหรับลิงก์ที่มี weight)
การเปรียบเทียบ ViewGroup สองตัวสำหรับการกำหนดตำแหน่งแบบสัมพัทธ์: RelativeLayout (คลาสสิก, API 1+) และ ConstraintLayout (สมัยใหม่, Jetpack) เกณฑ์: ความยืดหยุ่น, ประสิทธิภาพ, การรองรับ RTL และความสามารถในการปรับเปลี่ยน
| คุณลักษณะ | RelativeLayout | ConstraintLayout |
|---|---|---|
| ขนาดเปอร์เซ็นต์ | ไม่ (เฉพาะแบบคงที่และ match_parent) | ใช่ (width_percent, height_percent, guideline_percent) |
| การรองรับ RTL | ผ่าน start/end (API 17+) | ในตัว |
| Chain / เชน | ไม่ | ใช่ (spread, spread_inside, packed) |
| Barrier / Group / Flow | ไม่ | ใช่ (ตัวช่วยเสมือน) |
| แอนิเมชัน MotionLayout | ไม่ | ใช่ |
| รอบ onMeasure | 1 (ไม่มีวงจรการพึ่งพา) | 2 (เสมอ แต่ได้รับการปรับให้เหมาะสม) |
| การจัดกึ่งกลาง | centerInParent, centerHorizontal, centerVertical | ผ่าน bias (0.0–1.0) พร้อมข้อจำกัดตรงข้าม |
| เมื่อใดควรใช้ | โคดเก่า, เค้าโครงง่ายๆ ที่มี 3–7 องค์ประกอบ | โปรเจกต์ใหม่, หน้าจอซับซ้อน, เค้าโครงปรับเปลี่ยนได้ |
RelativeLayout ด้อยกว่า ConstraintLayout ในเกือบทุกด้านยกเว้นหนึ่ง: มันถูกสร้างใน Android SDK ตั้งแต่ API 1 และไม่ต้องการการพึ่งพาเพิ่มเติม สำหรับโปรเจกต์ที่ยังใช้ minSdk ต่ำกว่า API 14 (Android 4.0) RelativeLayout เป็นตัวเลือกมาตรฐานเดียวสำหรับการกำหนดตำแหน่งแบบสัมพัทธ์ ในกรณีอื่นๆ ทั้งหมด Google แนะนำ ConstraintLayout
คำถามที่พบบ่อย
สาเหตุที่เป็นไปได้มากที่สุด คือการขาดกฎการกำหนดตำแหน่งที่เพียงพอ แต่ละองค์ประกอบใน RelativeLayout ต้องมีกฎแนวนอนอย่างน้อยหนึ่งกฎ (alignParentLeft, toRightOf, centerHorizontal) และกฎแนวตั้งหนึ่งกฎ (alignParentTop, below, centerVertical) หากไม่มีกฎ องค์ประกอบจะอยู่ในตำแหน่ง (0,0) และอาจถูกซ่อนไว้ใต้องค์ประกอบอื่นหรือนอกหน้าจอ สาเหตุที่สองคือการพึ่งพาแบบวงจร (องค์ประกอบ A เชื่อมกับ B, B เชื่อมกับ A) ซึ่งนำไปสู่พฤติกรรมที่ไม่สามารถคาดเดาได้
ใช้แอตทริบิวต์ android:layout_centerInParent="true" มันจะจัดกึ่งกลางองค์ประกอบบนทั้งสองแกนพร้อมกัน หากต้องการเฉพาะแนวนอน — android:layout_centerHorizontal="true" เฉพาะแนวตั้ง — android:layout_centerVertical="true" การจัดกึ่งกลางทำงานร่วมกับมาร์จิน — องค์ประกอบจะถูกเลื่อนโดยสัมพันธ์กับกึ่งกลางตามระยะที่ระบุ
ได้ แต่ไม่แนะนำ RelativeLayout ดำเนินการ onMeasure สองครั้งเมื่อมีการพึ่งพาแบบวงจร ซึ่งช้ากว่า ConstraintLayout 15–30% ในรายการ RecyclerView สำหรับเค้าโครงรายการของ RecyclerView ให้ใช้ ConstraintLayout — มันให้ลำดับชั้นแบบเรียบที่มีเวลาในการวัดน้อยที่สุด หาก RelativeLayout ถูกใช้ในโปรเจกต์เก่าอยู่แล้ว ให้ตรวจสอบว่าองค์ประกอบไม่สร้างการพึ่งพาแบบวงจร และวัด FPS การเลื่อนผ่าน Profile GPU Rendering
android:layout_margin กำหนดมาร์จินทุกด้านพร้อมกัน android:layout_marginTop — เฉพาะด้านบน แอตทริบิวต์ที่เฉพาะเจาะจงมากกว่าจะได้รับความสำคัญ: หากทั้ง layout_margin (16dp) และ layout_marginTop (8dp) ถูกตั้งค่า มาร์จินด้านบนจะเป็น 8dp ใน RelativeLayout มาร์จินทำงานโดยสัมพันธ์กับข้อจำกัด ไม่ใช่โดยสัมพันธ์กับองค์ประกอบที่อยู่ติดกัน: layout_marginTop กับ layout_below="@id/target" คือมาร์จินจากขอบบนขององค์ประกอบถึงขอบล่างของเป้าหมาย
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม