Les grands écrans déployés et les positions pliées uniques offrent de nouvelles expériences utilisateur sur les appareils pliables. Pour que votre application devienne pliable, utilisez la bibliothèque Jetpack WindowManager, qui fournit une surface d'API pour les caractéristiques des fenêtres des appareils pliables telles que les plis et les charnières. Une application pliable peut adapter sa mise en page pour éviter de placer du contenu important dans les zones des plis ou des charnières et les utiliser comme séparateurs naturels.
Déterminer si un appareil est compatible avec des configurations telles que le mode sur table ou avec un livre peut guider les décisions concernant la compatibilité avec différentes mises en page ou la fourniture des fonctionnalités spécifiques.
Informations sur la fenêtre
L'interface WindowInfoTracker
de Jetpack WindowManager présente des informations sur la mise en page de la fenêtre. La méthode windowLayoutInfo()
de l'interface renvoie une
flux de données WindowLayoutInfo
qui renseigne votre application sur un appareil pliable
l'état de pliage de l'appareil. La méthode WindowInfoTracker#getOrCreate()
crée un
une instance de WindowInfoTracker
.
WindowManager permet de collecter des données WindowLayoutInfo
à l'aide de
Flux Kotlin et rappels Java.
Flux Kotlin
Pour démarrer et arrêter la collecte de données WindowLayoutInfo
, vous pouvez utiliser un
coroutine tenant compte du cycle de vie dans laquelle le bloc de code repeatOnLifecycle
est
exécuté lorsque le cycle de vie est au moins égal à STARTED
et est arrêté lorsque le
est STOPPED
. L'exécution du bloc de code redémarre automatiquement lorsque l'état du cycle de vie est à nouveau STARTED
. Dans l'exemple suivant, le bloc de code collecte et utilise les données 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.
}
}
}
}
}
Rappels Java
La couche de compatibilité de rappel incluse dans le
La dépendance androidx.window:window-java
vous permet de collecter
WindowLayoutInfo
sans utiliser de flux Kotlin. L'artefact inclut la classe WindowInfoTrackerCallbackAdapter
, qui adapte un WindowInfoTracker
pour accepter l'abonnement (et le désabonnement) à des rappels de mises à jour WindowLayoutInfo
, par exemple :
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.
});
}
}
}
Compatibilité avec RxJava
Si vous utilisez déjà RxJava
(version 2
ou 3
),
vous pouvez exploiter les artefacts qui vous permettent d'utiliser
Observable
ou Flowable
pour collecter les mises à jour WindowLayoutInfo
sans utiliser de flux Kotlin.
La couche de compatibilité fournie par les androidx.window:window-rxjava2
et
Les dépendances androidx.window:window-rxjava3
incluent
WindowInfoTracker#windowLayoutInfoFlowable()
et
WindowInfoTracker#windowLayoutInfoObservable()
, qui permettent
application à recevoir les mises à jour WindowLayoutInfo
, par exemple:
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()
}
}
Caractéristiques des écrans pliables
La classe WindowLayoutInfo
de Jetpack WindowManager rend les caractéristiques d'un
fenêtre d'affichage disponible sous la forme d'une liste d'éléments DisplayFeature
.
Un FoldingFeature
est un type de DisplayFeature
qui fournit des informations
sur les écrans pliables, y compris:
state
: position pliée de l'appareil (FLAT
ouHALF_OPENED
).orientation
: orientation du pli ou de la charnière (HORIZONTAL
ouVERTICAL
occlusionType
: indique si le pli ou la charnière masque une partie de l'écran.NONE
ouFULL
isSeparating
: indique si le pli ou la charnière crée deux zones d'affichage logiques (vrai ou faux).
Un appareil pliable HALF_OPENED
signale toujours isSeparating
comme vrai
car l'écran est séparé en
deux zones d'affichage. De plus, isSeparating
est toujours vrai sur un appareil à double écran lorsque l'application recouvre les deux écrans.
Propriété bounds
FoldingFeature
(héritée de DisplayFeature
)
représente le rectangle de délimitation d'une caractéristique de pliage telle qu'un pli ou une charnière.
Les limites peuvent être utilisées pour positionner des éléments à l'écran par rapport à la caractéristique:
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. } } } }
Position à plat
Votre application peut utiliser les informations incluses dans l'objet FoldingFeature
où le téléphone est posé sur une surface plane, où la charnière est
en position horizontale, et que l'écran de l'appareil pliable est à moitié ouvert.
La position sur table offre aux utilisateurs la possibilité d'utiliser leur téléphone sans le tenir dans les mains. La position à plat est idéale pour regarder des contenus multimédias, prendre des photos et passer des appels vidéo.

Utilisez FoldingFeature.State
et FoldingFeature.Orientation
pour déterminer si l'appareil est posé à plat :
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); }
Une fois que vous savez que l'appareil est posé à plat, mettez à jour la mise en page de votre application en conséquence. Pour les applications multimédias, cela implique généralement de placer la lecture au-dessus du pli, et de placer des commandes de positionnement et du contenu supplémentaire juste en dessous pour une expérience de visionnage ou d'écoute en mode mains libres.
Sous Android 15 (niveau d'API 35) et versions ultérieures, vous pouvez appeler une API synchrone pour détecter si un appareil est compatible avec la position sur une table, quel que soit son état actuel.
L'API fournit la liste des positions prises en charge par l'appareil. Si la liste comporte une position à plat, vous pouvez diviser la mise en page de votre application pour la prendre en charge et exécutez des tests A/B sur l'UI de votre application pour les mises en page sur table et en plein écran.
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. } }
Exemples
Application
MediaPlayerActivity
: découvrez comment utiliser Media3 Exoplayer et WindowManager pour créer un lecteur vidéo adapté au mode à plat.Atelier de programmation Optimiser votre application d'appareil photo sur les appareils pliables avec Jetpack WindowManager : découvrez comment implémenter la position sur table pour les applications de photographie. Affichez le viseur dans la moitié supérieure de l'écran (au-dessus du pli) et les commandes dans la moitié inférieure (en dessous du pli).
Position debout
La position livre est une autre fonctionnalité unique des appareils pliables : l'appareil est à moitié ouvert. et la charnière est verticale. La position debout est idéale pour lire des e-books. Avec mise en page de deux pages sur un appareil pliable à grand écran ouvert comme un livre relié, un livre capture l'expérience de lecture d'un vrai livre.
Vous pouvez également l'utiliser pour prendre des photos si vous souhaitez obtenir un autre format tout en gardant les mains libres.
Implémentez la position livre avec les mêmes techniques que celles utilisées pour la position sur table. La seule différence : le code doit vérifier que l'orientation du pliage verticale et non horizontale:
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); }
Changements de taille de la fenêtre
La zone d'affichage d'une application peut changer à la suite d'un changement de configuration de l'appareil, (par exemple, lorsque l'appareil est plié ou déplié, qu'il pivote ou qu'une fenêtre redimensionnée en mode multifenêtre.
La classe Jetpack WindowManager WindowMetricsCalculator
vous permet de :
récupérer les métriques de la fenêtre actuelle et maximale. Comme les WindowMetrics
de la plate-forme introduites dans le niveau d'API 30, les WindowMetrics
de WindowManager fournissent les limites de fenêtre, mais l'API est rétrocompatible jusqu'au niveau d'API 14.
Consultez Utiliser des classes de taille de fenêtre.
Ressources supplémentaires
Exemples
- Jetpack WindowManager : exemple d'utilisation de la bibliothèque Jetpack WindowManager
- Jetcaster : Intégration d'une position à plat avec Compose
Ateliers de programmation
- Compatibilité avec les appareils pliables et à double écran grâce à Jetpack WindowManager
- Optimiser votre application d'appareil photo sur les appareils pliables avec Jetpack WindowManager