Edge To Edge

android/skills/system/edge-to-edge

作者 android42dc2270e960Complete terms in LICENSE.txt7.7K 个星标收录于 2026年10月8日更新于 2026年10月8日仓库13天前更新

Use this skill to migrate your Jetpack Compose app to add adaptive edge-to-edge support and troubleshoot common issues. Use this skill to fix UI components (like buttons or lists) that are obscured by or overlapping with the navigation bar or status bar, fix IME insets, and fix system bar legibility.

AI 生成的概览

指导将 Android Jetpack Compose 应用迁移为自适应边到边显示,并修复系统栏、输入法和内边距问题。

功能
提供为 Android Jetpack Compose 应用添加边到边支持的分步流程:按 Activity 规划改动、调用 enableEdgeToEdge、设置 adjustResize,并应用窗口内边距。内容涵盖 Scaffold 与 Material 组件的内边距处理、自适应脚手架、带正误代码示例的输入法内边距模式、导航栏对比度与系统栏图标可读性、列表以及全屏对话框。最后给出核对清单,包括运行 Gradle 构建。
适用场景
适用于将 Jetpack Compose 应用迁移到边到边显示,或按钮、列表、文本输入框等界面组件被导航栏、状态栏或输入法遮挡、重叠的情况。也适用于系统栏图标在应用内容上难以辨认时。
运行要求
一个使用 Jetpack Compose 且目标 SDK 为 35 或更高的 Android 项目;不附带脚本,仅为说明文档。核对清单提到运行 ./gradlew build 进行验证。

Prerequisites

  • Project MUST use Android Jetpack Compose.
  • Project MUST target SDK 35 or later. If the SDK is lower than 35, increase the SDK to 35.

Step 1: plan

  1. Locate and analyze all Activity classes to detect which have existing edge-to-edge support. For every Activity without edge-to-edge, plan to make each Activity edge-to-edge.
  2. In each Activity, Locate and analyze all lists and FAB components to detect which have existing edge-to-edge support. For every component without edge-to-edge support, plan to make each of these components edge-to-edge.
  3. In each Activity, scan for TextField, OutlinedTextField, or BasicTextField. If found, then you MUST verify the IME doesn't hide the input field by following the IME section of this skill.

Step 2: add edge-to-edge support

  1. Add enableEdgeToEdge before setContent in onCreate in each Activity that does not already call enableEdgeToEdge.
  2. Add android:windowSoftInputMode="adjustResize" in the AndroidManifest.xml for all Activities that use a soft keyboard.

Step 3: apply insets

  • The app MUST apply system insets, or align content to rulers, so critical UI remains tappable. Choose only one method to avoid double padding:

    1. PREFERRED: When available, use Scaffolds and pass PaddingValues to the content lambda.
    kotlin
    Scaffold { innerPadding ->    // innerPadding accounts for system bars and any Scaffold components    LazyColumn(        modifier = Modifier            .fillMaxSize()            .consumeWindowInsets(innerPadding),        contentPadding = innerPadding    ) { /* Content */ }}
    <br />
    1. PREFERRED: When available, use the automatic inset handling or padding modifiers in material components.

      • Material 3 Components manages safe areas for its own components, including:
        • TopAppBar
        • SmallTopAppBar
        • CenterAlignedTopAppBar
        • MediumTopAppBar
        • LargeTopAppBar
        • BottomAppBar
        • ModalDrawerSheet
        • DismissibleDrawerSheet
        • PermanentDrawerSheet
        • ModalBottomSheet
        • NavigationBar
        • NavigationRail
      • For Material 2 Components, use the windowInsetsparameter to apply insets manually for BottomAppBar, TopAppBar and BottomNavigation. DO NOT apply padding to the parent container; instead, pass insets directly to the App Bar component. Applying padding to the parent container prevents the App Bar background from drawing into the system bar area. For example, for TopAppBar, choose only one of the following options:
        1. PREFERRED: TopAppBar(windowInsets = AppBarDefaults.topAppBarWindowInsets)
        2. TopAppBar(windowInsets = WindowInsets.systemBars.exclude(WindowInsets.navigationBars))
        3. TopAppBar(windowInsets = WindowInsets.systemBars.add(WindowInsets.captionBar))
    2. For components outside a Scaffold, use padding modifiers, such as Modifier.safeDrawingPadding() or Modifier.windowInsetsPadding(WindowInsets.safeDrawing).

      kotlin
      Box(    modifier = Modifier        .fillMaxSize()        .safeDrawingPadding()) {    Button(        onClick = {},        modifier = Modifier.align(Alignment.BottomCenter)    ) {        Text("Login")    }}
      <br />
    3. For deeply nested components with excessive padding, use WindowInsetsRulers (e.g. Modifier.fitInside(WindowInsetsRulers.SafeDrawing.current)). See the IME section for a code sample.

    4. When you need an element (e.g. a custom header or decorative scrim) to equal the dimensions of a system bar, use inset size modifiers (e.g. Modifier.windowInsetsTopHeight(WindowInsets.systemBars)). See the Lists section for a code sample.

Adaptive Scaffolds

  • NavigationSuiteScaffold manages safe areas for its own components, like the NavigationRail or NavigationBar. However, the adaptive scaffolds (e.g. NavigationSuiteScaffold, ListDetailPaneScaffold) don't propagate PaddingValues to their inner contents. You MUST apply insets to individual screens or components (e.g., list contentPadding or FAB padding) as described in Step 3 . DO NOT apply safeDrawingPadding or similar modifiers to the NavigationSuiteScaffold parent. This clips and prevents an edge-to-edge screen.

IME

  • For each Activity with a soft keyboard, check that android:windowSoftInputMode="adjustResize" is set in the AndroidManifest.xml. DO NOT use SOFT_INPUT_ADJUST_RESIZE because it is deprecated. Then, maintain focus on the input field. Choose one:
      1. PREFERRED: Add Modifier.fitInside(WindowInsetsRulers.Ime.current) to the content container. This is preferred over imePadding() because it reduces jank and extra padding caused by forgetting to consume insets upstream in the hierarchy.
      1. Add imePadding to the content container. The padding modifier MUST be placed before Modifier.verticalScroll(). Do NOT use Modifier.imePadding() if the parent already accounts for the IME with contentWindowInsets (e.g. contentWindowInsets = WindowInsets.safeDrawing). Doing so will cause double padding.

IMEs with Scaffolds code patterns

RIGHT

RIGHT because contentWindowInsets contains IME insets, which are passed to the content lambda as innerPadding.

kotlin
// RIGHTScaffold(contentWindowInsets = WindowInsets.safeDrawing) { innerPadding ->    Column(        modifier = Modifier            .padding(innerPadding)            .consumeWindowInsets(innerPadding)            .verticalScroll(rememberScrollState())    ) { /* Content */ }}
<br />

RIGHT because fitInside fits the content to the IME insets regardless of contentWindowInsets.

kotlin
// RIGHTScaffold() { innerPadding ->    Column(        modifier = Modifier            .padding(innerPadding)            .consumeWindowInsets(innerPadding)            .fitInside(WindowInsetsRulers.Ime.current)            .verticalScroll(rememberScrollState())    ) { /* Content */ }}
<br />

RIGHT because the default contentWindowInsets does not contain IME insets, and imePadding() applies IME insets:

kotlin
// RIGHTScaffold() { innerPadding ->    Column(        modifier = Modifier            .padding(innerPadding)            .consumeWindowInsets(innerPadding)            .imePadding()            .verticalScroll(rememberScrollState())    ) { /* Content */ }}
<br />
WRONG

WRONG because there will be excess padding when the IME opens. IME insets are applied twice, once with innerPadding, which contains IME insets from the passed contentWindowInsets values, and once with imePadding:

kotlin
// WRONGScaffold(contentWindowInsets = WindowInsets.safeDrawing) { innerPadding ->    Column(        modifier = Modifier            .padding(innerPadding)            .imePadding()            .verticalScroll(rememberScrollState())    ) { /* Content */ }}
<br />

WRONG because the IME will cover up the content. Scaffold's default contentWindowInsets does NOT contain IME insets.

kotlin
// WRONGScaffold() { innerPadding ->    Column(        modifier = Modifier            .padding(innerPadding)            .verticalScroll(rememberScrollState())    ) { /* Content */ }}
<br />

IMEs without Scaffolds code patterns

RIGHT

The following code samples WILL NOT cause excessive padding.

kotlin
// RIGHTBox(    // Insets consumed    modifier = Modifier.safeDrawingPadding() // or imePadding(), safeContentPadding(), safeGesturesPadding()) {    Column(        modifier = Modifier.imePadding()    ) { /* Content */ }}
<br />
kotlin
// RIGHTBox(    // Insets consumed    modifier = Modifier.windowInsetsPadding(WindowInsets.safeDrawing) // or WindowInsets.ime, WindowInsets.safeContent, WindowInsets.safeGestures) {    Column(        modifier = Modifier.imePadding()    ) { /* Content */ }}
<br />
kotlin
// RIGHTBox(    // Insets not consumed, but irrelevant due to fitInside    modifier = Modifier.padding(WindowInsets.safeDrawing.asPaddingValues()) // or WindowInsets.ime.asPaddingValues(), WindowInsets.safeContent.asPaddingValues(), WindowInsets.safeGestures.asPaddingValues()) {    Column(        modifier = Modifier            .fillMaxSize()            .fitInside(WindowInsetsRulers.Ime.current)    ) { /* Content */ }}
<br />
WRONG

The following code sample WILL cause excessive padding because IME insets are applied twice:

kotlin
// WRONGBox(    // Insets not consumed    modifier = Modifier.padding(WindowInsets.safeDrawing.asPaddingValues()) // or WindowInsets.ime.asPaddingValues(), WindowInsets.safeContent.asPaddingValues(), WindowInsets.safeGestures.asPaddingValues()) {    Column(        modifier = Modifier.imePadding()    ) { /* Content */ }}
<br />

Navigation Bar Contrast & System Bar Icons

  • If the Activity uses enableEdgeToEdge from WindowCompat, you MUST set isAppearanceLightNavigationBars and isAppearanceLightStatusBars to the inverse of the device theme for apps that support light and dark theme so the system bar icons are legible. It's recommended to do this in your theme file. DO NOT do this if the Activities use enableEdgeToEdge from ComponentActivity because it handles the icon colors automatically.

    kotlin
    // Only use if calling `enableEdgeToEdge` from `WindowCompat`.// Apply to your theme file.@Composablefun MyTheme(    darkTheme: Boolean = isSystemInDarkTheme(),    content: @Composable () -> Unit) {    val view = LocalView.current    if (!view.isInEditMode) {        SideEffect {            val window = (view.context as? Activity)?.window ?: return@SideEffect            val controller = WindowCompat.getInsetsController(window, view)
                // Dark icons for Light Mode (!darkTheme), Light icons for Dark Mode            controller.isAppearanceLightStatusBars = !darkTheme            controller.isAppearanceLightNavigationBars = !darkTheme        }    }
        MaterialTheme(content = content)}
    <br />
  • If any screen uses a Scaffold or a NavigationSuiteScaffold with a bottom bar (e.g., BottomAppBar, NavigationBar), set window.isNavigationBarContrastEnforced = false in the corresponding Activity for SDK 29+. This prevents the system from adding a translucent background to the navigation bar, verifying your bottom bar colors extend to the bottom of the screen.

Lists

  • Apply inset padding (like Scaffold's innerPadding) to the contentPadding parameter of scrollable components (e.g. LazyColumn, LazyRow). DO NOT apply it as a Modifier.padding() to the list's parent container, as this clips the content and prevents it from scrolling behind the system bars.
  • Create a translucent composable covering the system bar so that the icons are still legible.
kotlin
class SystemBarProtectionSnippets : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)
        // enableEdgeToEdge sets window.isNavigationBarContrastEnforced = true        // which is used to add a translucent scrim to three-button navigation        enableEdgeToEdge()
        setContent {            MyTheme {                // Main content                MyContent()
                // After drawing main content, draw status bar protection                StatusBarProtection()            }        }    }}
@Composableprivate fun StatusBarProtection(    color: Color = MaterialTheme.colorScheme.surfaceContainer,) {    Spacer(        modifier = Modifier            .fillMaxWidth()            .height(                with(LocalDensity.current) {                    (WindowInsets.statusBars.getTop(this) * 1.2f).toDp()                }            )            .background(                brush = Brush.verticalGradient(                    colors = listOf(                        color.copy(alpha = 1f),                        color.copy(alpha = 0.8f),                        Color.Transparent                    )                )            )    )}
<br />

Dialogs

If both the following conditions are true, then the Dialog is full screen and must be made edge-to-edge:

  1. The DialogProperties contains usePlatformDefaultWidth = false.
  2. The Dialog calls Modifier.fillMaxSize().

To make a full screen Dialog edge-to-edge, set decorFitsSystemWindows = false in the DialogProperties.

kotlin
Dialog(    onDismissRequest = { /* Handle dismiss */ },    properties = DialogProperties(        // 1. Allows the dialog to span the full width of the screen        usePlatformDefaultWidth = false,        // 2. Allows the dialog to draw behind status and navigation bars        decorFitsSystemWindows = false    )) { /* Content */ }
<br />

Checklist

  • [ ] Does every Activity call enableEdgeToEdge()?
  • [ ] Is adjustResize set in the AndroidManifest.xml?
  • [ ] Does every TextField, OutlinedTextField, or BasicTextField have a parent with imePadding(), fitInside, Modifier.safeDrawingPadding(), Modifier.safeContentPadding(), Modifier.safeGesturesPadding(), or contentWindowInsets set to WindowInsets.safeDrawing or WindowInsets.ime?
  • [] Does the first and last list item draw away from the system bars by passing insets to contentPadding?
  • [] Do FABs draw above the navigation bars by either being inside a Scaffold or by applying Modifier.safeDrawingPadding()?
  • [] Does the project build? Run ./gradlew build to be sure.

来源与署名

来源:android/skills位于system/edge-to-edge提交42dc227

许可证: Complete terms in LICENSE.txt

内容归原作者所有。SourceWeft 从公开仓库中收录这些内容。

举报或申请下架

更多来自 android/skills 的技能

Wear Compose M3

android

指导构建、更新和迁移 Wear OS Compose Material3 应用,并提供规范组件示例与版本规则。

Software Development7.7K13天前更新

Display Glasses With Jetpack Compose Glimmer

android

Provides guidelines for developing projected Android XR apps for display glasses using the Jetpack Compose Glimmer UI toolkit. This skill covers foundational Glimmer design principles, workflows for implementing Jetpack Compose Glimmer, and interaction models for the glasses form factor. Use this skill to build an Android XR Augmented Experience app with Jetpack Compose Glimmer that adheres to the Glimmer design system for optimized glasses styling.

待分类7.7K13天前更新

Play Billing Library Version Upgrade

android

指导将 Android 项目从旧版 Google Play 结算库升级到最新稳定版本。

Software Development7.7K13天前更新

Leanback To Compose Tv Migration

android

Provides instructions and architectural patterns for migrating Android TV applications from legacy Leanback UI Toolkit, Android Views, or Support Fragments to Jetpack Compose for TV (androidx.tv). Use this skill for Leanback to Compose migrations, including browse screen, settings screen, authentication screen, login screen, or video playback screen migrations, or when replacing BrowseSupportFragment, LeanbackSettingsFragment, PreferenceFragment, BaseLeanbackPreferenceFragmentCompat, VideoSupportFragment, GuidedStepSupportFragment, SearchSupportFragment, VerticalGridSupportFragment, Presenter, ArrayObjectAdapter, or CursorMapper with modern Compose equivalents, implementing immersive carousels with focus memory, Media3 video playback with PlayerSurface, or custom 10-foot hero layouts.

待分类7.7K13天前更新

Testing Setup

android

分析 Android 项目的测试配置,并安装测试框架、测试装置以及单元、UI、截图和端到端测试。

Software Development7.7K13天前更新

Android Profiler

android

指导 Android 性能分析:录制系统跟踪、堆转储和方法记录,并使用 Perfetto SQL 进行分析。

Software Development7.7K13天前更新