FrameLayout: Các khái niệm chính, định vị đơn giản trong Android

Tác giả: IT Sectr Đã đăng: 2026-02-24 Thời gian đọc: 8 phút

Tìm hiểu các khái niệm chính của FrameLayout — ViewGroup đơn giản nhất trong Android SDK, được thiết kế để chứa một phần tử con duy nhất (thông thường) hoặc xếp chồng nhiều phần tử lên nhau. FrameLayout tiện lợi cho các vùng chứa giữ chỗ, fragment, chỉ báo tải và phần tử nền trước. Nó không quản lý vị trí của phần tử con — mỗi phần tử tiếp theo được vẽ lên trên phần tử trước đó và vị trí của chúng được xác định bởi layout_gravity (left, top, right, bottom, center). Các kịch bản cơ bản được mô tả trong Tài liệu tham khảo API FrameLayout.

Những điểm chính

  • Xếp chồng lớp — FrameLayout đặt các phần tử tuần tự lên nhau; phần tử được khai báo cuối cùng trong XML được vẽ ở trên cùng.
  • Nền trước (Foreground) — thuộc tính android:foreground cho phép đặt một drawable lên trên tất cả các phần tử con, hữu ích cho hiệu ứng và mặt nạ.
  • measureAllChildren — theo mặc định, FrameLayout đo tất cả các phần tử con, kể cả phần tử vô hình (GONE). Tắt nó giúp tăng tốc độ vẽ lên 20–60%.
  • layout_gravity — cách duy nhất để định vị phần tử con bên trong FrameLayout: top, bottom, left, right, center.
  • Sử dụng với Fragment — FrameLayout là vùng chứa tiêu chuẩn cho FragmentTransaction.replace(), thay thế nội dung mà không cần tạo lại Activity.

FrameLayout là gì?

FrameLayout là ViewGroup đơn giản nhất trong Android SDK, được thiết kế để chặn một vùng màn hình và hiển thị một view con duy nhất (hoặc xếp chồng nhiều view). Không giống như LinearLayout (sắp xếp tuần tự) và RelativeLayout (định vị tương đối), FrameLayout không thay đổi vị trí của các phần tử con — mỗi phần tử con mới được đặt ở góc trên bên trái (0,0) theo mặc định và được vẽ lên trên phần tử trước đó.

FrameLayout được giới thiệu ở API Level 1 và vẫn là vùng chứa Android nhẹ nhất: nó không ghi đè onMeasure bằng logic phức tạp và thực hiện số lượng thao tác layout tối thiểu. Theo Android Performance Blog, FrameLayout thực thi onLayout trong một lần duy nhất và hầu như không thêm chi phí so với việc đặt View trực tiếp. Điều này làm cho nó trở thành lựa chọn lý tưởng cho các vùng chứa mà tốc độ quan trọng: bố cục mục RecyclerView (kết hợp với ConstraintLayout để định vị), vùng chứa Fragment, các lớp phủ.

Kích thước của FrameLayout theo mặc định được xác định bởi phần tử con lớn nhất (nếu không đặt match_parent). Nếu không có phần tử con nào được chỉ định, FrameLayout thu gọn xuống (0,0). Thuộc tính android:measureAllChildren (phần bên dưới) thay đổi hành vi này.

FrameLayout trong hệ phân cấp ViewGroup Android

FrameLayout kế thừa từ ViewGroup và là cha trực tiếp của nhiều vùng chứa chuyên biệt: FragmentContainerView (fragment), CardView (thẻ có bóng), ScrollView (một phần tử con), NestedScrollView. Khi tạo Activity với điều hướng Fragment, mẫu Android Studio tiêu chuẩn sử dụng FrameLayout (hoặc FragmentContainerView) làm vùng chứa gốc cho các fragment.

Nền trước và measureAllChildren

android:foreground — một thuộc tính FrameLayout chỉ định một drawable được vẽ lên trên tất cả các phần tử con. Không giống như background (bên dưới phần tử con), foreground hiển thị phía trên nội dung và có thể trong suốt. Nó được sử dụng cho: hiệu ứng phủ khi nhấn (ripple qua ?attr/selectableItemBackground), tạo mặt nạ cho hình ảnh, hiển thị chỉ báo trạng thái (dấu chọn trên hình ảnh).

Foreground hỗ trợ các tài nguyên drawable tiêu chuẩn: ColorDrawable, ShapeDrawable, RippleDrawable, LayerDrawable. Từ API 23+, android:foregroundGravity có sẵn để định vị foreground (fill, center, top, bottom). Ở chế độ fill, foreground mở rộng trên toàn bộ FrameLayout; ở chế độ center, nó được vẽ ở trung tâm.

android:measureAllChildren — một thuộc tính boolean (mặc định là true) xác định liệu có nên đo tất cả các phần tử con khi tính kích thước FrameLayout hay không. Nếu true (mặc định), FrameLayout tính đến kích thước của tất cả các phần tử con, bao gồm cả GONE (với kích thước 0). Nếu false, FrameLayout chỉ đo các phần tử con VISIBLE và INVISIBLE — các phần tử GONE bị loại khỏi tính toán. Theo Google I/O 2019, tắt measureAllChildren cho các vùng chứa có nhiều phần tử GONE (ví dụ: danh sách chuyển đổi chế độ hiển thị) giúp tăng tốc độ vẽ ban đầu lên 20–60%.

layout_gravity: Định vị bên trong FrameLayout

android:layout_gravity — một thuộc tính của phần tử con FrameLayout (và các ViewGroup khác) xác định vị trí của nó bên trong vùng chứa. Trong FrameLayout, layout_gravity là cách duy nhất để kiểm soát vị trí của phần tử con, vì FrameLayout không cung cấp các quy tắc định vị riêng (như RelativeLayout) hoặc hướng (như LinearLayout).

Các giá trị có thể: top, bottom, left, right, center, center_horizontal, center_vertical, fill, fill_horizontal, fill_vertical, clip_horizontal, clip_vertical. Kết hợp qua |: android:layout_gravity="bottom|center_horizontal" — phần tử được gắn vào cạnh dưới và căn giữa theo chiều ngang. Đối với các phần tử nhỏ hơn FrameLayout, layout_gravity xác định vị trí của chúng trong không gian trống.

Nếu layout_gravity không được đặt, phần tử được đặt ở góc trên bên trái (top|left) theo mặc định. Đối với FrameLayout chứa nhiều phần tử con, mỗi phần tử có thể có layout_gravity riêng — một phần tử có thể ở góc trên bên trái, một phần tử khác ở góc dưới bên phải, phần tử thứ ba ở trung tâm. Điều này cho phép tạo các lớp phủ đơn giản (ví dụ: biểu tượng đóng trên hình ảnh).

Sự khác biệt: gravity vs layout_gravity

android:gravity (thuộc tính cha) căn chỉnh nội dung bên trong FrameLayout — ví dụ: văn bản bên trong TextView. android:layout_gravity (thuộc tính con) căn chỉnh chính phần tử bên trong FrameLayout. Trong ngữ cảnh của FrameLayout, gravity xác định cách các phần tử con được định vị theo mặc định (tương tự như layout_gravity cho tất cả các phần tử con cùng lúc), nhưng layout_gravity của từng phần tử cụ thể ghi đè giá trị cha.

Ví dụ: XML và trường hợp sử dụng

Ví dụ 1: Lớp phủ cơ bản — Nhãn trên hình ảnh

Một hình ảnh với nhãn văn bản ở góc dưới bên phải. FrameLayout chứa ImageView lấp đầy màn hình và TextView với layout_gravity="bottom|end" để định vị trên hình ảnh.

xml
<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 lấp đầy toàn bộ FrameLayout (chiều cao 200dp). TextView với layout_gravity="bottom|end" được đặt ở góc dưới bên phải trên hình ảnh. elevation=2dp thêm bóng bên dưới nhãn, tách biệt nó khỏi hình ảnh một cách trực quan. Ví dụ tối thiểu này sẽ yêu cầu một vùng chứa lồng nhau hoặc mã tùy chỉnh trong LinearLayout.

Ví dụ 2: Chỉ báo tải (thanh tiến trình trên nội dung)

Một màn hình có nội dung và thanh tiến trình ở trung tâm xuất hiện trong khi tải. FrameLayout chứa hai phần tử: nội dung và ProgressBar với visibility="gone" (được chuyển đổi trong mã).

xml
<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="Nội dung đã tải"
        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 bị ẩn theo mặc định (gone). Khi bắt đầu tải, findViewById(R.id.loading_spinner).visibility = View.VISIBLE được gọi — vòng quay xuất hiện ở trung tâm trên nội dung. Sau khi tải — .visibility = View.GONE. FrameLayout cung cấp lớp phủ mà không làm dịch chuyển vị trí nội dung — văn bản không di chuyển khi vòng quay xuất hiện, vì ProgressBar được vẽ lên trên.

Ví dụ 3: Vùng chứa Fragment

FrameLayout là vùng chứa tiêu chuẩn cho FragmentTransaction. Activity thay thế các fragment bên trong vùng chứa này tùy theo điều hướng.

xml
<FrameLayout
    android:id="@+id/fragment_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
kotlin
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 làm vùng chứa fragment là cách nhẹ nhất để hỗ trợ điều hướng Fragment. FragmentContainerView (lớp con của FrameLayout) được khuyến nghị với Navigation Component 2.4+, nhưng FrameLayout đơn giản vẫn hợp lệ cho FragmentTransaction thủ công. Lợi thế chính là FragmentTransaction.replace() thay thế hoàn toàn nội dung mà không ảnh hưởng đến Activity.

Khi nào sử dụng FrameLayout

FrameLayout tối ưu cho bốn kịch bản: vùng chứa fragment (FragmentContainerView hoặc FrameLayout), lớp phủ tải (ProgressBar trên nội dung), nhãn thẻ (nhãn trên hình ảnh), vùng chứa giữ chỗ cho ViewStub (tải lười).

Không phù hợp cho: định vị phức tạp nhiều phần tử (sử dụng ConstraintLayout), danh sách tuần tự (LinearLayout hoặc RecyclerView), bố cục động thay đổi thứ tự phần tử.

FrameLayout làm phần tử gốc của màn hình — thói quen xấu nếu màn hình chứa nhiều hơn 2–3 phần tử. Đối với vùng chứa gốc, hãy sử dụng ConstraintLayout — nó cung cấp nhiều kiểm soát hơn về định vị mà không làm giảm hiệu suất. Chỉ sử dụng FrameLayout cho các khối con nơi cần lớp phủ hoặc giữ chỗ.

Câu hỏi thường gặp

Sự khác biệt giữa FrameLayout và ConstraintLayout là gì?

FrameLayout — một vùng chứa tối giản không có hệ thống định vị (chỉ layout_gravity), được thiết kế để xếp chồng các phần tử. ConstraintLayout — một hệ thống bố cục hoàn chỉnh với các ràng buộc, chuỗi, rào cản, kích thước phần trăm và hỗ trợ MotionLayout. FrameLayout nhanh hơn cho các trường hợp đơn giản (1 phần tử con hoặc lớp phủ); ConstraintLayout cần thiết cho bố cục phức tạp. Lựa chọn: FrameLayout cho lớp phủ tải và vùng chứa fragment; ConstraintLayout cho mọi thứ khác.

Làm thế nào để FrameLayout chỉ chiếm không gian của phần tử con lớn nhất?

Đặt kích thước FrameLayout thành wrap_content trên trục mong muốn (android:layout_width="wrap_content" và/hoặc android:layout_height="wrap_content"). FrameLayout sau đó sẽ điều chỉnh theo kích thước của phần tử con lớn nhất. Nếu tất cả các phần tử con nhỏ hơn FrameLayout, không gian thừa sẽ để trống. Để kiểm soát chính xác, hãy sử dụng match_parent với padding cố định.

Tại sao các phần tử trong FrameLayout chồng lên nhau?

Đây là hành vi dự định của FrameLayout — nó không phân phối các phần tử con trong không gian mà vẽ chúng tuần tự lên nhau. Nếu bạn muốn các phần tử không chồng lên nhau, hãy sử dụng vùng chứa khác (LinearLayout, ConstraintLayout). Để chồng một phần với kiểm soát thứ tự vẽ, hãy sử dụng layout_gravity để dịch chuyển và elevation cho thứ tự Z.

Cái nào nhanh hơn: FrameLayout hay ConstraintLayout?

FrameLayout nhanh hơn cho các trường hợp đơn giản nhất — một phần tử con, không có định vị phức tạp. FrameLayout thực thi onMeasure trong thời gian tối thiểu mà không tính toán các ràng buộc. Tuy nhiên, sự khác biệt chỉ đáng chú ý ở hàng trăm lần lặp (RecyclerView với hàng chục nghìn mục). Đối với màn hình điển hình (1–3 FrameLayout), sự khác biệt là micro giây và không ảnh hưởng đến trải nghiệm người dùng. ConstraintLayout là lựa chọn phổ quát cho 95% tác vụ.

Tóm tắt

  • FrameLayout — ViewGroup Android đơn giản nhất để xếp chồng phần tử và vùng chứa giữ chỗ với chi phí tối thiểu.
  • Xếp chồng lớp — mỗi phần tử con mới được vẽ lên trên phần tử trước đó; phần tử được khai báo cuối cùng trong XML ở trên cùng.
  • Nền trước — một drawable trên tất cả các phần tử con cho hiệu ứng phủ, tạo mặt nạ và hoạt ảnh gợn sóng.
  • measureAllChildren — tắt (false) loại trừ các phần tử GONE khỏi tính toán kích thước, tăng tốc độ vẽ lên 20–60%.
  • layout_gravity — cơ chế định vị duy nhất: top, bottom, left, right, center và các kết hợp của chúng.
  • Vùng chứa Fragment — trường hợp sử dụng phổ biến nhất: FrameLayout (hoặc FragmentContainerView) cho FragmentTransaction.replace().
  • Hiệu suất — vùng chứa nhanh nhất cho 1 phần tử con; đối với bố cục phức tạp, nó thua kém ConstraintLayout.

Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay

IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.

Thảo luận dự án

Đọc thêm