A biblioteca Picture-in-picture (PiP) do Jetpack oferece uma solução simplificada e robustez para que desenvolvedores de apps Android implementem a funcionalidade PiP, principalmente para apps de reprodução de mídia, comunicação por vídeo e navegação. Ao fornecer uma API unificada, a biblioteca ajuda a eliminar o código boilerplate, bugs comuns no app e melhorar a qualidade geral da experiência do usuário com o PiP.
A biblioteca PiP Jetpack facilita as APIs PiP atuais ao resolver vários desafios e inconsistências importantes em todo o ecossistema Android:
- Fragmentação do SO: a biblioteca processa automaticamente as diferenças nas chamadas da API PiP em várias versões do Android, como o uso de
enterPictureInPictureModeantes do Android 12 eisAutoEnterEnableddepois. Assim, os desenvolvedores não precisam gerenciar as diferenças de versão. - Parâmetros PiP incorretos: oferece uma solução unificada para definir corretamente
parâmetros PiP, por exemplo,
setSourceRectHint, para criar animações suaves e de alta qualidade durante a reprodução de mídia. - Callbacks de estado PiP unificados: consolida
onPictureInPictureModeChangedeonPictureInPictureUiStateChangedem uma única interface de callback unificada (PictureInPictureDelegate.OnPictureInPictureEventListener) para gerenciamento simplificado de estado e UI. - Redução do código boilerplate: a biblioteca reduz a quantidade de código repetitivo e
boilerplate ao oferecer conjuntos predefinidos de
RemoteActionspara casos de uso comuns, como controles de reprodução e ações de videochamada. - Preparação para o futuro: mais recursos de PiP são fornecidos pela biblioteca Jetpack, permitindo que os usuários acessem funcionalidades adicionais com pouco ou nenhum esforço.
Workflow de migração
Identifique a categoria de caso de uso do app e a lógica PiP legada:
Categorias:Reprodução de vídeo, Navegação ou Videochamada.
Lógica legada de PiP para identificar:
onUserLeaveHintsetAutoEnterEnabledonPictureInPictureModeChangedonPictureInPictureUiStateChangedsetPictureInPictureParams.
2. Configuração do AndroidManifest
Verifique se a atividade que entra no modo picture-in-picture declara suporte em AndroidManifest.xml com
o configChanges necessário para evitar reinicializações desnecessárias:
<activity
android:name="VideoActivity" android:supportsPictureInPicture="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation">
</activity>
3. Configuração do ambiente
Adicione as dependências necessárias ao build.gradle:
dependencies {
implementation("androidx.core:core:1.18.0")
implementation("androidx.activity:activity:1.13.0")
implementation("androidx.core:core-pip:1.0.0-alpha02") }
Use as bibliotecas AndroidX mais recentes para as dependências e consulte a página de lançamentos para mais informações.
4. Seleção e inicialização de modelos
Escolha o modelo de implementação que melhor se adapta ao caso de uso do app:
- Navegação e videochamada:
BasicPictureInPicture; o redimensionamento contínuo geralmente não é compatível, e você não precisa de uma dica de retângulo de origem. - Reprodução de vídeo:
VideoPlaybackPictureInPicture; rastreia automaticamente os limites da visualização do player para a dica de retângulo de origem e permite o redimensionamento contínuo por padrão.
Para adotar a biblioteca Jetpack, substitua a implementação personalizada de PiP pelas APIs da biblioteca Jetpack. A complexidade e o custo da adoção variam de acordo com a implementação atual do app.
As seções a seguir descrevem alguns dos casos de uso típicos do PiP e as etapas de implementação necessárias:
Navegação
O app informa à biblioteca o estado ativo ou inativo da navegação e define a proporção. A biblioteca Jetpack cuida do restante.
Principais diferenças:
- Não é necessário diferenciar a entrada automática e a entrada legada no lado do app.
- Interfaces de callback consolidadas.
- Novo criador de
PictureInPictureParamspara compatibilidade com versões anteriores.
Videochamada
O app informa à biblioteca o estado ativo ou inativo da chamada e define a proporção da tela.
Principais diferenças:
- Não é necessário diferenciar a entrada automática e a entrada legada no lado do app.
- Interfaces de callback consolidadas.
- Novo criador de
PictureInPictureParamspara compatibilidade com versões anteriores. - Ícones de ação padronizados para videochamadas.
5. Migração de código
- Lógica de entrada:substitua a lógica específica da API, como
setAutoEnterEnabledpara Android 12 e versões mais recentes ouonUserLeaveHintpara Android 11 e versões anteriores comsetEnabled. Acione isso sempre que o status de qualificação para o PiP mudar. - Callbacks:consolide
onPictureInPictureModeChanged(alternância de layout) eonPictureInPictureUiStateChanged(animação/estados) em um callback unificado baseado em eventosonPictureInPictureEvent. - Ações e parâmetros:atualize os parâmetros usando
setActionsesetAspectRationa instância do modelo sempre que eles mudarem.
Padrões de implementação de referência
Exemplos de implementações.
Navegação e videochamada
class NavOrVideoCallJpipActivity : ComponentActivity(), PictureInPictureDelegate.OnPictureInPictureEventListener { private lateinit var pictureInPictureImpl: BasicPictureInPicture override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) pictureInPictureImpl = BasicPictureInPicture(this) // BasicPictureInPicture is ideal for Navigation and Video call use cases. pictureInPictureImpl.addOnPictureInPictureEventListener( ContextCompat.getMainExecutor(this), this ) setContent { } } override fun onPictureInPictureEvent( event: PictureInPictureDelegate.Event, config: Configuration? ) { when (event) { PictureInPictureDelegate.Event.ENTERED -> { /* Toggle to PiP layout */ } PictureInPictureDelegate.Event.EXITED -> { /* Toggle to Full-screen layout */ } PictureInPictureDelegate.Event.STASHED -> { /* Optional: PiP is stashed */ } PictureInPictureDelegate.Event.UNSTASHED -> { /* Optional: PiP is unstashed */ } } } }
Reprodução de vídeo
class VideoPlaybackJpipActivity : ComponentActivity(), PictureInPictureDelegate.OnPictureInPictureEventListener { private lateinit var pictureInPictureImpl: VideoPlaybackPictureInPicture override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) pictureInPictureImpl = VideoPlaybackPictureInPicture(this) pictureInPictureImpl.addOnPictureInPictureEventListener( ContextCompat.getMainExecutor(this), this ) setContent { ContentScreen(pictureInPictureImpl) } } override fun onPictureInPictureEvent( event: PictureInPictureDelegate.Event, config: Configuration? ) { when (event) { PictureInPictureDelegate.Event.ENTER_ANIMATION_START -> { /* Hide overlays */ } PictureInPictureDelegate.Event.ENTER_ANIMATION_END -> { /* Animation finished */ } PictureInPictureDelegate.Event.ENTERED -> { /* Switch to PiP layout */ } PictureInPictureDelegate.Event.STASHED -> { /* PiP stashed */ } PictureInPictureDelegate.Event.UNSTASHED -> { /* PiP unstashed */ } PictureInPictureDelegate.Event.EXITED -> { /* Return to full-screen */ } } } @Composable fun ContentScreen(pipController: VideoPlaybackPictureInPicture) { DisposableEffect(pipController) { onDispose { pipController.close() } } } }