เรียนรู้แนวคิดหลักของ FrameLayout — ViewGroup ที่ง่ายที่สุดใน Android SDK ออกแบบมาเพื่อโฮสต์องค์ประกอบย่อยเดียว (โดยทั่วไป) หรือซ้อนหลายองค์ประกอบทับกัน FrameLayout สะดวกสำหรับคอนเทนเนอร์ตัวแทน เศษส่วน (fragments) ตัวบ่งชี้การโหลด และองค์ประกอบพื้นหน้า ไม่จัดการตำแหน่งขององค์ประกอบย่อย — องค์ประกอบถัดไปแต่ละรายการจะถูกวาดทับองค์ประกอบก่อนหน้า และตำแหน่งขององค์ประกอบเหล่านั้นถูกกำหนดโดย layout_gravity (left, top, right, bottom, center) สถานการณ์พื้นฐานอธิบายไว้ใน FrameLayout API Reference
ประเด็นสำคัญ
FrameLayout คือ ViewGroup ที่ง่ายที่สุดใน Android SDK ออกแบบมาเพื่อบล็อกพื้นที่หน้าจอและแสดงวิวย่อยเดียว (หรือซ้อนหลายวิว) แตกต่างจาก LinearLayout (การจัดเรียงตามลำดับ) และ RelativeLayout (การวางตำแหน่งแบบสัมพัทธ์) FrameLayout ไม่เปลี่ยนแปลงตำแหน่งขององค์ประกอบย่อย — องค์ประกอบย่อยใหม่แต่ละรายการจะถูกวางไว้ที่มุมบนซ้าย (0,0) โดยค่าเริ่มต้นและวาดทับองค์ประกอบก่อนหน้า
FrameLayout ถูกนำมาใช้ใน API Level 1 และยังคงเป็นคอนเทนเนอร์ Android ที่เบาที่สุด: ไม่แทนที่ onMeasure ด้วยตรรกะที่ซับซ้อนและดำเนินการ layout น้อยที่สุด ตาม Android Performance Blog FrameLayout ดำเนินการ onLayout ในครั้งเดียวและแทบไม่เพิ่มค่าใช้จ่ายเมื่อเทียบกับการวาง View โดยตรง ทำให้เป็นตัวเลือกที่เหมาะสำหรับคอนเทนเนอร์ที่ความเร็วสำคัญ: เลย์เอาต์ไอเท็ม RecyclerView (ร่วมกับ ConstraintLayout สำหรับการวางตำแหน่ง) คอนเทนเนอร์ Fragment ชั้นซ้อนทับ
ขนาดของ FrameLayout โดยค่าเริ่มต้นถูกกำหนดโดยองค์ประกอบย่อยที่ใหญ่ที่สุด (หากไม่ได้ตั้งค่า match_parent) หากไม่ได้ระบุองค์ประกอบย่อย FrameLayout จะยุบเป็น (0,0) แอตทริบิวต์ android:measureAllChildren (ส่วนด้านล่าง) เปลี่ยนแปลงพฤติกรรมนี้
FrameLayout สืบทอดจาก ViewGroup และเป็นพาเรนต์โดยตรงของคอนเทนเนอร์เฉพาะทางมากมาย: FragmentContainerView (fragments), CardView (การ์ดที่มีเงา), ScrollView (องค์ประกอบย่อยเดียว), NestedScrollView เมื่อสร้าง Activity ด้วยการนำทางแบบ Fragment เทมเพลตมาตรฐานของ Android Studio จะใช้ FrameLayout (หรือ FragmentContainerView) เป็นคอนเทนเนอร์รูทสำหรับ fragments
android:foreground — แอตทริบิวต์ FrameLayout ที่ระบุ drawable ซึ่งวาดทับองค์ประกอบย่อยทั้งหมด แตกต่างจาก background (ใต้ element ย่อย) foreground จะแสดงเหนือเนื้อหาและสามารถโปร่งใสได้ ใช้สำหรับ: เอฟเฟกต์ซ้อนทับเมื่อกด (ripple ผ่าน ?attr/selectableItemBackground), การซ่อนภาพ, การแสดงตัวบ่งชี้สถานะ (เครื่องหมายถูกบนภาพ)
Foreground รองรับทรัพยากร drawable มาตรฐาน: ColorDrawable, ShapeDrawable, RippleDrawable, LayerDrawable ตั้งแต่ API 23+ android:foregroundGravity พร้อมใช้งานสำหรับการวางตำแหน่งพื้นหน้า (fill, center, top, bottom) ในโหมดเติม (fill) foreground จะขยายทั่วทั้ง FrameLayout; ในโหมด center จะวาดที่กึ่งกลาง
android:measureAllChildren — แอตทริบิวต์บูลีน (ค่าเริ่มต้น true) ที่กำหนดว่าจะวัดองค์ประกอบย่อยทั้งหมดเมื่อคำนวณขนาด FrameLayout หรือไม่ หากเป็น true (ค่าเริ่มต้น) FrameLayout จะพิจารณาขนาดขององค์ประกอบย่อยทั้งหมดรวมถึง GONE (ด้วยขนาด 0) หากเป็น false FrameLayout จะวัดเฉพาะองค์ประกอบย่อย VISIBLE และ INVISIBLE — องค์ประกอบ GONE จะถูกแยกออกจากการคำนวณ ตาม Google I/O 2019 การปิด measureAllChildren สำหรับคอนเทนเนอร์ที่มีองค์ประกอบ GONE จำนวนมาก (เช่น รายการที่สลับการมองเห็น) จะเพิ่มความเร็วในการวาดเริ่มต้น 20–60%
android:layout_gravity — แอตทริบิวต์ขององค์ประกอบย่อย FrameLayout (และ ViewGroup อื่นๆ) ที่กำหนดตำแหน่งภายในคอนเทนเนอร์ ใน FrameLayout layout_gravity เป็นวิธีเดียวในการควบคุมตำแหน่งขององค์ประกอบย่อย เนื่องจาก FrameLayout ไม่มีกฎการวางตำแหน่งของตัวเอง (เช่น RelativeLayout) หรือทิศทาง (เช่น LinearLayout)
ค่าที่เป็นไปได้: top, bottom, left, right, center, center_horizontal, center_vertical, fill, fill_horizontal, fill_vertical, clip_horizontal, clip_vertical รวมกันผ่าน |: android:layout_gravity="bottom|center_horizontal" — องค์ประกอบจะชิดขอบล่างและกึ่งกลางในแนวนอน สำหรับองค์ประกอบที่เล็กกว่า FrameLayout layout_gravity จะกำหนดตำแหน่งในพื้นที่ว่าง
หากไม่ได้ตั้งค่า layout_gravity องค์ประกอบจะถูกวางไว้ที่ มุมบนซ้าย (top|left) โดยค่าเริ่มต้น สำหรับ FrameLayout ที่มีองค์ประกอบย่อยหลายรายการ แต่ละรายการสามารถมี layout_gravity ของตัวเองได้ — องค์ประกอบหนึ่งอาจอยู่ที่มุมบนซ้าย อีกองค์ประกอบหนึ่งที่มุมล่างขวา องค์ประกอบที่สามอยู่กึ่งกลาง สิ่งนี้ช่วยให้สร้างการซ้อนทับอย่างง่าย (เช่น ไอคอนปิดบนภาพ)
android:gravity (แอตทริบิวต์พาเรนต์) จัดแนวเนื้อหาภายใน FrameLayout — ตัวอย่างเช่น ข้อความภายใน TextView android:layout_gravity (แอตทริบิวต์ย่อย) จัดแนวองค์ประกอบเองภายใน FrameLayout ในบริบทของ FrameLayout gravity กำหนดว่าองค์ประกอบย่อยถูกวางตำแหน่งอย่างไรโดยค่าเริ่มต้น (คล้ายกับ layout_gravity สำหรับองค์ประกอบย่อยทั้งหมดพร้อมกัน) แต่ layout_gravity ของแต่ละองค์ประกอบเฉพาะจะแทนที่ค่าพาเรนต์
ภาพที่มีป้ายข้อความที่มุมล่างขวา FrameLayout ประกอบด้วย ImageView ที่เต็มหน้าจอและ TextView ที่มี layout_gravity="bottom|end" สำหรับวางตำแหน่งทับภาพ
<FrameLayout
android:layout_width="match_parent"
android:layout_height="200dp">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/product_photo"
android:scaleType="centerCrop" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="8dp"
android:background="@drawable/badge_background"
android:elevation="2dp"
android:paddingHorizontal="8dp"
android:paddingVertical="4dp"
android:text="-30%"
android:textColor="@android:color/white"
android:textSize="14sp"
android:textStyle="bold" />
</FrameLayout>
ImageView เติม FrameLayout ทั้งหมด (สูง 200dp) TextView ที่มี layout_gravity="bottom|end" ถูกวางไว้ที่มุมล่างขวาทับภาพ elevation=2dp เพิ่มเงาใต้ป้าย แยกออกจากภาพด้วยสายตา ตัวอย่างเล็กน้อยนี้ใน LinearLayout จะต้องใช้คอนเทนเนอร์ที่ซ้อนกันหรือโค้ดที่กำหนดเอง
หน้าจอที่มีเนื้อหาและแถบความคืบหน้าที่กึ่งกลางซึ่งปรากฏระหว่างการโหลด FrameLayout ประกอบด้วยสององค์ประกอบ: เนื้อหาและ ProgressBar ที่มี visibility="gone" (สลับในโค้ด)
<FrameLayout
android:id="@+id/content_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/content_text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="เนื้อหาที่โหลด"
android:textSize="18sp" />
<ProgressBar
android:id="@+id/loading_spinner"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="center"
android:visibility="gone" />
</FrameLayout>
ProgressBar ถูกซ่อนไว้โดยค่าเริ่มต้น (gone) เมื่อเริ่มโหลด จะเรียก findViewById(R.id.loading_spinner).visibility = View.VISIBLE — สปินเนอร์ปรากฏที่กึ่งกลางทับเนื้อหา หลังจากโหลด — .visibility = View.GONE FrameLayout ให้การซ้อนทับโดยไม่เปลี่ยนตำแหน่งเนื้อหา — ข้อความไม่ขยับเมื่อสปินเนอร์ปรากฏ เนื่องจาก ProgressBar วาดทับ
FrameLayout เป็นคอนเทนเนอร์มาตรฐานสำหรับ FragmentTransaction Activity จะแทนที่ fragments ภายในคอนเทนเนอร์นี้ตามการนำทาง
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
import androidx.fragment.app.FragmentTransaction
val fragmentContainer = R.id.fragment_container
fun navigateTo(fragment: Fragment) {
supportFragmentManager
.beginTransaction()
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.replace(fragmentContainer, fragment)
.addToBackStack(null)
.commit()
}
FrameLayout เป็นคอนเทนเนอร์ fragment เป็นวิธีที่เบาที่สุดในการรองรับการนำทาง Fragment FragmentContainerView (ซับคลาสของ FrameLayout) แนะนำให้ใช้กับ Navigation Component 2.4+ แต่ FrameLayout ธรรมดายังคงใช้ได้สำหรับ FragmentTransaction ด้วยตนเอง ข้อได้เปรียบหลักคือ FragmentTransaction.replace() จะแทนที่เนื้อหาทั้งหมดโดยไม่ส่งผลกระทบต่อ Activity
FrameLayout เหมาะสมที่สุด สำหรับสี่สถานการณ์: คอนเทนเนอร์ fragment (FragmentContainerView หรือ FrameLayout), การซ้อนทับการโหลด (ProgressBar ทับเนื้อหา), ป้ายการ์ด (ป้ายบนภาพ), คอนเทนเนอร์ตัวแทนสำหรับ ViewStub (การโหลดแบบขี้เกียจ)
ไม่เหมาะสำหรับ: การวางตำแหน่งที่ซับซ้อนของหลายองค์ประกอบ (ใช้ ConstraintLayout), รายการตามลำดับ (LinearLayout หรือ RecyclerView), เลย์เอาต์แบบไดนามิกที่เปลี่ยนลำดับองค์ประกอบ
FrameLayout เป็นองค์ประกอบรูทของหน้าจอ — ปฏิบัติที่ไม่ดีหากหน้าจอมีมากกว่า 2–3 องค์ประกอบ สำหรับคอนเทนเนอร์รูท ให้ใช้ ConstraintLayout — ให้การควบคุมการวางตำแหน่งมากขึ้นโดยไม่สูญเสียประสิทธิภาพ ใช้ FrameLayout เฉพาะสำหรับบล็อกย่อยที่ต้องการการซ้อนทับหรือตัวแทน
คำถามที่พบบ่อย
FrameLayout — คอนเทนเนอร์แบบมินิมอลไม่มีระบบวางตำแหน่ง (เฉพาะ layout_gravity) ออกแบบมาสำหรับการซ้อนทับองค์ประกอบ ConstraintLayout — ระบบเลย์เอาต์ครบวงจรพร้อมข้อจำกัด (constraints) เชื่อมโยง (chains) สิ่งกีดขวาง (barriers) ขนาดเปอร์เซ็นต์และการรองรับ MotionLayout FrameLayout เร็วกว่าสำหรับกรณีง่ายๆ (1 องค์ประกอบย่อยหรือการซ้อนทับ) ConstraintLayout จำเป็นสำหรับเลย์เอาต์ที่ซับซ้อน ทางเลือก: FrameLayout สำหรับการซ้อนทับการโหลดและคอนเทนเนอร์ fragment; ConstraintLayout สำหรับทุกอย่างอื่น
ตั้งค่าขนาด FrameLayout เป็น wrap_content บนแกนที่ต้องการ (android:layout_width="wrap_content" และ/หรือ android:layout_height="wrap_content") FrameLayout จะปรับตามขนาดขององค์ประกอบย่อยที่ใหญ่ที่สุด หากองค์ประกอบย่อยทั้งหมดเล็กกว่า FrameLayout พื้นที่ส่วนเกินจะว่างเปล่า สำหรับการควบคุมที่แม่นยำ ให้ใช้ match_parent กับ padding คงที่
นี่คือพฤติกรรมที่ตั้งใจของ FrameLayout — มันไม่ได้กระจายองค์ประกอบย่อยในพื้นที่ แต่วาดตามลำดับทับกัน หากคุณต้องการให้องค์ประกอบไม่ทับซ้อนกัน ให้ใช้คอนเทนเนอร์อื่น (LinearLayout, ConstraintLayout) สำหรับการซ้อนทับบางส่วนด้วยการควบคุมลำดับการวาด ให้ใช้ layout_gravity สำหรับการขยับและ elevation สำหรับลำดับ Z
FrameLayout เร็วกว่า สำหรับกรณีที่ง่ายที่สุด — องค์ประกอบย่อยเดียว ไม่มีการวางตำแหน่งที่ซับซ้อน FrameLayout ดำเนินการ onMeasure ในเวลาน้อยที่สุดโดยไม่คำนวณข้อจำกัด อย่างไรก็ตาม ความแตกต่างจะสังเกตเห็นได้เฉพาะในการทำซ้ำหลายร้อยครั้ง (RecyclerView ที่มีรายการนับหมื่น) สำหรับหน้าจอทั่วไป (1–3 FrameLayout) ความแตกต่างเป็นไมโครวินาทีและไม่ส่งผลต่อประสบการณ์ผู้ใช้ ConstraintLayout เป็นตัวเลือกสากลสำหรับ 95% ของงาน
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม