Hiển thị nội dung tràn viền (edge-to-edge) trong app dùng View
Khi app target SDK 35 trở lên và chạy trên thiết bị Android 15 trở lên, app sẽ hiển thị edge-to-edge: cửa sổ app chiếm toàn bộ chiều rộng và chiều cao màn hình, vẽ cả phần nằm phía sau các system bar. System bar gồm status bar, caption bar và navigation bar.
Nhiều app có top app bar. Top app bar nên kéo dài lên tận mép trên màn hình và nằm phía sau status bar. Bạn có thể cho top app bar thu nhỏ lại bằng chiều cao status bar khi nội dung được cuộn.
Tương tự, nhiều app có bottom app bar hoặc bottom navigation bar. Các thanh này cũng nên kéo dài xuống mép dưới màn hình và nằm phía sau navigation bar. Nếu app không có các thanh này, nội dung cuộn nên hiển thị phía sau navigation bar.
Khi làm bố cục edge-to-edge cho app, bạn cần lưu ý các việc sau:
- Bật hiển thị edge-to-edge
- Làm bố cục thích ứng (adaptive layout) để trải nghiệm tốt trên nhiều kiểu thiết bị (điện thoại, tablet, máy gập…)
- Xử lý các chỗ nội dung bị chồng lấn với system UI
- Cân nhắc hiển thị scrim (lớp nền mờ) phía sau các system bar

Hình 2. Ví dụ hình ảnh hiển thị phía sau status bar.
Bật hiển thị edge-to-edge
Phần tiêu đề “Bật hiển thị edge-to-edge”Nếu app target SDK 35 trở lên, edge-to-edge được bật tự động trên thiết bị Android 15 trở lên.
Để bật edge-to-edge trên các phiên bản Android cũ hơn, gọi enableEdgeToEdge trong onCreate của Activity:
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) WindowCompat.enableEdgeToEdge(window) ...}@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); WindowCompat.enableEdgeToEdge(getWindow()); ...}Mặc định, enableEdgeToEdge() làm các system bar trong suốt (transparent). Ngoại lệ là chế độ điều hướng 3 nút: navigation bar khi đó được phủ một scrim bán trong suốt (translucent). Màu của các icon hệ thống và của scrim tự điều chỉnh theo theme sáng hoặc tối của hệ thống.
Nếu muốn bật edge-to-edge mà không dùng hàm enableEdgeToEdge(), xem Manually set up the edge-to-edge display.
Xử lý chồng lấn bằng insets
Phần tiêu đề “Xử lý chồng lấn bằng insets”Một số view trong app có thể bị vẽ phía sau các system bar, như trong hình 3.
Bạn xử lý chồng lấn bằng cách phản ứng với insets. Insets cho biết những phần nào của màn hình giao với system UI như navigation bar hay status bar. “Giao nhau” có thể là system UI hiển thị đè lên nội dung, nhưng cũng có thể là thông tin về vùng dành cho cử chỉ hệ thống.
Các loại insets liên quan đến việc hiển thị app edge-to-edge:
- System bars insets: phù hợp nhất cho các view bấm được và không được phép bị system bar che khuất.
- Display cutout insets: cho các vùng có thể bị khoét trên màn hình (tai thỏ, lỗ camera) do hình dạng thiết bị.
- System gesture insets: cho các vùng điều hướng bằng cử chỉ mà hệ thống dùng, có độ ưu tiên cao hơn app của bạn.
System bars insets
Phần tiêu đề “System bars insets”System bars insets là loại insets được dùng nhiều nhất. Chúng biểu diễn vùng mà system UI hiển thị nằm trên app của bạn theo trục Z. Dùng chúng để dịch chuyển hoặc thêm padding cho các view bấm được và không được phép bị system bar che khuất.
Ví dụ, floating action button (FAB) trong hình 3 bị navigation bar che mất một phần:

Hình 3. Navigation bar chồng lên FAB trong bố cục edge-to-edge.
Để tránh kiểu chồng lấn này ở cả chế độ cử chỉ lẫn chế độ nút, bạn tăng margin của view bằng getInsets(int) với WindowInsetsCompat.Type.systemBars().
Ví dụ sau cho thấy cách áp dụng system bars insets:
ViewCompat.setOnApplyWindowInsetsListener(fab) { v, windowInsets -> val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) // Áp insets làm margin cho view. Ở đây chỉ đặt các cạnh dưới, trái và phải, // nhưng bạn có thể áp bất kỳ cạnh nào phù hợp với bố cục của mình. // Cũng có thể cập nhật padding nếu hợp lý hơn. v.updateLayoutParams<MarginLayoutParams> { leftMargin = insets.left bottomMargin = insets.bottom rightMargin = insets.right }
// Trả về CONSUMED nếu bạn không muốn window insets tiếp tục // được truyền xuống các view con. WindowInsetsCompat.CONSUMED}ViewCompat.setOnApplyWindowInsetsListener(fab, (v, windowInsets) -> { Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); // Áp insets làm margin cho view. Ở đây chỉ đặt các cạnh dưới, trái và phải, // nhưng bạn có thể áp bất kỳ cạnh nào phù hợp với bố cục của mình. // Cũng có thể cập nhật padding nếu hợp lý hơn. MarginLayoutParams mlp = (MarginLayoutParams) v.getLayoutParams(); mlp.leftMargin = insets.left; mlp.bottomMargin = insets.bottom; mlp.rightMargin = insets.right; v.setLayoutParams(mlp);
// Trả về CONSUMED nếu bạn không muốn window insets tiếp tục // được truyền xuống các view con. return WindowInsetsCompat.CONSUMED;});Áp dụng cách này cho ví dụ ở hình 3, FAB không còn bị che ở chế độ nút, như trong hình 4:

Hình 4. Hết chồng lấn ở chế độ điều hướng bằng nút.
Chế độ điều hướng bằng cử chỉ cũng vậy, như trong hình 5:

Hình 5. Hết chồng lấn ở chế độ điều hướng bằng cử chỉ.
Display cutout insets
Phần tiêu đề “Display cutout insets”Một số thiết bị có display cutout (phần màn hình bị khoét). Thường cutout nằm ở đầu màn hình và nằm trong status bar. Khi xoay ngang, cutout có thể nằm ở cạnh dọc. Tuỳ nội dung app hiển thị, bạn nên thêm padding để tránh display cutout, vì mặc định app sẽ vẽ cả vào vùng cutout.
Ví dụ, nhiều màn hình app hiển thị một danh sách. Đừng để các item trong danh sách bị display cutout hoặc system bar che mất.
ViewCompat.setOnApplyWindowInsetsListener(binding.recyclerView) { v, insets -> val bars = insets.getInsets( WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout() ) v.updatePadding( left = bars.left, top = bars.top, right = bars.right, bottom = bars.bottom, ) WindowInsetsCompat.CONSUMED}ViewCompat.setOnApplyWindowInsetsListener(mBinding.recyclerView, (v, insets) -> { Insets bars = insets.getInsets( WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout() ); v.setPadding(bars.left, bars.top, bars.right, bars.bottom); return WindowInsetsCompat.CONSUMED;});Giá trị truyền vào getInsets là phép OR theo bit (logical or) của hai loại system bars và display cutout, nghĩa là lấy vùng hợp của cả hai.
Đặt clipToPadding cho RecyclerView để phần padding cuộn cùng với các item. Nhờ vậy item có thể đi ra phía sau system bar khi người dùng cuộn, như ví dụ sau:
<androidx.recyclerview.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" android:clipToPadding="false" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />System gesture insets
Phần tiêu đề “System gesture insets”System gesture insets là các vùng của cửa sổ mà cử chỉ hệ thống được ưu tiên hơn app. Các vùng này được tô màu cam trong hình 6:
Hình 6. System gesture insets.
Giống system bars insets, bạn tránh chồng lấn với system gesture insets bằng getInsets(int) với WindowInsetsCompat.Type.systemGestures().
Dùng các insets này để dịch chuyển hoặc thêm padding, đưa các view vuốt được ra xa mép màn hình. Các trường hợp hay gặp: bottom sheet, thao tác vuốt trong game, và carousel làm bằng ViewPager2.
Trên Android 10 trở lên, system gesture insets gồm một inset ở cạnh dưới cho cử chỉ về màn hình chính (home), và một inset ở cạnh trái, một ở cạnh phải cho cử chỉ quay lại (back):
Hình 7. Kích thước các system gesture insets.
Ví dụ sau cho thấy cách áp dụng system gesture insets:
ViewCompat.setOnApplyWindowInsetsListener(view) { view, windowInsets -> val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemGestures()) // Áp insets làm padding cho view. Ở đây đặt cả bốn cạnh, // tuỳ bố cục của bạn. Cũng có thể cập nhật margin nếu hợp lý hơn. view.updatePadding(insets.left, insets.top, insets.right, insets.bottom)
// Trả về CONSUMED nếu bạn không muốn window insets tiếp tục // được truyền xuống các view con. WindowInsetsCompat.CONSUMED}ViewCompat.setOnApplyWindowInsetsListener(view, (v, windowInsets) -> { Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemGestures()); // Áp insets làm padding cho view. Ở đây đặt cả bốn cạnh, // tuỳ bố cục của bạn. Cũng có thể cập nhật margin nếu hợp lý hơn. v.setPadding(insets.left, insets.top, insets.right, insets.bottom);
// Trả về CONSUMED nếu bạn không muốn window insets tiếp tục // được truyền xuống các view con. return WindowInsetsCompat.CONSUMED;});Material Components
Phần tiêu đề “Material Components”Nhiều Android Material Components dạng View (com.google.android.material) tự xử lý insets, gồm BottomAppBar, BottomNavigationView, NavigationRailView và NavigationView.
Riêng AppBarLayout không tự xử lý insets. Thêm android:fitsSystemWindows="true" để nó xử lý insets ở cạnh trên.
Cách xử lý insets với Material Components trong Compose xem ở Material Components in Compose.
Truyền insets tương thích ngược
Phần tiêu đề “Truyền insets tương thích ngược”Để ngừng truyền insets xuống các view con và tránh bị padding hai lần, bạn có thể “tiêu thụ” (consume) insets bằng hằng số WindowInsetsCompat.CONSUMED. Tuy nhiên, trên thiết bị chạy Android 10 (API level 29) trở về trước, sau khi một view trả về WindowInsetsCompat.CONSUMED thì insets không được truyền sang các view anh em (sibling) nữa, có thể gây chồng lấn ngoài ý muốn.

Hình 8. Ví dụ insets bị truyền sai. Trên Android 10 (API level 29) trở về trước, sau khi ViewGroup 1 tiêu thụ insets thì insets không được truyền sang view anh em, khiến TextView 2 bị navigation bar đè lên. Từ Android 11 (API level 30), insets được truyền sang view anh em đúng như mong đợi.
Để đảm bảo insets được truyền sang các view anh em trên mọi phiên bản Android mà app hỗ trợ, gọi ViewGroupCompat#installCompatInsetsDispatch trước khi tiêu thụ insets. Hàm này có từ AndroidX Core và Core-ktx 1.16.0-alpha01.
// Dùng id gán cho root view của layout, ví dụ R.id.mainval rootView = findViewById<View>(R.id.main)// Gọi trước khi tiêu thụ insetsViewGroupCompat.installCompatInsetsDispatch(rootView)// Dùng id gán cho root view của layout, ví dụ R.id.mainLinearLayout rootView = findViewById(R.id.main);// Gọi trước khi tiêu thụ insetsViewGroupCompat.installCompatInsetsDispatch(rootView);
Hình 9. Insets được truyền đúng sau khi gọi ViewGroupCompat#installCompatInsetsDispatch.
Immersive mode
Phần tiêu đề “Immersive mode”Có những nội dung xem toàn màn hình sẽ tốt hơn, cho người dùng trải nghiệm “đắm chìm” (immersive). Bạn ẩn các system bar cho immersive mode bằng WindowInsetsController và WindowInsetsControllerCompat:
val windowInsetsController = WindowCompat.getInsetsController(window, window.decorView)
// Ẩn system bar.windowInsetsController.hide(Type.systemBars())
// Hiện system bar.windowInsetsController.show(Type.systemBars())Window window = getWindow();WindowInsetsControllerCompat windowInsetsController = WindowCompat.getInsetsController(window, window.getDecorView());if (windowInsetsController == null) { return;}// Ẩn system bar.windowInsetsController.hide(WindowInsetsCompat.Type.systemBars());
// Hiện system bar.windowInsetsController.show(WindowInsetsCompat.Type.systemBars());Chi tiết cách làm tính năng này xem ở Hide system bars for immersive mode.
Màu icon trên system bar
Phần tiêu đề “Màu icon trên system bar”Gọi enableEdgeToEdge đảm bảo màu icon trên system bar được cập nhật khi theme của thiết bị thay đổi.
Khi chuyển sang edge-to-edge, có thể bạn cần tự cập nhật màu icon trên system bar để chúng tương phản với nền app. Ví dụ, để icon trên status bar có màu sáng:
WindowCompat.getInsetsController(window, window.decorView) .isAppearanceLightStatusBars = falseWindowCompat.getInsetsController(window, window.getDecorView()) .setAppearanceLightStatusBars(false);Lưu ý tên thuộc tính dễ gây nhầm: isAppearanceLightStatusBars = true nghĩa là status bar có giao diện sáng (nền sáng, icon tối). Đặt false thì icon sáng màu, hợp với nền tối.
Lớp bảo vệ system bar (system bar protection)
Phần tiêu đề “Lớp bảo vệ system bar (system bar protection)”Khi app target SDK 35 trở lên, edge-to-edge là bắt buộc. Status bar và navigation bar dạng cử chỉ của hệ thống trong suốt, còn navigation bar 3 nút thì bán trong suốt. Gọi enableEdgeToEdge để có hành vi tương tự trên các phiên bản cũ hơn.
Tuy vậy, mặc định của hệ thống không phải lúc nào cũng hợp với mọi trường hợp. Tham khảo hướng dẫn thiết kế system bar của Android và Edge-to-edge design để quyết định nên dùng system bar trong suốt hay bán trong suốt.
Tạo system bar trong suốt
Phần tiêu đề “Tạo system bar trong suốt”Để có status bar trong suốt: target Android 15 (SDK 35) trở lên, hoặc gọi enableEdgeToEdge() với tham số mặc định cho các phiên bản trước.
Để có navigation bar dạng cử chỉ trong suốt: target Android 15 trở lên, hoặc gọi enableEdgeToEdge() với tham số mặc định cho các phiên bản trước. Với navigation bar 3 nút, đặt Window.setNavigationBarContrastEnforced thành false, nếu không hệ thống sẽ phủ một scrim bán trong suốt.
Tạo system bar bán trong suốt
Phần tiêu đề “Tạo system bar bán trong suốt”Để tạo status bar bán trong suốt:
- Nâng dependency
androidx-corelên 1.16.0-beta01 trở lên. - Bọc XML layout trong
androidx.core.view.insets.ProtectionLayoutvà gán cho nó một ID. - Truy cập
ProtectionLayoutbằng code để đặt protection, chỉ định cạnh và mộtGradientProtectioncho status bar.
<androidx.core.view.insets.ProtectionLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/list_protection" android:layout_width="match_parent" android:layout_height="match_parent">
<ScrollView android:id="@+id/item_list" android:clipToPadding="false" android:layout_width="match_parent" android:layout_height="match_parent">
<!--items-->
</ScrollView>
</androidx.core.view.insets.ProtectionLayout>findViewById<ProtectionLayout>(R.id.list_protection) .setProtections( listOf( GradientProtection( WindowInsetsCompat.Side.TOP, // Lý tưởng nhất là màu nền của pane paneBackgroundColor ) ) )Đảm bảo giá trị ColorInt truyền vào GradientProtection khớp với màu nền của nội dung. Ví dụ, bố cục list-detail trên máy gập có thể dùng các GradientProtection khác màu nhau cho pane danh sách và pane chi tiết.
Không tạo navigation bar dạng cử chỉ bán trong suốt. Để tạo navigation bar 3 nút bán trong suốt, làm một trong hai cách:
- Nếu layout đã được bọc trong
ProtectionLayout, bạn có thể truyền thêm mộtColorProtectionhoặcGradientProtectionvào hàmsetProtections. Trước đó, đảm bảowindow.isNavigationBarContrastEnforced = false. - Nếu không, đặt
window.isNavigationBarContrastEnforced = true.
Các mẹo khác
Phần tiêu đề “Các mẹo khác”Một số mẹo thêm khi xử lý insets.
Cho nội dung cuộn hiển thị edge-to-edge
Phần tiêu đề “Cho nội dung cuộn hiển thị edge-to-edge”Kiểm tra item cuối cùng trong RecyclerView hoặc NestedScrollView không bị system bar che, bằng cách xử lý insets và đặt clipToPadding thành false.
Video sau là một RecyclerView khi tắt edge-to-edge (bên trái) và khi bật (bên phải):
Code mẫu xem ở phần Create dynamic lists with RecyclerView.
Cho dialog toàn màn hình hiển thị edge-to-edge
Phần tiêu đề “Cho dialog toàn màn hình hiển thị edge-to-edge”Để dialog toàn màn hình hiển thị edge-to-edge, gọi enableEdgeToEdge trên window của Dialog.
class MyAlertDialogFragment : DialogFragment() { override fun onStart(){ super.onStart() dialog?.window?.let { WindowCompat.enableEdgeToEdge(it) } } ...}public class MyAlertDialogFragment extends DialogFragment { @Override public void onStart() { super.onStart(); Dialog dialog = getDialog(); if (dialog != null) { Window window = dialog.getWindow(); if (window != null) { WindowCompat.enableEdgeToEdge(window); } } } ...}Tài liệu thêm
Phần tiêu đề “Tài liệu thêm”Blog
Thiết kế
Tài liệu khác
Video