A Splash Screen API chegou para padronizar uma das partes mais inconsistentes do Android histórico: a primeira tela do app. Durante anos, todo projeto resolvia o “tempo de inicialização” de um jeito diferente — uma Activity em branco com um logo, um Handler.postDelayed, às vezes uma biblioteca third-party — e o resultado era duplicação de cold start, animações truncadas e UX inconsistente entre fabricantes. A partir do Android 12 a plataforma passou a controlar o splash nativamente, e em 2026, com o Android 16 consolidando o SplashScreen da AndroidX Core como padrão, manter um splash legado virou dívida técnica com custo real.
Neste guia você implementa a Splash Screen API no Android 16 com Kotlin e Jetpack Compose, do zero à migração de um projeto existente, cobrindo duration, ícone adaptativo, windowBackground, animação de saída e testes. Se você está começando um app novo, vale revisar antes o tutorial de criação do primeiro app Android e o guia de Jetpack Compose, porque a integração do splash depende da configuração de tema e da raiz de navegação em Compose.
1. Por que não fazer um splash “na mão”
A tentação de criar uma SplashActivity dedicada ainda existe, mas o custo é alto e quase sempre invisível até virar bug:
- Cold start duplicado: a
Activityextra inflaciona layout, criaApplication-level DI, rodasetContente só então navega para a tela real. O usuário espera o custo de duas telas para ver uma. - Quebra em modo gelado / AOT: em primeiro launch após instalar ou atualizar, o Android já mostra o splash do sistema por cima do seu splash manual, gerando dois logotipos piscando.
- Comportamento inconsistente entre OEMs: Samsung, Xiaomi e Pixel renderizam
windowBackgroundde formas diferentes; a Splash Screen API normaliza isso. - Animação de saída não suportada: o splash manual não tem hook de “deixe a plataforma animar a transição para o seu primeiro frame Compose”.
- Acessibilidade: a API nativa respeita contraste, dimensão mínima do ícone e tempo máximo de exibição, coisas fáceis de errar numa implementação caseira.
A regra em 2026 é simples: deixe a plataforma desenhar o splash, use a AndroidX só para customizar.
2. Dependências
No libs.versions.toml (padrão que vale seguir — veja o guia de Gradle Version Catalog):
[versions]
androidxCoreSplashscreen = "1.0.1"
[libraries]
androidx-core-splashscreen = { group = "androidx.core", name = "core-splashscreen", version.ref = "androidxCoreSplashscreen" }
No app/build.gradle.kts:
dependencies {
implementation(libs.androidx.core.splashscreen)
}
A versão 1.0.1 é estável em 2026 e cobre API 21+ com backport do comportamento do Android 12. Em API 31+ o sistema desenha nativamente; em API mais baixa, a AndroidX simula o mesmo contrato a partir do tema.
3. O tema Theme.SplashScreen
O ponto central é um tema dedicado que herda de Theme.SplashScreen. Em res/values/themes.xml:
<style name="Theme.App.Starting" parent="Theme.SplashScreen">
<item name="windowSplashScreenBackground">@color/splash_background</item>
<item name="windowSplashScreenAnimatedIcon">@drawable/splash_icon</item>
<item name="windowSplashScreenAnimationDuration">200</item>
<item name="postSplashScreenTheme">@style/Theme.App</item>
</style>
Pontos críticos que erram em revisão de código:
postSplashScreenThemeé obrigatório. Sem ele o app inteiro herda o tema de splash, quebrando Cores,ColorSchemedo Compose Material 3 e tokens dewindowBackground.windowSplashScreenAnimatedIconaceita drawable vetorial ou adaptative icon. Ícones raster estouram em telas high-DPI e não animam.- A duração máxima útil é ~1000ms. Acima disso o sistema corta e o app parece travado. Para uma discussão mais ampla de performance de inicialização, o guia de performance em Kotlin cobre reduzir tempo de cold start além do splash.
- O ícone tem área de mascaramento. Use um
vector drawablecom 108dp onde o conteúdo visível fica num círculo de ~72dp centralizado; o sistema pode recortar as bordas.
No AndroidManifest.xml:
<application
android:name=".App"
android:theme="@style/Theme.App.Starting">
<activity
android:name=".MainActivity"
android:exported="true"
android:theme="@style/Theme.App.Starting">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
4. Instalar o splash na Activity
A chamada precisa acontecer antes de super.onCreate e antes de setContent. É a parte mais fácil de errar:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
val splash = installSplashScreen()
super.onCreate(savedInstanceState)
var contentReady = mutableStateOf(false)
splash.setKeepOnScreenCondition { !contentReady.value }
enableEdgeToEdge()
setContent {
AppRoot(contentReady = contentReady)
}
}
}
O setKeepOnScreenCondition é o substituto moderno do “segure o splash enquanto carrega dados”. Enquanto retornar true, o sistema mantém o splash visível; ao virar false, ele anima a saída e entrega o primeiro frame Compose. Esse padrão combina bem com edge-to-edge no Android 16, que agora é default.
Importante: não bloqueie a main thread dentro de setKeepOnScreenCondition. O callback roda no frame loop; carregamento real deve rodar em LaunchedEffect, viewModelScope ou Application.onCreate. Se você precisa inicializar DI, logging ou analytics antes da UI, mova para Application ou use um Startup Provider da AndroidX.
5. Animação de saída customizada
O splash padrão já faz fade-out, mas você pode adicionar uma animação de saída mais marcada com setOnExitAnimationListener:
splash.setOnExitAnimationListener { splashView ->
val iconView = splashView.iconView ?: run {
splashView.remove()
return@setOnExitAnimationListener
}
val scale = ObjectAnimator.ofFloat(
iconView, View.SCALE_X, 1f, 0.6f
).apply { duration = 150 }
val alpha = ObjectAnimator.ofFloat(
splashView.view, View.ALPHA, 1f, 0f
).apply { duration = 150 }
AnimatorSet().apply {
playTogether(scale, alpha)
doOnEnd { splashView.remove() }
start()
}
}
Duas armadilhas:
- Sempre chame
splashView.remove()no fim. Esquecer isso segura o splash para sempre e vira ANR em produção. - Não exceda ~300ms de animação de saída. O usuário já esperou o cold start; alongar a transição causa sensação de lentidão.
6. Splash no Android 16 especificamente
O Android 16 reforça duas coisas que vale revisar em apps em produção:
enableEdgeToEdge()é default, então o temaTheme.App.Startingprecisa considerarwindowLayoutInDisplayCutoutModeestatusBarColortransparentes, senão o splash mostra borda colorida que some abruptamente na UI real.- Modo gelado pré-aquecido: o sistema pode mostrar o splash antes mesmo do processo do app subir, com o ícone em silhueta. Use um ícone com contraste forte contra
windowSplashScreenBackgroundpara evitar um “fantasma” ilegível. - Suporte a tela inicial preenchida em tablets/dobráveis: o splash centraliza o ícone em qualquer tamanho de tela; layouts manuais costumam quebrar em
res/values-sw600dp.
Para apps que ainda têm windowBackground customizado, remova-o do tema base e migre para Theme.SplashScreen herdando o comportamento de fundo — isso evita flashes de cor diferente entre splash e UI.
7. Migração do splash legado
O caminho padrão para um app que ainda usa SplashActivity:
- Remova a
Activitylegada e o filtro deLAUNCHERdela. - Torne a
MainActivityreal a exportada comMAIN/LAUNCHER. - Aplique
Theme.App.StartingnaMainActivitye no<application>. - Chame
installSplashScreen()antes desuper.onCreate. - Mova qualquer
Handler.postDelayed/Thread.sleepparasetKeepOnScreenConditioncontrolado por estado real (carregamento de feature flags, sessão, etc.). - Apague layouts, drawables e bibliotecas de splash third-party.
- Rode o app em API 21, 31 e o emulador Android 16 para validar consistência.
Esse fluxo costuma reduzir o cold start percebido em 150–300ms e elimina uma classe inteira de bugs de OEM. Se você mantém Feature Flags no projeto, vale gatear a migração por flag para poder reverter rápido em caso de regressão visual.
8. Como medir o tempo de splash
O tempo percebido de splash não é o que você acha — é o que o usuário vê. Para medir corretamente:
- Use
adb shell am start -W com.seu.app/.MainActivitye observeWaitTimeeTotalTime. - Em instrumentação, capture
ActivityManager.ProcessStateao redor do primeiro frame Compose; o delta entreinstallSplashScreen()e o primeiro frame estável é o tempo real de splash. - Em produção, registre um evento
app_first_framecom duração desdeApplication.onCreate; agregue por versão de Android e modelo de dispositivo.
Não confie em cronômetro na mão. Dispositivos com thermal throttle, primeiro launch após OTA ou apps pesados em DI mostram tempos bem diferentes do dev machine.
9. Erros comuns em revisão de código
- Esquecer
postSplashScreenThemee quebrar o tema do app inteiro. - Chamar
installSplashScreen()depois desetContent— vira no-op silencioso. - Manter
windowDisablePreviewno tema, que desativa o splash do sistema. - Usar
windowSplashScreenAnimatedIconcom bitmap grande em vez de vector — estoura DPI e consome memória no cold start. - Animar saída sem chamar
splashView.remove(). - Pedir permissão de rede ou I/O dentro do callback
setKeepOnScreenCondition.
10. Quando NÃO usar splash
A Splash Screen API é para tempo de inicialização frio inevitável, não para “dar uma sensação de carregamento”. Se o seu app abre em menos de 300ms e você está segurando o splash artificialmente, está piorando a UX — remova-o e deixe a plataforma mostrar o mínimo necessário. Para carregamento de dados pesado, prefira skeleton screens na própria UI Compose; o tutorial de Jetpack Compose cobre estados de loading com Crossfade e AnimatedContent.
Conclusão
A Splash Screen API no Android 16 é o caminho correto para qualquer app Kotlin em 2026: padroniza o cold start, respeita OEMs, suporta animação de saída e se integra limpo com Compose e edge-to-edge. A migração de um splash legado é pequena (um tema, uma linha em onCreate, um callback de condição), mas remove uma fonte permanente de bugs visuais e reduz o tempo percebido de inicialização. A regra é deixar a plataforma desenhar o splash e só customizar o que agrega UX real — ícone da marca, cor de fundo, animação de saída curta. Para o resto, gaste energia em baixar o cold start de verdade, que é onde a melhoria de UX acontece.