Pré-requisitos
- O projeto DEVE usar Android Jetpack Compose.
- O projeto DEVE ter como alvo SDK 35 ou superior. Se o SDK for inferior a 35, aumente o SDK para 35.
Passo 1: planejar
- Localize e analise todas as classes Activity para detectar quais já possuem suporte edge-to-edge. Para cada Activity sem edge-to-edge, planeje tornar cada Activity edge-to-edge.
- Em cada Activity, localize e analise todos os componentes de lista e FAB para detectar quais já possuem suporte edge-to-edge. Para cada componente sem suporte edge-to-edge, planeje tornar cada um desses componentes edge-to-edge.
- Em cada Activity, verifique a existência de
TextField,OutlinedTextFieldouBasicTextField. Se encontrado, você DEVE verificar se o IME não esconde o campo de entrada seguindo a seção IME desta skill.
Passo 2: adicionar suporte edge-to-edge
- Adicione
enableEdgeToEdgeantes desetContentemonCreateem cada Activity que ainda não chameenableEdgeToEdge. - Adicione
android:windowSoftInputMode="adjustResize"no AndroidManifest.xml para todas as Activities que usam teclado virtual.
Passo 3: aplicar insets
-
O app DEVE aplicar insets do sistema, ou alinhar o conteúdo às réguas, para que a UI crítica permaneça tocável. Escolha apenas um método para evitar padding duplicado:
- PREFERENCIAL: Quando disponível, use
Scaffolde passePaddingValuespara o lambda de conteúdo.
Scaffold { innerPadding -> // innerPadding considera as barras do sistema e quaisquer componentes do Scaffold LazyColumn( modifier = Modifier .fillMaxSize() .consumeWindowInsets(innerPadding), contentPadding = innerPadding ) { /* Conteúdo */ } }-
PREFERENCIAL: Quando disponível, use o tratamento automático de insets ou modificadores de padding em componentes material.
- Componentes Material 3 gerenciam áreas seguras para seus próprios componentes, incluindo:
TopAppBarSmallTopAppBarCenterAlignedTopAppBarMediumTopAppBarLargeTopAppBarBottomAppBarModalDrawerSheetDismissibleDrawerSheetPermanentDrawerSheetModalBottomSheetNavigationBarNavigationRail
- Para componentes Material 2, use o parâmetro
windowInsetspara aplicar insets manualmente paraBottomAppBar,TopAppBareBottomNavigation. NÃO aplique padding ao contêiner pai; em vez disso, passe os insets diretamente para o componente App Bar. Aplicar padding ao contêiner pai impede que o fundo da App Bar se estenda para a área das barras do sistema. Por exemplo, paraTopAppBar, escolha apenas uma das seguintes opções:- PREFERENCIAL:
TopAppBar(windowInsets = AppBarDefaults.topAppBarWindowInsets) TopAppBar(windowInsets = WindowInsets.systemBars.exclude(WindowInsets.navigationBars))TopAppBar(windowInsets = WindowInsets.systemBars.add(WindowInsets.captionBar))
- PREFERENCIAL:
- Componentes Material 3 gerenciam áreas seguras para seus próprios componentes, incluindo:
-
Para componentes fora de um Scaffold, use modificadores de padding, como
Modifier.safeDrawingPadding()ouModifier.windowInsetsPadding(WindowInsets.safeDrawing).Box( modifier = Modifier .fillMaxSize() .safeDrawingPadding() ) { Button( onClick = {}, modifier = Modifier.align(Alignment.BottomCenter) ) { Text("Login") } } -
Para componentes profundamente aninhados com padding excessivo, use
WindowInsetsRulers(ex.:Modifier.fitInside(WindowInsetsRulers.SafeDrawing.current)). Veja a seção IME para um exemplo de código. -
Quando você precisar que um elemento (ex.: um cabeçalho personalizado ou uma proteção decorativa) tenha as mesmas dimensões de uma barra do sistema, use modificadores de tamanho de insets (ex.:
Modifier.windowInsetsTopHeight(WindowInsets.systemBars)). Veja a seção Listas para um exemplo de código.
- PREFERENCIAL: Quando disponível, use
Scaffolds Adaptativos
NavigationSuiteScaffoldgerencia áreas seguras para seus próprios componentes, comoNavigationRailouNavigationBar. No entanto, os scaffolds adaptativos (ex.:NavigationSuiteScaffold,ListDetailPaneScaffold) não propagamPaddingValuespara seus conteúdos internos. Você DEVE aplicar insets a telas ou componentes individuais (ex.:contentPaddingda lista ou padding do FAB) conforme descrito no Passo 3. NÃO apliquesafeDrawingPaddingou modificadores semelhantes ao paiNavigationSuiteScaffold. Isso corta e impede uma tela edge-to-edge.
IME
- Para cada Activity com teclado virtual, verifique se
android:windowSoftInputMode="adjustResize"está definido no AndroidManifest.xml. NÃO useSOFT_INPUT_ADJUST_RESIZEporque está obsoleto. Em seguida, mantenha o foco no campo de entrada. Escolha uma:-
- PREFERENCIAL: Adicione
Modifier.fitInside(WindowInsetsRulers.Ime.current)ao contêiner de conteúdo. Isso é preferível aimePadding()porque reduz travamentos e padding extra causado por esquecer de consumir insets upstream na hierarquia.
- PREFERENCIAL: Adicione
-
- Adicione
imePaddingao contêiner de conteúdo. O modificador de padding DEVE ser colocado antes deModifier.verticalScroll(). NÃO useModifier.imePadding()se o pai já considera o IME comcontentWindowInsets(ex.:contentWindowInsets = WindowInsets.safeDrawing). Isso causará padding duplicado.
- Adicione
-
Padrões de código para IMEs com Scaffolds
CERTO
CERTO porque contentWindowInsets contém insets do IME, que são passados para o lambda de conteúdo como innerPadding.
// CERTO
Scaffold(contentWindowInsets = WindowInsets.safeDrawing) { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding)
.consumeWindowInsets(innerPadding)
.verticalScroll(rememberScrollState())
) { /* Conteúdo */ }
}
CERTO porque fitInside ajusta o conteúdo aos insets do IME independentemente de contentWindowInsets.
// CERTO
Scaffold() { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding)
.consumeWindowInsets(innerPadding)
.fitInside(WindowInsetsRulers.Ime.current)
.verticalScroll(rememberScrollState())
) { /* Conteúdo */ }
}
CERTO porque o contentWindowInsets padrão não contém insets do IME, e imePadding() aplica insets do IME:
// CERTO
Scaffold() { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding)
.consumeWindowInsets(innerPadding)
.imePadding()
.verticalScroll(rememberScrollState())
) { /* Conteúdo */ }
}
ERRADO
ERRADO porque haverá padding excessivo quando o IME abrir. Os insets do IME são aplicados duas vezes, uma vez com innerPadding, que contém insets do IME dos valores contentWindowInsets passados, e outra vez com imePadding:
// ERRADO
Scaffold(contentWindowInsets = WindowInsets.safeDrawing) { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding)
.imePadding()
.verticalScroll(rememberScrollState())
) { /* Conteúdo */ }
}
ERRADO porque o IME cobrirá o conteúdo. O contentWindowInsets padrão do Scaffold NÃO contém insets do IME.
// ERRADO
Scaffold() { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding)
.verticalScroll(rememberScrollState())
) { /* Conteúdo */ }
}
Padrões de código para IMEs sem Scaffolds
CERTO
Os seguintes exemplos de código NÃO causarão padding excessivo.
// CERTO
Box(
// Insets consumidos
modifier = Modifier.safeDrawingPadding() // ou imePadding(), safeContentPadding(), safeGesturesPadding()
) {
Column(
modifier = Modifier.imePadding()
) { /* Conteúdo */ }
}
// CERTO
Box(
// Insets consumidos
modifier = Modifier.windowInsetsPadding(WindowInsets.safeDrawing) // ou WindowInsets.ime, WindowInsets.safeContent, WindowInsets.safeGestures
) {
Column(
modifier = Modifier.imePadding()
) { /* Conteúdo */ }
}
// CERTO
Box(
// Insets não consumidos, mas irrelevantes devido a fitInside
modifier = Modifier.padding(WindowInsets.safeDrawing.asPaddingValues()) // ou WindowInsets.ime.asPaddingValues(), WindowInsets.safeContent.asPaddingValues(), WindowInsets.safeGestures.asPaddingValues()
) {
Column(
modifier = Modifier
.fillMaxSize()
.fitInside(WindowInsetsRulers.Ime.current)
) { /* Conteúdo */ }
}
ERRADO
O seguinte exemplo de código CAUSARÁ padding excessivo porque os insets do IME são aplicados duas vezes:
// ERRADO
Box(
// Insets não consumidos
modifier = Modifier.padding(WindowInsets.safeDrawing.asPaddingValues()) // ou WindowInsets.ime.asPaddingValues(), WindowInsets.safeContent.asPaddingValues(), WindowInsets.safeGestures.asPaddingValues()
) {
Column(
modifier = Modifier.imePadding()
) { /* Conteúdo */ }
}
Contraste da Barra de Navegação e Ícones das Barras do Sistema
-
Se a Activity usar
enableEdgeToEdgedeWindowCompat, você DEVE definirisAppearanceLightNavigationBarseisAppearanceLightStatusBarscomo o inverso do tema do dispositivo para apps que suportam tema claro e escuro, para que os ícones das barras do sistema sejam legíveis. Recomenda-se fazer isso no arquivo de tema. NÃO faça isso se as Activities usaremenableEdgeToEdgedeComponentActivity, porque isso gerencia as cores dos ícones automaticamente.// Use apenas se estiver chamando `enableEdgeToEdge` de `WindowCompat`. // Aplique ao seu arquivo de tema. @Composable fun 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) // Ícones escuros para Modo Claro (!darkTheme), Ícones claros para Modo Escuro controller.isAppearanceLightStatusBars = !darkTheme controller.isAppearanceLightNavigationBars = !darkTheme } } MaterialTheme(content = content) } -
Se alguma tela usar um
ScaffoldouNavigationSuiteScaffoldcom uma barra inferior (ex.:BottomAppBar,NavigationBar), definawindow.isNavigationBarContrastEnforced = falsena Activity correspondente para SDK 29+. Isso impede que o sistema adicione um fundo translúcido à barra de navegação, garantindo que as cores da sua barra inferior se estendam até a parte inferior da tela.
Listas
- Aplique o padding de insets (como
innerPaddingdoScaffold) ao parâmetrocontentPaddingde componentes roláveis (ex.:LazyColumn,LazyRow). NÃO aplique comoModifier.padding()ao contêiner pai da lista, pois isso corta o conteúdo e impede que ele role atrás das barras do sistema. - Crie um composable translúcido cobrindo a barra do sistema para que os ícones ainda sejam legíveis.
class SystemBarProtectionSnippets : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// enableEdgeToEdge define window.isNavigationBarContrastEnforced = true
// que é usado para adicionar uma proteção translúcida para navegação de três botões
enableEdgeToEdge()
setContent {
MyTheme {
// Conteúdo principal
MyContent()
// Após desenhar o conteúdo principal, desenhe a proteção da barra de status
StatusBarProtection()
}
}
}
}
@Composable
private 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
)
)
)
)
}
Diálogos
Se ambas as condições a seguir forem verdadeiras, o Diálogo é em tela cheia e deve ser tornado edge-to-edge:
- O
DialogPropertiescontémusePlatformDefaultWidth = false. - O Diálogo chama
Modifier.fillMaxSize().
Para tornar um Diálogo em tela cheia edge-to-edge, defina decorFitsSystemWindows = false nas DialogProperties.
Dialog(
onDismissRequest = { /* Lidar com dispensa */ },
properties = DialogProperties(
// 1. Permite que o diálogo cubra toda a largura da tela
usePlatformDefaultWidth = false,
// 2. Permite que o diálogo desenhe atrás das barras de status e navegação
decorFitsSystemWindows = false
)
) { /* Conteúdo */ }
Checklist
- Toda
ActivitychamaenableEdgeToEdge()? -
adjustResizeestá definido noAndroidManifest.xml? - Todo
TextField,OutlinedTextFieldouBasicTextFieldtem um pai comimePadding(),fitInside,Modifier.safeDrawingPadding(),Modifier.safeContentPadding(),Modifier.safeGesturesPadding(), oucontentWindowInsetsdefinido comoWindowInsets.safeDrawingouWindowInsets.ime? - O primeiro e o último item da lista são desenhados longe das barras do sistema passando insets para
contentPadding? - Os FABs são desenhados acima das barras de navegação, seja estando dentro de um Scaffold ou aplicando
Modifier.safeDrawingPadding()? - O projeto compila? Execute
./gradlew buildpara ter certeza.