Fai conoscere l'app

I display di grandi dimensioni aperti e le esclusive posizioni di chiusura consentono nuove esperienze utente sui dispositivi pieghevoli. Per rendere la tua app compatibile con i dispositivi pieghevoli, utilizza la libreria Jetpack WindowManager, che fornisce una superficie API per le funzionalità della finestra dei dispositivi pieghevoli, come pieghe e cerniere. Quando la tua app è compatibile con la piega, può adattare il layout per evitare di posizionare contenuti importanti nell'area delle pieghe o delle cerniere e utilizzare pieghe e cerniere come separatori naturali.

Comprendere se un dispositivo supporta configurazioni come la postura a libro o a tavolino può guidare le decisioni in merito al supporto di layout diversi o alla fornitura di funzionalità specifiche.

Informazioni sulla finestra

L'interfaccia WindowInfoTracker in Jetpack WindowManager espone informazioni sul layout della finestra. Il metodo windowLayoutInfo() dell'interfaccia restituisce un flusso di dati WindowLayoutInfo che informa la tua app sullo stato di chiusura di un dispositivo pieghevole. Il metodo WindowInfoTracker#getOrCreate() crea un'istanza di WindowInfoTracker.

WindowManager fornisce il supporto per la raccolta dei dati WindowLayoutInfo utilizzando flussi Kotlin e callback Java.

Flussi Kotlin

Per avviare e interrompere la raccolta dei dati di WindowLayoutInfo, puoi utilizzare una coroutine riavviabile e consapevole del ciclo di vita in cui il blocco di codice repeatOnLifecycle viene eseguito quando il ciclo di vita è almeno STARTED e viene interrotto quando il ciclo di vita è STOPPED. L'esecuzione del blocco di codice viene riavviata automaticamente quando il ciclo di vita è di nuovo STARTED. Nell'esempio seguente, il blocco di codice raccoglie e utilizza i dati WindowLayoutInfo:

class DisplayFeaturesActivity : AppCompatActivity() {

    private lateinit var binding: ActivityDisplayFeaturesBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityDisplayFeaturesBinding.inflate(layoutInflater)
        setContentView(binding.root)

        lifecycleScope.launch(Dispatchers.Main) {
            lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
                WindowInfoTracker.getOrCreate(this@DisplayFeaturesActivity)
                    .windowLayoutInfo(this@DisplayFeaturesActivity)
                    .collect { newLayoutInfo ->
                        // Use newLayoutInfo to update the layout.
                    }
            }
        }
    }
}

Callback Java

Il livello di compatibilità del callback incluso nella dipendenza androidx.window:window-java ti consente di raccogliere gli aggiornamenti WindowLayoutInfo senza utilizzare un flusso Kotlin. L'artefatto include la classe WindowInfoTrackerCallbackAdapter, che adatta un WindowInfoTracker per supportare la registrazione (e l'annullamento della registrazione) dei callback per ricevere aggiornamenti WindowLayoutInfo, ad esempio:

public class SplitLayoutActivity extends AppCompatActivity {

    private WindowInfoTrackerCallbackAdapter windowInfoTracker;
    private ActivitySplitLayoutBinding binding;
    private final LayoutStateChangeCallback layoutStateChangeCallback =
            new LayoutStateChangeCallback();

   @Override
   protected void onCreate(@Nullable Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);

       binding = ActivitySplitLayoutBinding.inflate(getLayoutInflater());
       setContentView(binding.getRoot());

       windowInfoTracker =
                new WindowInfoTrackerCallbackAdapter(WindowInfoTracker.getOrCreate(this));
   }

   @Override
   protected void onStart() {
       super.onStart();
       windowInfoTracker.addWindowLayoutInfoListener(
                this, Runnable::run, layoutStateChangeCallback);
   }

   @Override
   protected void onStop() {
       super.onStop();
       windowInfoTracker
           .removeWindowLayoutInfoListener(layoutStateChangeCallback);
   }

   class LayoutStateChangeCallback implements Consumer<WindowLayoutInfo> {
       @Override
       public void accept(WindowLayoutInfo newLayoutInfo) {
           SplitLayoutActivity.this.runOnUiThread( () -> {
               // Use newLayoutInfo to update the layout.
           });
       }
   }
}

Supporto di RxJava

Se utilizzi già RxJava (versione 2 o 3), puoi sfruttare gli artefatti che ti consentono di utilizzare un Observable o un Flowable per raccogliere gli aggiornamenti WindowLayoutInfo senza utilizzare un flusso Kotlin.

Il livello di compatibilità fornito dalle dipendenze androidx.window:window-rxjava2 e androidx.window:window-rxjava3 include i metodi WindowInfoTracker#windowLayoutInfoFlowable() e WindowInfoTracker#windowLayoutInfoObservable(), che consentono alla tua app di ricevere aggiornamenti WindowLayoutInfo, ad esempio:

class RxActivity: AppCompatActivity {

    private lateinit var binding: ActivityRxBinding

    private var disposable: Disposable? = null
    private lateinit var observable: Observable<WindowLayoutInfo>

   @Override
   protected void onCreate(@Nullable Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);

       binding = ActivitySplitLayoutBinding.inflate(getLayoutInflater());
       setContentView(binding.getRoot());

        // Create a new observable.
        observable = WindowInfoTracker.getOrCreate(this@RxActivity)
            .windowLayoutInfoObservable(this@RxActivity)
   }

   @Override
   protected void onStart() {
       super.onStart();

        // Subscribe to receive WindowLayoutInfo updates.
        disposable?.dispose()
        disposable = observable
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe { newLayoutInfo ->
            // Use newLayoutInfo to update the layout.
        }
   }

   @Override
   protected void onStop() {
       super.onStop();

        // Dispose of the WindowLayoutInfo observable.
        disposable?.dispose()
   }
}

Caratteristiche dei display pieghevoli

La classe WindowLayoutInfo di Jetpack WindowManager rende disponibili le funzionalità di una finestra di visualizzazione come un elenco di elementi DisplayFeature.

Un FoldingFeature è un tipo di DisplayFeature che fornisce informazioni sui display pieghevoli, incluse le seguenti proprietà:

  • state: Lo stato di chiusura del dispositivo, FLAT o HALF_OPENED

  • orientation: l'orientamento della piega o della cerniera, HORIZONTAL o VERTICAL

  • occlusionType: indica se la piega o la cerniera nasconde parte del display, NONE o FULL.

  • isSeparating: Indica se la piega o la cerniera crea due aree di visualizzazione logiche, true o false

Un dispositivo pieghevole che è HALF_OPENED segnala sempre isSeparating come true perché lo schermo è suddiviso in due aree di visualizzazione. Inoltre, isSeparating è sempre true su un dispositivo dual-screen quando l'applicazione si estende su entrambi gli schermi.

La proprietà FoldingFeature bounds (ereditata da DisplayFeature) rappresenta il rettangolo di selezione di una funzionalità di piegatura come una piega o una cerniera. I limiti possono essere utilizzati per posizionare gli elementi sullo schermo rispetto alla funzionalità:

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    // ...
    lifecycleScope.launch(Dispatchers.Main) {
        lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
            // Safely collects from WindowInfoTracker when the lifecycle is
            // STARTED and stops collection when the lifecycle is STOPPED.
            WindowInfoTracker.getOrCreate(this@MainActivity)
                .windowLayoutInfo(this@MainActivity)
                .collect { layoutInfo ->
                    // New posture information.
                    val foldingFeature = layoutInfo.displayFeatures
                        .filterIsInstance<FoldingFeature>()
                        .firstOrNull()
                    // Use information from the foldingFeature object.
                }
        }
    }
}

Java

private WindowInfoTrackerCallbackAdapter windowInfoTracker;
private final LayoutStateChangeCallback layoutStateChangeCallback =
                new LayoutStateChangeCallback();

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    // ...
    windowInfoTracker =
            new WindowInfoTrackerCallbackAdapter(WindowInfoTracker.getOrCreate(this));
}

@Override
protected void onStart() {
    super.onStart();
    windowInfoTracker.addWindowLayoutInfoListener(
            this, Runnable::run, layoutStateChangeCallback);
}

@Override
protected void onStop() {
    super.onStop();
    windowInfoTracker.removeWindowLayoutInfoListener(layoutStateChangeCallback);
}

class LayoutStateChangeCallback implements Consumer<WindowLayoutInfo> {
    @Override
    public void accept(WindowLayoutInfo newLayoutInfo) {
        // Use newLayoutInfo to update the Layout.
        List<DisplayFeature> displayFeatures = newLayoutInfo.getDisplayFeatures();
        for (DisplayFeature feature : displayFeatures) {
            if (feature instanceof FoldingFeature) {
                // Use information from the feature object.
            }
        }
    }
}

Posizione da tavolo

Utilizzando le informazioni incluse nell'oggetto FoldingFeature, la tua app può supportare posture come quella da tavolo, in cui lo smartphone è appoggiato su una superficie, la cerniera è in posizione orizzontale e lo schermo pieghevole è aperto a metà.

La postura da tavolo offre agli utenti la comodità di utilizzare lo smartphone senza tenerlo in mano. La postura sul tavolo è ideale per guardare contenuti multimediali, scattare foto e fare videochiamate.

Figura 1. Un'app di video player in posizione orizzontale: video sulla parte verticale dello schermo; controlli di riproduzione sulla parte orizzontale.

Utilizza FoldingFeature.State e FoldingFeature.Orientation per determinare se il dispositivo è in posizione orizzontale:

Kotlin

fun isTableTopPosture(foldFeature : FoldingFeature?) : Boolean {
    contract { returns(true) implies (foldFeature != null) }
    return foldFeature?.state == FoldingFeature.State.HALF_OPENED &&
            foldFeature.orientation == FoldingFeature.Orientation.HORIZONTAL
}

Java

boolean isTableTopPosture(FoldingFeature foldFeature) {
    return (foldFeature != null) &&
           (foldFeature.getState() == FoldingFeature.State.HALF_OPENED) &&
           (foldFeature.getOrientation() == FoldingFeature.Orientation.HORIZONTAL);
}

Una volta che sai che il dispositivo è in modalità Tavolo, aggiorna il layout dell'app di conseguenza. Per le app multimediali, in genere significa posizionare la riproduzione sopra la piega e posizionare i controlli e i contenuti supplementari subito dopo per un'esperienza di visualizzazione o ascolto a mani libere.

Su Android 15 (livello API 35) e versioni successive, puoi richiamare un'API sincrona per rilevare se un dispositivo supporta la postura a tavolino indipendentemente dallo stato attuale del dispositivo.

L'API fornisce un elenco di posture supportate dal dispositivo. Se l'elenco contiene la postura del tablet, puoi dividere il layout dell'app per supportare la postura ed eseguire test A/B sull'interfaccia utente dell'app per i layout del tablet e a schermo intero.

Kotlin

if (WindowSdkExtensions.getInstance().extensionsVersion >= 6) {
    val postures = WindowInfoTracker.getOrCreate(context).supportedPostures
    if (postures.contains(TABLE_TOP)) {
        // Device supports tabletop posture.
   }
}

Java

if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 6) {
    List<SupportedPosture> postures = WindowInfoTracker.getOrCreate(context).getSupportedPostures();
    if (postures.contains(SupportedPosture.TABLETOP)) {
        // Device supports tabletop posture.
    }
}

Esempi

Postura di lettura

Un'altra funzionalità unica dei dispositivi pieghevoli è la postura a libro, in cui il dispositivo è aperto a metà e la cerniera è verticale. La postura a libro è ideale per leggere ebook. Con un layout a due pagine su un dispositivo pieghevole con schermo di grandi dimensioni aperto come un libro rilegato, la postura libro cattura l'esperienza di lettura di un libro reale.

Può essere utilizzato anche per la fotografia se vuoi acquisire un formato diverso mentre scatti foto a mani libere.

Implementa la postura del libro con le stesse tecniche utilizzate per la postura sul tavolo. L'unica differenza è che il codice deve verificare che l'orientamento della funzionalità di chiusura sia verticale anziché orizzontale:

Kotlin

fun isBookPosture(foldFeature : FoldingFeature?) : Boolean {
    contract { returns(true) implies (foldFeature != null) }
    return foldFeature?.state == FoldingFeature.State.HALF_OPENED &&
            foldFeature.orientation == FoldingFeature.Orientation.VERTICAL
}

Java

boolean isBookPosture(FoldingFeature foldFeature) {
    return (foldFeature != null) &&
           (foldFeature.getState() == FoldingFeature.State.HALF_OPENED) &&
           (foldFeature.getOrientation() == FoldingFeature.Orientation.VERTICAL);
}

Modifiche alle dimensioni della finestra

L'area di visualizzazione di un'app può cambiare in seguito a una modifica della configurazione del dispositivo, ad esempio quando il dispositivo viene piegato o aperto, ruotato o quando una finestra viene ridimensionata in modalità multi-finestra.

La classe Jetpack WindowManager WindowMetricsCalculator ti consente di recuperare le metriche della finestra corrente e massima. Come la piattaforma WindowMetrics introdotta nel livello API 30, WindowManager WindowMetrics fornisce i limiti della finestra, ma l'API è compatibile con le versioni precedenti fino al livello API 14.

Vedi Utilizzare le classi di dimensioni della finestra.

Risorse aggiuntive

Campioni

  • WindowManager di Jetpack: esempio di come utilizzare la libreria WindowManager di Jetpack
  • Jetcaster : implementazione della postura sul tavolo con Compose

Codelab