Hiển thị nội dung tràn viền trong thành phần hiển thị

Thử cách dùng Compose
Jetpack Compose là bộ công cụ giao diện người dùng được khuyên dùng cho Android. Tìm hiểu cách sử dụng chế độ tràn viền trong Compose.

Sau khi bạn nhắm đến SDK 35 trở lên trên một thiết bị chạy Android 15 trở lên, ứng dụng của bạn sẽ hiển thị tràn viền. Cửa sổ trải rộng trên toàn bộ chiều rộng và chiều cao của màn hình bằng cách vẽ phía sau các thanh hệ thống. Thanh hệ thống bao gồm thanh trạng thái, thanh chú thích và thanh điều hướng.

Nhiều ứng dụng có thanh ứng dụng trên cùng. Thanh ứng dụng trên cùng phải kéo dài đến cạnh trên cùng của màn hình và hiển thị phía sau thanh trạng thái. Bạn có thể thu nhỏ thanh ứng dụng trên cùng xuống chiều cao của thanh trạng thái khi nội dung cuộn.

Nhiều ứng dụng cũng có thanh ứng dụng dưới cùng hoặc thanh điều hướng dưới cùng. Các thanh này cũng phải kéo dài đến cạnh dưới cùng của màn hình và hiển thị phía sau thanh điều hướng. Nếu không, ứng dụng sẽ hiển thị nội dung cuộn phía sau thanh điều hướng.

Hình 1. Thanh hệ thống trong bố cục tràn viền.

Khi triển khai bố cục tràn viền trong ứng dụng, hãy lưu ý những điều sau:

  1. Bật chế độ hiển thị tràn viền
  2. Triển khai bố cục thích ứng để tối ưu hoá trải nghiệm người dùng trên nhiều hệ số hình dạng
  3. Xử lý mọi phần chồng chéo trực quan
  4. Cân nhắc việc hiển thị lớp phủ mờ phía sau các thanh hệ thống
ví dụ về hình ảnh phía sau thanh trạng thái
Hình 2. Ví dụ về hình ảnh phía sau thanh trạng thái.

Bật chế độ hiển thị tràn viền

Nếu ứng dụng của bạn nhắm đến SDK 35 trở lên, thì chế độ tràn viền sẽ tự động được bật cho các thiết bị Android 15 trở lên.

Để bật chế độ tràn viền trên các phiên bản Android trước đó, hãy gọi enableEdgeToEdge theo cách thủ công trong onCreate của Activity.

Kotlin

 override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         WindowCompat.enableEdgeToEdge(window)
        ...
      }

Java

 @Override
      protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        WindowCompat.enableEdgeToEdge(getWindow());
        ...
      }

Theo mặc định, enableEdgeToEdge() sẽ làm cho các thanh hệ thống trong suốt, ngoại trừ ở chế độ thao tác bằng 3 nút, thanh điều hướng sẽ có một lớp phủ mờ. Màu của các biểu tượng hệ thống và lớp phủ mờ được điều chỉnh dựa trên giao diện sáng hoặc tối của hệ thống.

Để bật chế độ hiển thị tràn viền trong ứng dụng mà không cần dùng hàm enableEdgeToEdge(), hãy xem phần Thiết lập chế độ hiển thị tràn viền theo cách thủ công.

Xử lý phần chồng chéo bằng phần lồng ghép

Một số khung hiển thị của ứng dụng có thể vẽ phía sau các thanh hệ thống, như minh hoạ trong hình 3.

Bạn có thể giải quyết phần chồng chéo bằng cách phản hồi phần lồng ghép. Phần lồng ghép này chỉ định những phần nào của màn hình giao nhau với Giao diện người dùng hệ thống, chẳng hạn như thanh điều hướng hoặc thanh trạng thái. Giao nhau có thể có nghĩa là hiển thị phía trên nội dung, nhưng cũng có thể thông báo cho ứng dụng của bạn về các cử chỉ hệ thống.

Các loại phần lồng ghép áp dụng cho việc hiển thị ứng dụng của bạn ở chế độ tràn viền là:

  • Phần lồng ghép thanh hệ thống: phù hợp nhất cho các khung hiển thị có thể nhấn và không được các thanh hệ thống che khuất về mặt hình ảnh.

  • Phần lồng ghép vết cắt trên màn hình: dành cho các khu vực có thể có vết cắt trên màn hình do hình dạng của thiết bị.

  • Phần lồng ghép cử chỉ hệ thống: dành cho các khu vực điều hướng bằng cử chỉ do hệ thống sử dụng, có mức độ ưu tiên cao hơn ứng dụng của bạn.

Phần lồng ghép thanh hệ thống

Phần lồng ghép thanh hệ thống là loại phần lồng ghép thường được dùng nhất. Chúng đại diện cho khu vực mà giao diện người dùng hệ thống hiển thị trong trục Z phía trên ứng dụng của bạn. Chúng được dùng tốt nhất để di chuyển hoặc thêm khoảng đệm cho các khung hiển thị trong ứng dụng của bạn có thể nhấn và không được các thanh hệ thống che khuất về mặt hình ảnh.

Ví dụ: nút hành động nổi (FAB) trong hình 3 bị thanh điều hướng che khuất một phần:

ví dụ về chế độ hiển thị tràn viền đã được triển khai, nhưng thanh điều hướng đang che nút FAB
Hình 3. Thanh điều hướng chồng lên FAB trong bố cục tràn viền.

Để tránh loại phần chồng chéo trực quan này ở chế độ cử chỉ hoặc chế độ nút, bạn có thể tăng lề của khung hiển thị bằng cách sử dụng getInsets(int) với WindowInsetsCompat.Type.systemBars().

Ví dụ về mã sau đây cho thấy cách triển khai phần lồng ghép thanh hệ thống:

Kotlin

ViewCompat.setOnApplyWindowInsetsListener(fab) { v, windowInsets ->
  val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
  // Apply the insets as a margin to the view. This solution sets
  // only the bottom, left, and right dimensions, but you can apply whichever
  // insets are appropriate to your layout. You can also update the view padding
  // if that's more appropriate.
  v.updateLayoutParams<MarginLayoutParams> {
      leftMargin = insets.left
      bottomMargin = insets.bottom
      rightMargin = insets.right
  }

  // Return CONSUMED if you don't want the window insets to keep passing
  // down to descendant views.
  WindowInsetsCompat.CONSUMED
}

Java

ViewCompat.setOnApplyWindowInsetsListener(fab, (v, windowInsets) -> {
  Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
  // Apply the insets as a margin to the view. This solution sets only the
  // bottom, left, and right dimensions, but you can apply whichever insets are
  // appropriate to your layout. You can also update the view padding if that's
  // more appropriate.
  MarginLayoutParams mlp = (MarginLayoutParams) v.getLayoutParams();
  mlp.leftMargin = insets.left;
  mlp.bottomMargin = insets.bottom;
  mlp.rightMargin = insets.right;
  v.setLayoutParams(mlp);

  // Return CONSUMED if you don't want the window insets to keep passing
  // down to descendant views.
    return WindowInsetsCompat.CONSUMED;
});

Nếu bạn áp dụng giải pháp này cho ví dụ minh hoạ trong hình 3, thì sẽ không có phần chồng chéo trực quan nào ở chế độ nút, như minh hoạ trong hình 4:

thanh điều hướng trong mờ không che FAB
Hình 4. Giải quyết phần chồng chéo trực quan ở chế độ nút mode.

Điều tương tự cũng áp dụng cho chế độ điều hướng bằng cử chỉ, như minh hoạ trong hình 5:

tràn viền có chế độ điều hướng bằng cử chỉ
Hình 5. Giải quyết phần chồng chéo trực quan ở chế độ thao tác bằng cử chỉ.

Phần lồng ghép vết cắt trên màn hình

Một số thiết bị có vết cắt trên màn hình. Thông thường, vết cắt nằm ở đầu màn hình và được đưa vào thanh trạng thái. Khi màn hình thiết bị ở chế độ ngang, vết cắt có thể nằm ở cạnh dọc. Tuỳ thuộc vào nội dung mà ứng dụng của bạn hiển thị trên màn hình, bạn nên triển khai khoảng đệm để tránh vết cắt trên màn hình, vì theo mặc định, ứng dụng sẽ vẽ trong vết cắt trên màn hình.

Ví dụ: nhiều màn hình ứng dụng hiển thị danh sách các mục. Không che khuất các mục trong danh sách bằng vết cắt trên màn hình hoặc các thanh hệ thống.

Kotlin

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
}

Java

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;
});

Xác định giá trị của WindowInsetsCompat bằng cách lấy or logic của các thanh hệ thống và các loại vết cắt trên màn hình.

Đặt clipToPadding thành RecyclerView để khoảng đệm cuộn cùng với các mục trong danh sách. Điều này cho phép các mục nằm phía sau các thanh hệ thống khi người dùng cuộn, như minh hoạ trong 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" />

Phần lồng ghép cử chỉ hệ thống

Phần lồng ghép cử chỉ hệ thống đại diện cho các khu vực của cửa sổ mà cử chỉ hệ thống có mức độ ưu tiên cao hơn ứng dụng của bạn. Các khu vực này được hiển thị bằng màu cam trong hình 6:

Ví dụ về phần lồng ghép cử chỉ hệ thống
Hình 6. Phần lồng ghép cử chỉ hệ thống.

Giống như phần lồng ghép thanh hệ thống, bạn có thể tránh chồng chéo phần lồng ghép cử chỉ hệ thống bằng cách sử dụng getInsets(int) với WindowInsetsCompat.Type.systemGestures().

Sử dụng các phần lồng ghép này để di chuyển hoặc thêm khoảng đệm cho các khung hiển thị có thể vuốt ra khỏi các cạnh. Các trường hợp sử dụng phổ biến bao gồm trang tính dưới cùng, vuốt trong trò chơi và băng chuyền được triển khai bằng ViewPager2.

Trên Android 10 trở lên, phần lồng ghép cử chỉ hệ thống chứa một phần lồng ghép dưới cùng cho cử chỉ trang chủ và một phần lồng ghép bên trái và bên phải cho các cử chỉ quay lại:

ví dụ về các phép đo phần lồng ghép cử chỉ hệ thống
Hình 7. Số đo phần lồng ghép cử chỉ hệ thống.

Ví dụ về mã sau đây cho thấy cách triển khai phần lồng ghép cử chỉ hệ thống:

Kotlin

ViewCompat.setOnApplyWindowInsetsListener(view) { view, windowInsets ->
    val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemGestures())
    // Apply the insets as padding to the view. Here, set all the dimensions
    // as appropriate to your layout. You can also update the view's margin if
    // more appropriate.
    view.updatePadding(insets.left, insets.top, insets.right, insets.bottom)

    // Return CONSUMED if you don't want the window insets to keep passing down
    // to descendant views.
    WindowInsetsCompat.CONSUMED
}

Java

ViewCompat.setOnApplyWindowInsetsListener(view, (v, windowInsets) -> {
    Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemGestures());
    // Apply the insets as padding to the view. Here, set all the dimensions
    // as appropriate to your layout. You can also update the view's margin if
    // more appropriate.
    view.setPadding(insets.left, insets.top, insets.right, insets.bottom);

    // Return CONSUMED if you don't want the window insets to keep passing down
    // to descendant views.
    return WindowInsetsCompat.CONSUMED;
});

Thành phần Material

Nhiều thành phần Material Android dựa trên khung hiển thị (com.google.android.material) tự động xử lý phần lồng ghép, bao gồm BottomAppBar, BottomNavigationView, NavigationRailViewNavigationView

Tuy nhiên, AppBarLayout không tự động xử lý phần lồng ghép. Thêm android:fitsSystemWindows="true" để xử lý phần lồng ghép trên cùng.

Đọc cách xử lý phần lồng ghép bằng Material Components trong Compose.

Gửi phần lồng ghép tương thích ngược

Để ngừng gửi phần lồng ghép đến các khung hiển thị con và tránh thêm quá nhiều khoảng đệm, bạn có thể sử dụng phần lồng ghép bằng WindowInsetsCompat.CONSUMED hằng số. Tuy nhiên, trên các thiết bị chạy Android 10 (cấp độ API 29 trở xuống), phần lồng ghép không được gửi đến các phần tử đồng cấp sau khi gọi WindowInsetsCompat.CONSUMED, điều này có thể gây ra phần chồng chéo trực quan không mong muốn.

Ví dụ về việc gửi phần lồng ghép bị lỗi
Hình 8. Ví dụ về việc gửi phần lồng ghép bị lỗi. Phần lồng ghép không gửi đến các khung hiển thị cùng cấp sau khi ViewGroup 1 sử dụng phần lồng ghép trên Android 10 (cấp độ API 29) trở xuống, khiến TextView 2 chồng lên thanh điều hướng hệ thống. Tuy nhiên, phần lồng ghép được gửi đến các khung hiển thị cùng cấp trên Android 11 (cấp độ API 30) trở lên, như dự kiến.

Để xác nhận rằng phần lồng ghép được gửi đến các phần tử cùng cấp cho tất cả các phiên bản Android được hỗ trợ, hãy sử dụng ViewGroupCompat#installCompatInsetsDispatch trước khi sử dụng phần lồng ghép, có sẵn trên AndroidX Core và Core-ktx 1.16.0-alpha01 trở lên.

Kotlin

// Use the i.d. assigned to your layout's root view, e.g. R.id.main
val rootView = findViewById(R.id.main)
// Call before consuming insets
ViewGroupCompat.installCompatInsetsDispatch(rootView)

Java

// Use the i.d. assigned to your layout's root view, e.g. R.id.main
LinearLayout rootView = findViewById(R.id.main);
// Call before consuming insets
ViewGroupCompat.installCompatInsetsDispatch(rootView);
Ví dụ về việc gửi phần lồng ghép cố định
Hình 9. Gửi phần lồng ghép cố định sau khi gọi ViewGroupCompat#installCompatInsetsDispatch.

Chế độ hiển thị tối đa

Một số nội dung được trải nghiệm tốt nhất ở chế độ toàn màn hình, mang đến cho người dùng trải nghiệm phong phú hơn. Bạn có thể ẩn các thanh hệ thống cho chế độ hiển thị tối đa bằng cách sử dụng WindowInsetsControllerWindowInsetsControllerCompat thư viện:

Kotlin

val windowInsetsController =
      WindowCompat.getInsetsController(window, window.decorView)

// Hide the system bars.
windowInsetsController.hide(Type.systemBars())

// Show the system bars.
windowInsetsController.show(Type.systemBars())

Java

Window window = getWindow();
WindowInsetsControllerCompat windowInsetsController =
      WindowCompat.getInsetsController(window, window.getDecorView());
if (windowInsetsController == null) {
    return;
  }
// Hide the system bars.
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars());

// Show the system bars.
windowInsetsController.show(WindowInsetsCompat.Type.systemBars());

Tham khảo phần Ẩn thanh hệ thống cho chế độ hiển thị tối đa để biết thêm thông tin về cách triển khai tính năng này.

Biểu tượng thanh hệ thống

Việc gọi enableEdgeToEdge đảm bảo màu biểu tượng thanh hệ thống cập nhật khi giao diện thiết bị thay đổi.

Trong khi chuyển sang chế độ tràn viền, bạn có thể cần cập nhật màu biểu tượng thanh hệ thống theo cách thủ công để chúng tương phản với nền của ứng dụng. Ví dụ: để tạo biểu tượng thanh trạng thái sáng:

Kotlin

WindowCompat.getInsetsController(window, window.decorView)
    .isAppearanceLightStatusBars = false

Java

WindowCompat.getInsetsController(window, window.getDecorView())
    .setAppearanceLightStatusBars(false);

Bảo vệ thanh hệ thống

Sau khi ứng dụng của bạn nhắm đến SDK 35 trở lên, chế độ tràn viền sẽ được thực thi. Thanh trạng thái hệ thống và thanh điều hướng bằng cử chỉ có màu trong suốt, nhưng thanh điều hướng bằng 3 nút có màu trong mờ. Gọi enableEdgeToEdge để làm cho tính năng này tương thích ngược.

Tuy nhiên, chế độ mặc định của hệ thống có thể không phù hợp cho một số trường hợp sử dụng. Tham khảo hướng dẫn thiết kế thanh hệ thống AndroidThiết kế tràn viền để xác định xem có nên sử dụng thanh hệ thống trong suốt hay trong mờ hay không.

Tạo thanh hệ thống trong suốt

Tạo thanh trạng thái trong suốt bằng cách nhắm đến Android 15 (SDK 35) trở lên hoặc bằng cách gọi enableEdgeToEdge() với các đối số mặc định cho các phiên bản trước đó.

Tạo thanh điều hướng bằng cử chỉ trong suốt bằng cách nhắm đến Android 15 trở lên hoặc bằng cách gọi enableEdgeToEdge() với các đối số mặc định cho các phiên bản trước đó. Đối với thanh điều hướng bằng 3 nút, hãy đặt Window.setNavigationBarContrastEnforced thành false, nếu không, một lớp phủ mờ sẽ được áp dụng.

Tạo thanh hệ thống trong mờ

Để tạo thanh trạng thái trong mờ, hãy làm như sau:

  1. Cập nhật phần phụ thuộc androidx-core lên 1.16.0-beta01 trở lên
  2. Bọc bố cục XML trong androidx.core.view.insets.ProtectionLayout và chỉ định một mã nhận dạng.
  3. Truy cập theo lập trình vào ProtectionLayout để đặt các biện pháp bảo vệ, chỉ định phía và một GradientProtection cho thanh trạng thái.

<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,
                // Ideally, this is the pane's background color
                paneBackgroundColor
            )
        )
    )

Đảm bảo ColorInt được truyền vào GradientProtection khớp với nền nội dung. Ví dụ: bố cục danh sách-chi tiết hiển thị trên thiết bị có thể gập lại có thể có GradientProtections với nhiều màu khác nhau cho bảng điều khiển danh sách và bảng điều khiển chi tiết.

Hình 1. Bảo vệ bằng màu chuyển sắc.

Không tạo thanh điều hướng bằng cử chỉ trong mờ. Để tạo thanh điều hướng bằng 3 nút trong mờ, hãy thực hiện một trong những thao tác sau:

  • Nếu đã bọc bố cục trong ProtectionView, bạn có thể truyền thêm ColorProtection hoặc GradientProtection vào phương thức setProtections. Trước khi thực hiện, hãy đảm bảo window.isNavigationBarContrastEnforced = false.
  • Nếu không, hãy đặt window.isNavigationBarContrastEnforced = true.

Các mẹo khác

Các mẹo khác khi xử lý phần lồng ghép.

Chuyển nội dung cuộn sang chế độ tràn viền

Kiểm tra để đảm bảo mục cuối cùng trong danh sách không bị các thanh hệ thống che khuất trong RecyclerView hoặc NestedScrollView bằng cách xử lý phần lồng ghép và đặt clipToPadding thành false.

Video sau đây cho thấy RecyclerView với chế độ hiển thị tràn viền bị tắt (bên trái) và bật (bên phải):

Xem các đoạn mã trong phần Tạo danh sách động bằng RecyclerView để biết mã mẫu.

Chuyển hộp thoại toàn màn hình sang chế độ tràn viền

Để chuyển hộp thoại toàn màn hình sang chế độ tràn viền, hãy gọi enableEdgeToEdge trên Hộp thoại.

Kotlin

class MyAlertDialogFragment : DialogFragment() {
    override fun onStart(){
        super.onStart()
        dialog?.window?.let { WindowCompat.enableEdgeToEdge(it) }
    }
    ...
}

Java

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 nguyên khác

Hãy xem các tài liệu tham khảo sau để biết thêm thông tin về cách chuyển sang chế độ tràn viền.

Blog

Thiết kế

Tài liệu khác

Video