Usare la funzionalità Picture in picture (PIP)

Prova Compose
Jetpack Compose è il toolkit UI consigliato per Android. Scopri come supportare la funzionalità Picture in picture in Compose.

A partire da Android 8.0 (livello API 26), Android consente alle attività di essere avviate in modalità Picture in picture (PIP). PIP è un tipo speciale di modalità multi-finestra utilizzata per la riproduzione video, le videochiamate e la navigazione. Consente all'utente di bloccare la finestra dell'attività esistente in un angolo dello schermo mentre naviga tra le app o sfoglia i contenuti nella schermata principale.

PIP sfrutta le API multi-finestra rese disponibili in Android 7.0 per fornire la finestra di overlay video bloccata. Per aggiungere PIP alla tua app, devi registrare le attività che supportano PIP, passare l'attività alla modalità PIP in base alle esigenze e assicurarti che gli elementi UI siano nascosti e che la riproduzione video continui quando l'attività è in modalità PIP.

La finestra PIP viene visualizzata nel livello più alto dello schermo, in un angolo scelto dal sistema.

PIP è supportato anche sui dispositivi Android TV OS compatibili con Android 14 (livello API 34) o versioni successive. Sebbene ci siano molte somiglianze, è necessario fare ulteriori considerazioni quando si utilizza PIP su TV.

In che modo gli utenti possono interagire con la finestra PIP

Gli utenti possono trascinare la finestra PIP in un'altra posizione. A partire da Android 12, gli utenti possono anche:

  • Toccare una volta la finestra per visualizzare un pulsante di attivazione/disattivazione a schermo intero, un pulsante di chiusura, un pulsante delle impostazioni e le azioni personalizzate fornite dalla tua app (ad esempio, i controlli di riproduzione).

  • Toccare due volte la finestra per alternare le dimensioni PIP correnti e le dimensioni PIP massime o minime. Ad esempio, se tocchi due volte una finestra ingrandita, questa viene ridotta a icona e viceversa.

  • Nascondere la finestra trascinandola verso il bordo sinistro o destro. Per ripristinare la finestra, tocca la parte visibile della finestra nascosta o trascinala verso l'esterno.

  • Ridimensionare la finestra PIP utilizzando il gesto di pizzicamento per lo zoom.

La tua app controlla quando l'attività corrente entra in modalità PIP. Ecco alcuni esempi:

  • Un'attività può entrare in modalità PIP quando l'utente tocca il pulsante Home o scorre verso l'alto fino alla schermata Home. In questo modo, Google Maps continua a mostrare le indicazioni stradali mentre l'utente esegue un'altra attività contemporaneamente.

  • La tua app può spostare un video in modalità PIP quando l'utente torna indietro dal video per sfogliare altri contenuti.

  • La tua app può passare un video in modalità PIP mentre un utente guarda la fine di un episodio di contenuti. La schermata principale mostra informazioni promozionali o di riepilogo sull'episodio successivo della serie.

  • La tua app può fornire agli utenti un modo per mettere in coda contenuti aggiuntivi mentre guardano un video. La riproduzione del video continua in modalità PIP mentre la schermata principale mostra un'attività di selezione dei contenuti.

Dichiarare il supporto di PIP

Per impostazione predefinita, il sistema non supporta automaticamente PIP per le app. Se vuoi supportare PIP nella tua app, registra la tua attività video nel file manifest impostando android:supportsPictureInPicture su true. Inoltre, specifica che l'attività gestisce le modifiche alla configurazione del layout in modo che non venga riavviata quando si verificano modifiche al layout durante le transizioni in modalità PIP.

<activity android:name="VideoActivity"
    android:supportsPictureInPicture="true"
    android:configChanges=
        "screenSize|smallestScreenSize|screenLayout|orientation"
    ...

Implementare il PIP con Jetpack

Utilizza la libreria Jetpack Picture in picture per implementare l'esperienza Picture in picture, in quanto semplifica l'integrazione e riduce i problemi comuni all'interno dell'app. Consulta la nostra app di esempio della piattaforma per vedere un esempio del suo utilizzo. Tuttavia, se preferisci implementare la funzionalità PIP utilizzando le API della piattaforma, consulta la documentazione seguente.

Passare l'attività a PIP

A partire da Android 12, puoi passare l'attività alla modalità PIP impostando il setAutoEnterEnabled flag su true. Con questa impostazione, un'attività passa automaticamente alla modalità PIP in base alle esigenze senza dover chiamare esplicitamente enterPictureInPictureMode() in onUserLeaveHint. Inoltre, questo offre il vantaggio aggiuntivo di transizioni molto più fluide. Per maggiori dettagli, consulta Rendere più fluide le transizioni alla modalità PIP dalla navigazione tramite gesti.

Se hai come target Android 11 o versioni precedenti, un'attività deve chiamare enterPictureInPictureMode() per passare alla modalità PIP. Ad esempio, il seguente codice passa un'attività alla modalità PIP quando l'utente fa clic su un pulsante dedicato nell'UI dell'app:

Kotlin

override fun onActionClicked(action: Action) {
    if (action.id.toInt() == R.id.lb_control_picture_in_picture) {
        activity?.enterPictureInPictureMode()
        return
    }
}

Java

@Override
public void onActionClicked(Action action) {
    if (action.getId() == R.id.lb_control_picture_in_picture) {
        getActivity().enterPictureInPictureMode();
        return;
    }
    ...
}

Potresti voler includere una logica che passa un'attività in modalità PIP anziché in background. Ad esempio, Google Maps passa alla modalità PIP se l'utente preme il pulsante Home o Recenti mentre l'app sta navigando. Puoi intercettare questo caso sostituendo onUserLeaveHint():

Kotlin

override fun onUserLeaveHint() {
    if (iWantToBeInPipModeNow()) {
        enterPictureInPictureMode()
    }
}

Java

@Override
public void onUserLeaveHint () {
    if (iWantToBeInPipModeNow()) {
        enterPictureInPictureMode();
    }
}

Consigliato: offrire agli utenti un'esperienza di transizione PIP ottimizzata

Android 12 ha aggiunto miglioramenti estetici significativi alle transizioni animate tra le finestre a schermo intero e PIP. Ti consigliamo vivamente di implementare tutte le modifiche applicabili. Una volta eseguita questa operazione, queste modifiche vengono scalate automaticamente a schermi di grandi dimensioni come pieghevoli e tablet senza ulteriori interventi.

Se la tua app non include gli aggiornamenti applicabili, le transizioni PIP sono comunque funzionali, ma le animazioni sono meno ottimizzate. Ad esempio, la transizione dalla modalità a schermo intero alla modalità PIP può causare la scomparsa della finestra PIP durante la transizione prima che venga visualizzata di nuovo al termine della transizione.

Queste modifiche riguardano quanto segue.

  • Rendere più fluide le transizioni alla modalità PIP dalla navigazione tramite gesti
  • Impostare un sourceRectHint appropriato per l'ingresso e l'uscita dalla modalità PIP
  • Disattivare il ridimensionamento senza interruzioni per i contenuti non video

Fai riferimento all'esempio Android Kotlin PictureInPicture come riferimento per l'attivazione di un'esperienza di transizione ottimizzata.

Rendere più fluide le transizioni alla modalità PIP dalla navigazione tramite gesti

A partire da Android 12, il setAutoEnterEnabled flag fornisce un'animazione molto più fluida per la transizione ai contenuti video in modalità PIP utilizzando la navigazione tramite gesti, ad esempio quando scorri verso l'alto fino alla schermata Home dalla modalità a schermo intero.

Completa i seguenti passaggi per apportare questa modifica:

  1. Utilizza setAutoEnterEnabled per creare PictureInPictureParams.Builder:

    Kotlin

    setPictureInPictureParams(PictureInPictureParams.Builder()
        .setAspectRatio(aspectRatio)
        .setSourceRectHint(sourceRectHint)
        .setAutoEnterEnabled(true)
        .build())

    Java

    setPictureInPictureParams(new PictureInPictureParams.Builder()
        .setAspectRatio(aspectRatio)
        .setSourceRectHint(sourceRectHint)
        .setAutoEnterEnabled(true)
        .build());
  2. Chiama setPictureInPictureParams con l'aggiornato PictureInPictureParams in anticipo. L'app non attende il callback onUserLeaveHint (come avrebbe fatto in Android 11).

    Ad esempio, potresti voler chiamare setPictureInPictureParams alla prima riproduzione e a quelle successive se le proporzioni vengono modificate.

  3. Chiama setAutoEnterEnabled(false), ma solo se necessario. Ad esempio, probabilmente non vuoi entrare in modalità PIP se la riproduzione corrente è in stato di pausa.

Impostare un sourceRectHint appropriato per l'ingresso e l'uscita dalla modalità PIP

A partire dall'introduzione di PIP in Android 8.0, setSourceRectHint indicava l'area dell'attività visibile dopo la transizione a Picture in picture, ad esempio i limiti della visualizzazione video in un video player.

Con Android 12, il sistema utilizza sourceRectHint per implementare un'animazione molto più fluida sia quando si entra che quando si esce dalla modalità PIP.

Per impostare correttamente sourceRectHint per l'ingresso e l'uscita dalla modalità PIP:

  1. Crea PictureInPictureParams utilizzando i limiti appropriati come sourceRectHint. Ti consigliamo anche di collegare un listener di modifiche al layout al video player:

    Kotlin

    val mOnLayoutChangeListener =
    OnLayoutChangeListener { v: View?, oldLeft: Int,
            oldTop: Int, oldRight: Int, oldBottom: Int, newLeft: Int, newTop:
            Int, newRight: Int, newBottom: Int ->
        val sourceRectHint = Rect()
        mYourVideoView.getGlobalVisibleRect(sourceRectHint)
        val builder = PictureInPictureParams.Builder()
            .setSourceRectHint(sourceRectHint)
        setPictureInPictureParams(builder.build())
    }
    
    mYourVideoView.addOnLayoutChangeListener(mOnLayoutChangeListener)

    Java

    private final View.OnLayoutChangeListener mOnLayoutChangeListener =
            (v, oldLeft, oldTop, oldRight, oldBottom, newLeft, newTop, newRight,
            newBottom) -> {
        final Rect sourceRectHint = new Rect();
        mYourVideoView.getGlobalVisibleRect(sourceRectHint);
        final PictureInPictureParams.Builder builder =
            new PictureInPictureParams.Builder()
                .setSourceRectHint(sourceRectHint);
        setPictureInPictureParams(builder.build());
    };
    
    mYourVideoView.addOnLayoutChangeListener(mOnLayoutChangeListener);
  2. Se necessario, aggiorna sourceRectHint prima che il sistema avvii la transizione di uscita. Quando il sistema sta per uscire dalla modalità PIP, la gerarchia di oggetti View dell'attività viene disposta nella configurazione di destinazione (ad esempio, a schermo intero). L'app può collegare un listener di modifiche al layout alla visualizzazione principale o alla visualizzazione di destinazione (ad esempio, la visualizzazione del video player) per rilevare l'evento e aggiornare sourceRectHint prima dell'inizio dell'animazione.

    Kotlin

    // Listener is called immediately after the user exits PiP but before animating.
    playerView.addOnLayoutChangeListener { _, left, top, right, bottom,
                        oldLeft, oldTop, oldRight, oldBottom ->
        if (left != oldLeft
            || right != oldRight
            || top != oldTop
            || bottom != oldBottom) {
            // The playerView's bounds changed, update the source hint rect to
            // reflect its new bounds.
            val sourceRectHint = Rect()
            playerView.getGlobalVisibleRect(sourceRectHint)
            setPictureInPictureParams(
                PictureInPictureParams.Builder()
                    .setSourceRectHint(sourceRectHint)
                    .build()
            )
        }
    }

    Java

    // Listener is called right after the user exits PiP but before animating.
    playerView.addOnLayoutChangeListener((v, left, top, right, bottom,
                        oldLeft, oldTop, oldRight, oldBottom) -> {
        if (left != oldLeft
            || right != oldRight
            || top != oldTop
            || bottom != oldBottom) {
            // The playerView's bounds changed, update the source hint rect to
            // reflect its new bounds.
            final Rect sourceRectHint = new Rect();
            playerView.getGlobalVisibleRect(sourceRectHint);
            setPictureInPictureParams(
                new PictureInPictureParams.Builder()
                    .setSourceRectHint(sourceRectHint)
                    .build());
        }
    });

Disattivare il ridimensionamento senza interruzioni per i contenuti non video

Android 12 aggiunge il flag setSeamlessResizeEnabled, che fornisce un'animazione di dissolvenza incrociata molto più fluida quando si ridimensionano i contenuti non video nella finestra PIP. In precedenza, il ridimensionamento dei contenuti non video in una finestra PIP poteva creare artefatti visivi stridenti.

Per attivare il ridimensionamento senza interruzioni per i contenuti video:

Kotlin

setPictureInPictureParams(PictureInPictureParams.Builder()
    .setSeamlessResizeEnabled(true)
    .build())

Java

setPictureInPictureParams(new PictureInPictureParams.Builder()
    .setSeamlessResizeEnabled(true)
    .build());

Gestire l'UI durante PIP

Quando l'attività entra o esce dalla modalità Picture in picture (PIP), il sistema chiama Activity.onPictureInPictureModeChanged() o Fragment.onPictureInPictureModeChanged().

Android 15 introduce modifiche che garantiscono una transizione ancora più fluida quando si entra in modalità PIP. Questo è utile per le app che hanno elementi UI sovrapposti alla UI principale, che passa a PIP.

Gli sviluppatori utilizzano il callback onPictureInPictureModeChanged() per definire la logica che attiva/disattiva la visibilità degli elementi UI sovrapposti. Questo callback viene attivato al termine dell'animazione di ingresso o uscita PIP. A partire da Android 15, la PictureInPictureUiState classe include un nuovo stato.

Con questo nuovo stato dell'UI, le app che hanno come target Android 15 osservano l'invocazione del Activity#onPictureInPictureUiStateChanged() callback con isTransitioningToPip() non appena inizia l'animazione PIP. Esistono molti elementi UI che non sono pertinenti per l'app quando è in modalità PIP, ad esempio visualizzazioni o layout che includono informazioni come suggerimenti, video imminenti, valutazioni e titoli. Quando l'app passa alla modalità PIP, utilizza il callback onPictureInPictureUiStateChanged() per nascondere questi elementi UI. Quando l'app passa alla modalità a schermo intero dalla finestra PIP, utilizza il callback onPictureInPictureModeChanged() per mostrare di nuovo questi elementi, come mostrato negli esempi seguenti:

Kotlin

override fun onPictureInPictureUiStateChanged(pipState: PictureInPictureUiState) {
        if (pipState.isTransitioningToPip()) {
          // Hide UI elements.
        }
    }

Java

@Override
public void onPictureInPictureUiStateChanged(PictureInPictureUiState pipState) {
        if (pipState.isTransitioningToPip()) {
          // Hide UI elements.
        }
    }

Kotlin

override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) {
        if (isInPictureInPictureMode) {
          // Unhide UI elements.
        }
    }

Java

@Override
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
        if (isInPictureInPictureMode) {
          // Unhide UI elements.
        }
    }

Questo rapido passaggio di visibilità degli elementi UI non pertinenti (per una finestra PIP) contribuisce a garantire un'animazione di ingresso PIP più fluida e senza sfarfallio.

Sostituisci questi callback per ridisegnare gli elementi UI dell'attività. Tieni presente che, in modalità PIP, l'attività viene visualizzata in una piccola finestra. Gli utenti non possono interagire con gli elementi UI dell'app quando l'app è in modalità PIP e i dettagli degli elementi UI di piccole dimensioni potrebbero essere difficili da vedere. Le attività di riproduzione video con un'UI minima offrono la migliore esperienza utente.

Se la tua app deve fornire azioni personalizzate per PIP, consulta Aggiungere controlli in questa pagina. Rimuovi gli altri elementi UI prima che l'attività entri in modalità PIP e ripristinali quando l'attività torna a schermo intero.

Aggiungi controlli

La finestra PIP può visualizzare i controlli quando l'utente apre il menu della finestra (toccando la finestra su un dispositivo mobile o selezionando il menu dal telecomando della TV).

Se un'app ha una sessione multimediale attiva , vengono visualizzati i controlli di riproduzione, pausa, successivo e precedente.

Puoi anche specificare esplicitamente azioni personalizzate creando PictureInPictureParams con PictureInPictureParams.Builder.setActions() prima di entrare in modalità PIP e passare i parametri quando entri in modalità PIP utilizzando enterPictureInPictureMode(android.app.PictureInPictureParams) o setPictureInPictureParams(android.app.PictureInPictureParams). Fai attenzione. Se provi ad aggiungere più di getMaxNumPictureInPictureActions(), otterrai solo il numero massimo.

Continuare la riproduzione video in modalità PIP

Quando l'attività passa a PIP, il sistema la mette in stato di pausa e chiama il metodo dell'attività onPause(). La riproduzione video non deve essere messa in pausa, ma deve continuare se l'attività viene messa in pausa durante la transizione alla modalità PIP.

In Android 7.0 e versioni successive, devi mettere in pausa e riprendere la riproduzione video quando il sistema chiama onStop() e onStart() dell'attività. In questo modo, puoi evitare di dover verificare se l'app è in modalità PIP in onPause() e continuare esplicitamente la riproduzione.

Se non hai impostato il flag setAutoEnterEnabled su true e devi mettere in pausa la riproduzione nell'implementazione di onPause(), controlla la modalità PIP chiamando isInPictureInPictureMode() e gestisci la riproduzione in modo appropriato. Ad esempio:

Kotlin

override fun onPause() {
    super.onPause()
    // If called while in PiP mode, do not pause playback.
    if (isInPictureInPictureMode) {
        // Continue playback.
    } else {
        // Use existing playback logic for paused activity behavior.
    }
}

Java

@Override
public void onPause() {
    // If called while in PiP mode, do not pause playback.
    if (isInPictureInPictureMode()) {
        // Continue playback.
        ...
    } else {
        // Use existing playback logic for paused activity behavior.
        ...
    }
}

Quando l'attività esce dalla modalità PIP e torna alla modalità a schermo intero, il sistema riprende l'attività e chiama il metodo onResume().

Utilizzare una singola attività di riproduzione per PIP

Nella tua app, un utente potrebbe selezionare un nuovo video mentre sfoglia i contenuti nella schermata principale, mentre un'attività di riproduzione video è in modalità PIP. Riproduci il nuovo video nell'attività di riproduzione esistente in modalità a schermo intero, anziché avviare una nuova attività che potrebbe confondere l'utente.

Per assicurarti che venga utilizzata una singola attività per le richieste di riproduzione video e che venga attivata o disattivata la modalità PIP in base alle esigenze, imposta android:launchMode dell'attività su singleTask nel file manifest:

<activity android:name="VideoActivity"
    ...
    android:supportsPictureInPicture="true"
    android:launchMode="singleTask"
    ...

Nell'attività, sostituisci onNewIntent() e gestisci il nuovo video, interrompendo la riproduzione video esistente, se necessario.

Best practice

PIP potrebbe essere disattivato sui dispositivi con poca RAM. Prima che la tua app utilizzi PIP, verifica che sia disponibile chiamando hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE).

PIP è destinato alle attività che riproducono video a schermo intero. Quando passi l'attività in modalità PIP, evita di mostrare qualsiasi elemento diverso dai contenuti video. Monitora quando l'attività entra in modalità PIP e nascondi gli elementi UI, come descritto in Gestire l'UI durante PIP.

Quando un'attività è in modalità PIP, per impostazione predefinita non riceve lo stato attivo dell'input. Per ricevere eventi di input in modalità PIP, utilizza MediaSession.setCallback(). Per ulteriori informazioni sull'utilizzo di setCallback(), consulta Visualizzare una scheda Now Playing card.

Quando l'app è in modalità PIP, la riproduzione video nella finestra PIP può causare interferenze audio con un'altra app, ad esempio un'app di lettore musicale o un'app di ricerca vocale. Per evitare questo problema, richiedi il focus audio quando inizi a riprodurre il video e gestisci le notifiche di modifica del focus audio, come descritto in Gestire il focus audio. Se ricevi una notifica di perdita dello stato attivo dell'audio in modalità PIP, metti in pausa o interrompi la riproduzione video.

Quando l'app sta per entrare in modalità PIP, tieni presente che solo l'attività principale entra in Picture in picture. In alcune situazioni, ad esempio sui dispositivi multi-finestra, è possibile che l'attività sottostante venga visualizzata e diventi di nuovo visibile insieme all'attività PIP. Devi gestire questo caso di conseguenza, inclusa l'attività sottostante che riceve un callback onResume() o onPause(). È anche possibile che l'utente interagisca con l'attività. Ad esempio, se hai visualizzato un'attività di elenco video e l'attività di riproduzione video in modalità PIP, l'utente potrebbe selezionare un nuovo video dall'elenco e l'attività PIP dovrebbe essere aggiornata di conseguenza.

Codice campione aggiuntivo

Per scaricare un'app di esempio scritta in Kotlin, consulta Esempio di Picture in picture per Android (Kotlin).