---
title: "Picture-in-Picture no Android com Kotlin e Compose em 2026"
url: "https://kotlin.dev.br/blog/picture-in-picture-android-kotlin-compose-2026/"
markdown_url: "https://kotlin.dev.br/blog/picture-in-picture-android-kotlin-compose-2026.MD"
description: "Implemente Picture-in-Picture (PiP) no Android com Kotlin e Jetpack Compose: manifesto, enterPictureInPictureMode, parâmetros, Media3, gestos e erros comuns."
date: "2026-09-07"
author: "Karina Melo"
---

# Picture-in-Picture no Android com Kotlin e Compose em 2026

Implemente Picture-in-Picture (PiP) no Android com Kotlin e Jetpack Compose: manifesto, enterPictureInPictureMode, parâmetros, Media3, gestos e erros comuns.


**Resposta rápida:** use **Picture-in-Picture (PiP)** no Android quando o usuário precisa continuar assistindo ou acompanhando uma experiência contínua enquanto navega para outro app. Em Kotlin, declare suporte no manifesto com `android:supportsPictureInPicture="true"` e `configChanges` adequados, construa um `PictureInPictureParams` com aspect ratio e ações, chame `enterPictureInPictureMode(params)` no momento certo (botão explícito, home ou fim da Activity) e adapte a UI — especialmente com Jetpack Compose e [Media3/ExoPlayer](/blog/media3-exoplayer-compose-kotlin-2026/) — para um layout mínimo dentro da janela flutuante. PiP não é um “mini app genérico”: ele funciona melhor para vídeo, chamada, navegação turn-by-turn ou qualquer superfície cuja pausa quebra o valor da tarefa.

Em produtos brasileiros de educação, streaming interno, atendimento, telemedicina, delivery com mapa ao vivo e fintech com onboarding por vídeo, o usuário frequentemente precisa responder mensagem, abrir outro app ou conferir um documento sem perder o contexto. Sem PiP, a opção ruim vira gravar a tela, forçar landscape fullscreen ou manter um [foreground service](/blog/foreground-service-android-kotlin-2026/) só para “não morrer” — soluções mais caras e menos claras. Este guia mostra como implementar PiP de forma correta em 2026, com Compose, Media3, parâmetros, gestos, testes e checklist de produção.

Antes de fixar detalhes de API ou comportamento por OEM, consulte a [documentação oficial de Picture-in-Picture](https://developer.android.com/develop/ui/views/picture-in-picture). Fabricantes e versões do Android podem alterar tamanho mínimo, gestos e disponibilidade; projete degradação segura.

## Quando Picture-in-Picture é a escolha certa

Faça três perguntas:

1. **Há uma experiência contínua que o usuário quer manter visível?** (vídeo, chamada, rota, monitoramento)
2. **O valor cai se a Activity sair de primeiro plano sem uma janela pequena?**
3. **A UI cabe de forma útil em uma janela reduzida?**

Três “sim” → PiP. Se a resposta for “quero só manter trabalho em background”, prefira WorkManager, MediaSession ou foreground service — PiP é interface, não mecanismo de sobrevivência do processo.

| Cenário | PiP faz sentido? | Alternativa melhor |
|---|---|---|
| Player de curso / streaming | Sim | — |
| Chamada de vídeo | Sim | Telecom / ConnectionService quando for chamada de verdade |
| Navegação turn-by-turn | Sim | Widget ou notificação rica se o mapa não for central |
| Upload longo com progresso | Não | [Foreground service](/blog/foreground-service-android-kotlin-2026/) + notificação |
| Sync adiável | Não | [WorkManager](/blog/workmanager-kotlin-android-2026/) |
| Abrir deep link e voltar | Não | [App Links / deep links](/blog/app-links-deep-links-android-kotlin-2026/) |
| Atalho para tarefa rápida | Não | [App Shortcuts](/blog/app-shortcuts-android-kotlin-2026/) |

### Regra de produto

PiP é bom quando completa esta frase:

> “A pessoa precisa **continuar vendo isto** enquanto faz outra coisa.”

“Continuar processando no servidor” não é PiP. “Continuar assistindo a aula enquanto anota no Notion” é PiP.

## Manifesto e Activity preparados para PiP

Declare suporte na Activity que hospeda o player ou a superfície contínua:

```xml
<activity
    android:name=".PlayerActivity"
    android:exported="false"
    android:supportsPictureInPicture="true"
    android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
    android:resizeableActivity="true">
</activity>
```

Pontos importantes:

- `supportsPictureInPicture="true"` habilita o contrato com o sistema.
- `configChanges` evita recriação destrutiva ao entrar/sair de PiP; você ainda precisa reagir a mudanças de layout.
- `resizeableActivity="true"` é coerente com janelas multi-window e PiP em dispositivos modernos.
- Não declare PiP em toda Activity do app. Reserve para a tela que realmente continua útil em miniatura.

Se o app usa uma única Activity com Compose Navigation, a Activity raiz pode declarar suporte, mas só deve entrar em PiP quando o destino atual for elegível (player, chamada, mapa ao vivo). Entrar em PiP a partir de uma lista ou formulário gera uma janela vazia e reclamações.

## Construindo `PictureInPictureParams`

O coração da API moderna é `PictureInPictureParams`. Em Kotlin:

```kotlin
import android.app.PictureInPictureParams
import android.app.RemoteAction
import android.graphics.Rect
import android.util.Rational
import android.os.Build

fun buildPipParams(
    sourceRect: Rect?,
    aspectRatio: Rational = Rational(16, 9),
    actions: List<RemoteAction> = emptyList(),
    autoEnter: Boolean = true,
    seamlessResize: Boolean = true,
): PictureInPictureParams {
    val builder = PictureInPictureParams.Builder()
        .setAspectRatio(aspectRatio)
        .setActions(actions)

    if (sourceRect != null) {
        builder.setSourceRectHint(sourceRect)
    }

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
        builder.setAutoEnterEnabled(autoEnter)
        builder.setSeamlessResizeEnabled(seamlessResize)
    }

    return builder.build()
}
```

### Aspect ratio

Use a proporção real do conteúdo, não a da tela cheia. Vídeo 16:9, chamada 3:4 ou 9:16, mapa amplo conforme o layout. Ratio inválido ou extremo pode fazer o sistema rejeitar ou cortar mal a janela.

### `sourceRectHint`

Informe o retângulo do player na tela cheia. Isso melhora a animação de entrada: o sistema “encolhe” a partir da área do vídeo, em vez de um salto genérico. Em Compose, obtenha o bounds com `onGloballyPositioned` e converta para coordenadas de tela.

### Auto-enter (Android 12+)

Com `setAutoEnterEnabled(true)`, o app pode entrar em PiP automaticamente quando o usuário toca Home ou troca de app, desde que os params estejam atualizados. Isso evita o padrão antigo de interceptar `onUserLeaveHint()` em todas as versões — ainda útil como fallback, mas não é a história principal em 2026.

## Entrando em Picture-in-Picture no momento certo

Há três gatilhos comuns:

1. **Botão explícito** “PiP” / “Flutuante” no player.
2. **Saída da Activity** (Home, app switcher) com auto-enter habilitado.
3. **Evento de produto** (usuário aceita chamada enquanto está em outra tela do app e você promove a superfície).

Exemplo com botão e fallback:

```kotlin
fun Activity.enterPipOrExplain(params: PictureInPictureParams) {
    if (!packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)) {
        // Dispositivo sem suporte: esconda o botão ou explique a limitação
        return
    }

    val entered = enterPictureInPictureMode(params)
    if (!entered) {
        // Params inválidos, OEM bloqueou ou Activity não está em estado elegível
        // Registre analytics e mantenha o player em tela cheia
    }
}
```

Não chame `enterPictureInPictureMode` em `onCreate` nem em resposta a deep link frio. O usuário precisa estar em uma sessão ativa. Também não entre em PiP quando o conteúdo está pausado sem intenção de continuar: janela preta ou estática parece bug.

### `onUserLeaveHint` como fallback

Em APIs mais antigas, ainda é comum:

```kotlin
override fun onUserLeaveHint() {
    super.onUserLeaveHint()
    if (shouldEnterPipOnLeave && Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
        enterPictureInPictureMode(buildPipParams(sourceRectHint))
    }
}
```

Em Android 12+, prefira params com auto-enter atualizados continuamente enquanto o conteúdo elegível estiver ativo. Assim o sistema decide com mais previsibilidade.

## Adaptando Jetpack Compose para a janela PiP

Compose não muda a API de PiP, mas muda a responsabilidade da UI: a mesma hierarquia precisa viver bem em tela cheia e em miniatura.

Padrão prático:

```kotlin
var inPipMode by remember { mutableStateOf(false) }

DisposableEffect(Unit) {
    val activity = context.findActivity()
    val callback = object : Consumer<Boolean> {
        override fun accept(isInPip: Boolean) {
            inPipMode = isInPip
        }
    }
    // Em Activity: sobrescreva onPictureInPictureModeChanged e atualize um StateFlow/CompositionLocal
    onDispose { }
}

Scaffold { padding ->
    if (inPipMode) {
        PipPlayerSurface(
            player = player,
            modifier = Modifier.fillMaxSize()
        )
    } else {
        FullPlayerScreen(
            player = player,
            onEnterPip = { activity.enterPipOrExplain(params) },
            modifier = Modifier.padding(padding)
        )
    }
}
```

### O que esconder em PiP

Na janela pequena, remova ou compacte:

- top app bars, abas e navegação inferior;
- listas de episódios, comentários e recomendações;
- formulários, banners e CTAs promocionais;
- controles densos demais para o tamanho mínimo.

Mantenha:

- superfície de vídeo / mapa / câmera;
- play/pause e, se couber, próxima/anterior via `RemoteAction`;
- indicador de estado (ao vivo, mudo, reconectando).

Se o app já trata [edge-to-edge e WindowInsets](/blog/android-16-edge-to-edge-compose-kotlin-2026/), revise paddings: em PiP as barras do sistema e gestos mudam, e insets de tela cheia podem sobrar como espaço morto.

### Source rect em Compose

```kotlin
var playerRect by remember { mutableStateOf<Rect?>(null) }

AndroidView(
    factory = { ctx -> PlayerView(ctx).apply { this.player = exoPlayer } },
    modifier = Modifier
        .fillMaxWidth()
        .aspectRatio(16f / 9f)
        .onGloballyPositioned { coordinates ->
            val pos = coordinates.positionInWindow()
            val size = coordinates.size
            playerRect = Rect(
                pos.x.toInt(),
                pos.y.toInt(),
                (pos.x + size.width).toInt(),
                (pos.y + size.height).toInt()
            )
        }
)
```

Atualize `setPictureInPictureParams` sempre que o retângulo ou o ratio mudarem de forma relevante (rotação, troca de mídia, entrada em fullscreen imersivo).

## Integração com Media3 / ExoPlayer

PiP e Media3 andam juntos na maior parte dos apps de vídeo. O player deve:

1. Continuar a reprodução ao entrar em PiP (não pause no `onPause` cegamente).
2. Pausar ou liberar somente quando a sessão realmente terminar.
3. Integrar `MediaSession` para fones, controles de notificação e Android Auto quando aplicável.

Evite este erro clássico:

```kotlin
override fun onPause() {
    super.onPause()
    player.pause() // quebra PiP: a Activity pausa, mas o vídeo deveria seguir
}
```

Prefira distinguir estados:

```kotlin
override fun onPause() {
    super.onPause()
    if (!isInPictureInPictureMode) {
        player.pauseIfPolicyRequires()
    }
}
```

Ou, melhor, conduza pausa/play por política de produto + lifecycle de mídia, não por `onPause` genérico. O guia de [Media3 com Compose](/blog/media3-exoplayer-compose-kotlin-2026/) cobre criação, `release()` e `AndroidView`; aqui o ponto extra é **não tratar PiP como background descartável**.

Para áudio em background sem vídeo visível, MediaSession + notificação (e às vezes foreground service de mídia) costuma ser mais adequado do que uma janela PiP preta.

## RemoteActions: controles na janela

`PictureInPictureParams.setActions` aceita uma lista pequena de `RemoteAction` (play/pause, pular, encerrar chamada). Cada ação precisa de ícone, título acessível e `PendingIntent` seguro.

Boas práticas:

- limite-se a 2–3 ações essenciais;
- localize títulos (“Pausar”, “Próximo”, “Encerrar”);
- use `PendingIntent` imutável e explícito;
- atualize as ações quando o estado mudar (playing ↔ paused);
- não coloque ação destrutiva sem rótulo claro.

RemoteActions não substituem uma UI Compose rica; elas existem porque a janela PiP é pequena demais para a chrome completa do app.

## Ciclo de vida, multi-window e predictive back

Ao entrar em PiP, a Activity passa por mudanças de ciclo de vida semelhantes a multi-window. Consequências práticas:

- coroutines e coletores ligados à UI devem tolerar `onPause` sem cancelar o playback;
- analytics deve registrar `pip_enter` / `pip_exit` sem duplicar `screen_view`;
- se você usa [predictive back](/blog/predictive-back-android-compose-kotlin-2026/), defina se o gesto sai do fullscreen, fecha o player ou encerra PiP — documente a regra;
- em foldables e telas grandes, combine PiP com o pensamento de [layouts adaptativos / window size class](/blog/layouts-adaptativos-android-compose-window-size-class-2026/): o usuário pode já estar em multi-window sem precisar de PiP.

Também atualize params em `onPictureInPictureModeChanged`:

```kotlin
override fun onPictureInPictureModeChanged(
    isInPictureInPictureMode: Boolean,
    newConfig: Configuration
) {
    super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
    pipState.value = isInPictureInPictureMode
    if (isInPictureInPictureMode) {
        hideChrome()
    } else {
        restoreChrome()
    }
}
```

## Permissões, políticas e expectativas do Play

PiP em si não pede uma permissão perigosa clássica, mas o contexto do app pode:

- câmera/microfone em chamada — siga o guia de [permissões Android](/blog/permissoes-android-kotlin-2026/);
- notificação de mídia / chamada — canal correto e, quando aplicável, foreground service type honesto;
- overlay de outros tipos **não** é PiP: não misture com `SYSTEM_ALERT_WINDOW`.

Do ponto de vista de produto e store:

- não use PiP para anúncio flutuante;
- não force PiP em fluxos onde o usuário só quer sair;
- se o conteúdo exigir login e a sessão expirar em PiP, mostre estado mínimo e encerre com dignidade — não deixe uma janela autenticada “vazando” dados.

## Como testar Picture-in-Picture

Checklist mínimo de QA:

1. Botão PiP entra na janela com aspect ratio correto.
2. Home / app switcher entra via auto-enter quando elegível.
3. Conteúdo continua reproduzindo (ou mantém estado esperado).
4. Play/pause por RemoteAction funciona com processo sob pressão de memória.
5. Rotação e mudança de ratio (trailer 16:9 → story 9:16) atualizam params.
6. Sair de PiP restaura chrome sem duplicar ViewModel nem recriar `ExoPlayer`.
7. Deep link aberto em paralelo não “rouba” a Activity de forma confusa.
8. Dispositivo sem feature esconde a opção.
9. OEM com gestos diferentes: arrastar para fechar, expandir e pin não quebra o player.
10. Acessibilidade: ações nomeadas, contraste do frame mínimo, foco não preso fora da janela.

Automatizar 100% de PiP em emulador é limitado; cubra a lógica de params e estados com testes de unidade, e valide animação/gestos em dispositivo físico. Para regressão da tela cheia ao redor, mantenha a disciplina do cluster de testes Compose / instrumentados do site.

## Erros comuns

### Pausar o player em todo `onPause`

A Activity pausa ao entrar em PiP. Se você pausa a mídia ali, o recurso perde o sentido. Distinga PiP de background real.

### Aspect ratio “chutado”

Usar `Rational(1, 1)` para tudo gera letterbox feio ou rejeição. Derive do vídeo/mapa atual.

### Entrar em PiP a partir de tela errada

Lista, settings ou onboarding em PiP produzem janela inútil. Gateie pelo destino elegível.

### Esquecer `setPictureInPictureParams` atualizado

Auto-enter com params velhos (ratio antigo, actions de outro estado, sourceRect errado) gera animação ruim e controles incorretos.

### Tratar PiP como overlay infinito

Fechar PiP deve ser fácil. Não lute contra o gesto do sistema nem recrie a janela em loop.

### Misturar PiP com “app forever”

Se a necessidade é trabalho contínuo sem UI flutuante, use foreground service / MediaSession. PiP é para **ver**, não para burlar limites de background.

### Ignorar Compose chrome

Deixar TopAppBar, chips e listas visíveis na miniatura torna o conteúdo ilegível. Tenha um layout PiP dedicado.

## Checklist de produção

Antes de liberar PiP:

- [ ] manifesto declara suporte só na Activity correta;
- [ ] feature check esconde UI em dispositivos sem suporte;
- [ ] `PictureInPictureParams` usa ratio real e `sourceRectHint`;
- [ ] auto-enter habilitado onde a versão permitir;
- [ ] player não pausa só porque `onPause` disparou em PiP;
- [ ] layout Compose mínimo para a janela;
- [ ] RemoteActions limitadas, localizadas e atualizadas;
- [ ] analytics de enter/exit sem contagem duplicada;
- [ ] sessão expirada e erros de rede têm estado claro;
- [ ] saída de PiP restaura UI sem recriar o player;
- [ ] QA em dispositivo físico com gestos de fechar/expandir;
- [ ] política de produto não usa PiP para spam ou anúncio.

## Perguntas frequentes

### Picture-in-Picture funciona com Jetpack Compose?

Sim. Compose renderiza a UI; a Activity chama as APIs de PiP. O trabalho extra é reagir a `onPictureInPictureModeChanged`, simplificar o layout e não recriar o player na recomposição.

### Preciso de Media3 para usar PiP?

Não. PiP é do framework Android. Media3 é o caminho usual quando o conteúdo é vídeo/áudio. Chamadas, mapas e outras superfícies também podem usar PiP sem ExoPlayer.

### Qual a diferença entre PiP e multi-window?

Multi-window divide a tela entre apps ou Activities. PiP cria uma janela flutuante especial, tipicamente pequena, com regras próprias de actions e elegibilidade. Um app pode viver nos dois mundos, mas os layouts e o ciclo de vida precisam ser testados separadamente.

### Posso abrir PiP automaticamente ao tocar Home?

A partir do Android 12, sim, com `setAutoEnterEnabled(true)` e params atualizados enquanto o conteúdo elegível estiver ativo. Em versões anteriores, o padrão clássico envolve `onUserLeaveHint()`.

### Quantas RemoteActions posso exibir?

O sistema impõe um limite pequeno. Trate como espaço premium: play/pause e uma ação secundária costumam bastar. Não tente portar a toolbar inteira do app.

### PiP consome muita bateria?

A janela em si não é o vilão — o conteúdo contínuo (câmera, decoding de vídeo, GPS) é. Otimize o player, pause quando o usuário pausar, e não mantenha sensores ligados sem necessidade. Para trabalho adiável, volte ao [WorkManager](/blog/workmanager-kotlin-android-2026/).

## Conclusão

Picture-in-Picture é uma ponte entre “o usuário quer sair” e “o usuário ainda precisa ver o que estava fazendo”. A implementação sólida em Kotlin combina manifesto correto, `PictureInPictureParams` honestos, UI Compose reduzida, playback que sobrevive a `onPause` e disciplina de produto para não transformar PiP em gambiarra de background.

Comece pelo caso mais claro do seu app — em geral um player Media3 com botão PiP, auto-enter e layout mínimo. Meça se as pessoas expandem de volta, fecham rápido ou concluem a tarefa paralela sem perder o contexto. Se o recurso ajudar de verdade, expanda para chamada ou mapa; se a janela ficar ociosa, simplifique em vez de forçar presença flutuante.

Para fechar o cluster de mídia e ciclo de vida no Android, avance a partir deste guia para [Media3/ExoPlayer com Compose](/blog/media3-exoplayer-compose-kotlin-2026/), [foreground services](/blog/foreground-service-android-kotlin-2026/) e [notificações push com FCM](/blog/fcm-notificacoes-push-android-kotlin-compose-2026/) — cada um resolve um pedaço diferente do mesmo problema: continuar útil quando o app deixa de ocupar a tela inteira.
