A biblioteca do Jetpack Picture-in-Picture (PiP) oferece uma solução simplificada e robusta para que os 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 do PiP.
A biblioteca do Jetpack PiP facilita as APIs PiP atuais, abordando vários desafios e inconsistências importantes no ecossistema Android:
- Fragmentação do SO: a biblioteca processa automaticamente as diferenças nas chamadas de API PiP
em várias versões do Android, como o uso de
enterPictureInPictureModeantes do Android 12 eisAutoEnterEnableddepois, para que os desenvolvedores não precisem gerenciar diferenças de versão. - Parâmetros PiP incorretos: ela oferece uma solução unificada para definir corretamente
os 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: ela consolida
onPictureInPictureModeChangedeonPictureInPictureUiStateChangedem uma única interface de callback unificada (PictureInPictureDelegate.OnPictureInPictureEventListener) para simplificar o estado e o gerenciamento da interface. - Redução de código boilerplate: a biblioteca reduz a quantidade de código boilerplate repetitivo, oferecendo conjuntos predefinidos de
RemoteActionspara casos de uso comuns, como controles de reprodução e ações de videochamada. - Preparação para o futuro: outros recursos do PiP são fornecidos pela biblioteca do Jetpack, permitindo que os usuários acessem funcionalidades extras com o mínimo 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 PiP legada a ser identificada :
onUserLeaveHintsetAutoEnterEnabledonPictureInPictureModeChangedonPictureInPictureUiStateChangedsetPictureInPictureParams.
2. Configuração do AndroidManifest
Verifique se a atividade que entra no PiP declara suporte no AndroidManifest.xml com as configChanges necessárias 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 mais recentes do AndroidX para as dependências e consulte a página de versões 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 normalmente 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 ativa o redimensionamento contínuo por padrão.
Para adotar a biblioteca do Jetpack, substitua a implementação PiP personalizada atual pelas APIs da biblioteca do 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 a biblioteca sobre o estado ativo ou inativo da navegação e define a proporção. A biblioteca do 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 builder
PictureInPictureParamspara oferecer compatibilidade com versões anteriores.
Videochamada
O app informa a biblioteca sobre o estado ativo ou inativo da chamada e define a proporção.
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 builder
PictureInPictureParamspara oferecer compatibilidade com versões anteriores. - Ícones de ação padronizados para videochamada.
5. Migração de código
- Lógica de entrada: substitua a lógica específica da API, como
setAutoEnterEnabledpara o Android 12 e versões mais recentes ouonUserLeaveHintpara o Android 11 e versões anteriores porsetEnabled. Acione isso sempre que o status de qualificação do 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. - Processamento especial de vídeo:para apps de vídeo, use
setPlayerViewpara automatizar atualizações de dicas de retângulo de origem e garantir transições suaves. ` ### 6. Limpeza
Para VideoPlaybackPictureInPicture, chame close em
onDispose ou onDestroy para liberar recursos como rastreadores de visualização.
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() } } } }