在视图中以无边框方式显示内容

尝试 Compose 方式
Jetpack Compose 是推荐用于 Android 的界面工具包。了解如何在 Compose 中实现边到边。

在搭载 Android 15 或更高版本的设备上,一旦您将目标平台设为 SDK 35 或更高版本,应用就会采用无边框画面。窗口通过在系统栏后面绘制,占据了显示屏的整个宽度和高度。系统栏包括状态栏、标题栏和导航栏。

许多应用都有顶部应用栏。顶部应用栏应延伸到屏幕顶部边缘,并显示在状态栏后面。(可选)当内容滚动时,顶部应用栏可以缩小到状态栏的高度。

许多应用还具有底部应用栏或底部导航栏。这些栏还应延伸到屏幕底部边缘,并显示在导航栏后面。否则,应用应在导航栏后面显示滚动内容。

图 1. 无边框布局中的系统栏。

在应用中实现边到边布局时,请注意以下几点:

  1. 启用无边框显示
  2. 实现自适应布局,以优化不同设备规格上的用户体验
  3. 处理任何视觉重叠
  4. 考虑在系统栏后面显示纱罩
状态栏背后的图片示例
图 2. 状态栏背后的图像示例。

启用无边框显示

如果您的应用以 SDK 35 或更高版本为目标平台,系统会自动为 Android 15 或更高版本的设备启用无边框。

如需在旧版 Android 上启用无边框设计,请在 Activity 的 onCreate 中手动调用 enableEdgeToEdge。

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

默认情况下,enableEdgeToEdge() 会使系统栏透明,但在三按钮导航模式下,导航栏会获得半透明的纱罩。系统图标和稀松布的颜色会根据系统浅色或深色主题进行调整。

如需在不使用 enableEdgeToEdge() 函数的情况下在应用中启用无边框显示,请参阅手动设置无边框显示。

使用边衬区处理重叠

应用的某些视图可能会在系统栏后面绘制,如图 3 所示。

您可以通过对边衬区做出响应来解决重叠问题,边衬区指定了屏幕的哪些部分与系统界面(例如导航栏或状态栏)相交。相交可能意味着显示在内容上方,但也可能向应用告知系统手势。

适用于无边框显示应用的边衬区类型包括:

  • 系统栏边衬区:最适合可点按且不得被系统栏遮挡的视图。

  • 刘海屏边衬区:适用于因设备形状而可能存在刘海屏的区域。

  • 系统手势边衬区:系统使用的手势导航区域,其优先级高于应用。

系统栏边衬区

系统栏边衬区是最常用的边衬区类型。它们表示系统界面在 Z 轴上显示在应用上方的区域。它们最适合用于移动或填充应用中可点按且不得被系统栏遮挡的视图。

例如,图 3 中的悬浮操作按钮 (FAB) 被导航栏部分遮挡:

已实现边到边,但导航栏覆盖了 FAB 的示例
图 3. 在边到边布局中,导航栏与悬浮操作按钮重叠。

为了避免在手势模式或按钮模式下出现这种视觉重叠,您可以使用 getInsets(int) 和 WindowInsetsCompat.Type.systemBars() 增加视图的外边距。

以下代码示例展示了如何实现系统栏边衬区:

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

如果您将此解决方案应用于图 3 所示的示例,则在按钮模式下不会出现视觉重叠,如图 4 所示:

半透明导航栏未覆盖 FAB
图 4. 解决按钮模式下的视觉重叠问题。

手势导航模式也是如此,如图 5 所示:

采用手势导航的无边框设计
图 5. 解决手势导航模式下的视觉重叠问题。

刘海屏边衬区

部分设备具有刘海屏。通常,刘海屏位于屏幕顶部,并包含在状态栏中。当设备屏幕处于横屏模式时,刘海屏可能位于垂直边缘。根据应用在屏幕上显示的内容,您应实现内边距以避开刘海屏,因为默认情况下,应用会在刘海屏中绘制。

例如,许多应用界面都会显示一个列表。请勿使用刘海屏或系统栏遮挡列表项。

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

通过对系统栏和刘海屏类型进行逻辑或运算,确定 WindowInsetsCompat 的值。

将 clipToPadding 设置为 RecyclerView,以便内边距随列表项一起滚动。这样,当用户滚动时,内容项就可以进入系统栏后面,如以下示例所示。

<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" />

系统手势边衬区

系统手势边衬区表示窗口中系统手势优先级高于应用的区域。这些区域在图 6 中以橙色显示:

系统手势边衬区示例
图 6. 系统手势边衬区。

与系统栏边衬区一样,您可以使用 getInsets(int) 和 WindowInsetsCompat.Type.systemGestures() 来避免与系统手势边衬区重叠。

使用这些边衬区可将可滑动视图移离边缘或在边缘处添加内边距。常见用例包括底部动作条、游戏中的滑动操作以及使用 ViewPager2 实现的轮播界面。

在 Android 10 或更高版本中,系统手势边衬区包含用于主屏幕手势的底部边衬区,以及用于返回手势的左侧和右侧边衬区:

系统手势边衬区测量示例
图 7. 系统手势边衬区测量。

以下代码示例展示了如何实现系统手势边衬区:

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

Material 组件

许多基于视图的 Android Material 组件 (com.google.android.material) 会自动处理边衬区,包括 BottomAppBar、BottomNavigationView、NavigationRailView 和 NavigationView

不过,AppBarLayout 不会自动处理 Inset。添加 android:fitsSystemWindows="true" 以处理顶部边衬区。

了解如何使用 Compose 中的 Material 组件处理 Insets。

向后兼容的边衬区调度

如需停止向子视图分派 Insets 并避免过度内边距,您可以使用 WindowInsetsCompat.CONSUMED 常量来使用 Insets。不过,在搭载 Android 10(API 级别 29 及更低级别)的设备上,调用 WindowInsetsCompat.CONSUMED 后,边衬区不会分派给同级视图,这可能会导致意外的视觉重叠。

损坏的边衬区调度示例
图 8. 损坏的边衬区调度示例。 在 Android 10(API 级别 29)及更低版本中,ViewGroup 1 消耗边衬区后,边衬区不会分派到同级视图,从而导致 TextView 2 与系统导航栏重叠。不过,在 Android 11(API 级别 30)及更高版本中,边衬区会按预期分派给同级视图。

如需确认在所有受支持的 Android 版本中,边衬区都会分派给同级视图,请在消耗边衬区之前使用 ViewGroupCompat#installCompatInsetsDispatch,该方法适用于 AndroidX Core 和 Core-ktx 1.16.0-alpha01 及更高版本。

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);
固定边衬区调度示例
图 9. 修复了调用 ViewGroupCompat#installCompatInsetsDispatch 后的边衬区调度问题。

沉浸模式

某些内容最适合在全屏模式下体验,这样可以为用户提供更具沉浸感的体验。您可以使用 WindowInsetsController 和 WindowInsetsControllerCompat 库隐藏系统栏以实现沉浸模式:

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

如需详细了解如何实现此功能,请参阅隐藏系统栏以实现沉浸模式。

系统栏图标

调用 enableEdgeToEdge 可确保系统栏图标颜色在设备主题更改时更新。

在采用无边框设计时,您可能需要手动更新系统栏图标颜色,使其与应用的背景形成对比。例如,如需创建浅色状态栏图标,请使用以下代码:

Kotlin

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

Java

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

系统栏保护

当应用以 SDK 35 或更高版本为目标平台时,系统会强制执行边到边。系统状态栏和手势导航栏是透明的,但三按钮导航栏是半透明的。调用 enableEdgeToEdge 以实现向后兼容。

不过,系统默认设置可能并不适用于所有用例。请参阅 Android 系统栏设计指南和全屏设计,确定是使用透明还是半透明系统栏。

创建透明系统栏

通过以 Android 15 (SDK 35) 或更高版本为目标平台,或通过使用早期版本的默认参数调用 enableEdgeToEdge(),创建透明状态栏。

通过以 Android 15 或更高版本为目标平台,或通过使用早期版本的默认参数调用 enableEdgeToEdge(),创建透明的手势导航栏。对于三按钮导航栏,请将 Window.setNavigationBarContrastEnforced 设置为 false,否则系统会应用半透明的薄纱层。

创建半透明系统栏

如需创建半透明状态栏,请执行以下操作:

  1. 将 androidx-core 依赖项更新为 1.16.0-beta01 或更高版本
  2. 将 XML 布局封装在 androidx.core.view.insets.ProtectionLayout 中,并分配一个 ID。
  3. 以编程方式访问 ProtectionLayout 以设置保护,并为状态栏指定侧边和 GradientProtection。

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

确保传入 GradientProtection 的 ColorInt 与内容背景匹配。例如,在可折叠设备上显示的列表详情布局可能具有不同颜色的不同 GradientProtections,分别用于列表面板和详情面板。

图 1. 不同颜色的渐变保护。

请勿创建半透明的手势导航栏。如需创建半透明的三按钮导航栏,请执行以下操作之一:

  • 如果您已将布局封装在 ProtectionView 中,则可以向 setProtections 方法传递额外的 ColorProtection 或 GradientProtection。在执行此操作之前,请确保 window.isNavigationBarContrastEnforced = false。
  • 否则,请设置 window.isNavigationBarContrastEnforced = true。

其他提示

处理 Inset 时的其他提示。

使滚动内容全屏显示

通过处理 Insets 并将 clipToPadding 设置为 false,检查 RecyclerView 或 NestedScrollView 中的最后一个列表项是否未被系统栏遮挡。

以下视频展示了停用(左侧)和启用(右侧)边到边显示的 RecyclerView:

如需查看代码示例,请参阅使用 RecyclerView 创建动态列表部分中的代码段。

将全屏对话框设为边到边

如需使全屏对话框采用边到边设计,请在 Dialog 上调用 enableEdgeToEdge。

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

其他资源

如需详细了解如何实现边到边,请参阅以下参考文档。

博客

设计

其他文档

视频