I display di grandi dimensioni aperti e gli stati di chiusura unici 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 le pieghe e le cerniere. Quando la tua app è compatibile con i dispositivi pieghevoli, può adattare il layout per evitare di posizionare contenuti importanti nell'area delle pieghe o delle cerniere e utilizzare le pieghe e le cerniere come separatori naturali.
Comprendere se un dispositivo supporta configurazioni come la postura da tavolo o a libro 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 le informazioni sul layout della finestra. Il metodo windowLayoutInfo() dell'interfaccia restituisce uno
stream di dati WindowLayoutInfo che informa la tua app sullo stato di chiusura di un dispositivo
pieghevole. Il WindowInfoTracker#getOrCreate() metodo crea un
istanza di WindowInfoTracker.
WindowManager fornisce il supporto per la raccolta dei dati WindowLayoutInfo utilizzando
i flussi Kotlin e i callback Java.
Flussi Kotlin
Per avviare e interrompere la raccolta dei dati WindowLayoutInfo, puoi utilizzare una coroutine riavviabile
con riconoscimento 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 è STARTED di nuovo. 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à dei callback incluso nella
androidx.window:window-java dipendenza 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 gli 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 Flowable
per raccogliere gli aggiornamenti WindowLayoutInfo senza utilizzare un flusso Kotlin.
Il livello di compatibilità fornito dalle androidx.window:window-rxjava2 e
androidx.window:window-rxjava3 dipendenze include i
WindowInfoTracker#windowLayoutInfoFlowable() e
WindowInfoTracker#windowLayoutInfoObservable() metodi, che consentono alla tua
app di ricevere WindowLayoutInfo aggiornamenti, 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()
}
}
Funzionalità dei display pieghevoli
La classe WindowLayoutInfo di Jetpack WindowManager rende disponibili le funzionalità di una
finestra di visualizzazione come un elenco di DisplayFeature elementi.
Un FoldingFeature è un tipo di DisplayFeature che fornisce informazioni
sui display pieghevoli, incluse le seguenti proprietà:
state: lo stato di chiusura del dispositivo,FLAToHALF_OPENEDorientation: l'orientamento della piega o della cerniera,HORIZONTALoVERTICALocclusionType: indica se la piega o la cerniera nasconde una parte del display,NONEoFULLisSeparating: indica se la piega o la cerniera crea due aree di visualizzazione logiche, true o false
Un dispositivo pieghevole HALF_OPENED segnala sempre isSeparating come true
perché lo schermo è suddiviso in due aree di visualizzazione. Inoltre, isSeparating è
sempre true su un dispositivo a doppio schermo quando l'applicazione si estende su entrambi gli
schermi.
La proprietà FoldingFeature bounds (ereditata da DisplayFeature)
rappresenta il rettangolo di delimitazione di una funzionalità di chiusura, ad esempio 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.
}
}
}
}
Postura 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 da tavolo è ideale per guardare contenuti multimediali, scattare foto ed effettuare videochiamate.
Utilizza FoldingFeature.State e FoldingFeature.Orientation per determinare
se il dispositivo è in postura da tavolo:
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 stabilito che il dispositivo è in postura da 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 in vivavoce.
Su Android 15 (livello API 35) e versioni successive, puoi richiamare un'API sincrona per rilevare se un dispositivo supporta la postura da tavolo indipendentemente dallo stato attuale del dispositivo.
L'API fornisce un elenco di posture supportate dal dispositivo. Se l'elenco contiene la postura da tavolo, puoi dividere il layout dell'app per supportare la postura ed eseguire test A/B sull'UI dell'app per i layout da tavolo 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
MediaPlayerActivityApp: scopri come utilizzare Media3 Exoplayer e WindowManager per creare un video player compatibile con i dispositivi pieghevoli.Ottimizza l'app Fotocamera sui dispositivi pieghevoli con Jetpack WindowManager codelab: scopri come implementare la postura da tavolo per le app di fotografia. Mostra il mirino nella metà superiore dello schermo (sopra la piega) e i controlli nella metà inferiore (sotto la piega).
Postura a libro
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 e‑book. Con un layout a due pagine su un dispositivo pieghevole a schermo grande aperto come un libro rilegato, la postura a libro cattura l'esperienza di leggere un libro vero.
Può essere utilizzata anche per la fotografia se vuoi acquisire un rapporto di aspetto diverso mentre scatti foto in vivavoce.
Implementa la postura a libro con le stesse tecniche utilizzate per la postura da 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 delle dimensioni della finestra
L'area di visualizzazione di un'app può cambiare a seguito di una modifica della configurazione del dispositivo, ad esempio quando il dispositivo viene chiuso o aperto, ruotato o quando una finestra viene ridimensionata in modalità multi‑finestra.
La classe WindowMetricsCalculator di Jetpack WindowManager ti consente di
recuperare le metriche della finestra correnti e massime. Come le
WindowMetrics della piattaforma introdotte nel livello API 30, le WindowManager
WindowMetrics forniscono 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
- Jetpack WindowManager: esempio di come utilizzare la libreria Jetpack WindowManager
- Jetcaster : implementazione della postura da tavolo con Compose
Codelab
- Supportare i dispositivi pieghevoli e a doppio schermo con Jetpack WindowManager
- Ottimizzare l'app Fotocamera sui dispositivi pieghevoli con Jetpack WindowManager