Adapter votre application aux appareils pliables

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.

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 un flux de données WindowLayoutInfo qui informe votre application de la position pliée d'un appareil pliable. La méthode WindowInfoTracker getOrCreate() crée une instance de WindowInfoTracker.

WindowManager contribue à la collecte des données WindowLayoutInfo à l'aide de flux Kotlin et de rappels Java.

Flux Kotlin

Pour démarrer et arrêter la collecte de données WindowLayoutInfo, vous pouvez utiliser une coroutine redémarrable sensible au cycle de vie, dans laquelle le bloc de code repeatOnLifecycle est exécuté lorsque le cycle de vie présente au moins la valeur STARTED et arrêté lorsque le cycle de vie présente l'état 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 la dépendance androidx.window:window-java vous permet de collecter des mises à jour 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 utiliser les artefacts qui vous permettent d'utiliser un Observable ou Flowable pour collecter les mises à jour WindowLayoutInfo sans utiliser de flux Kotlin.

La couche de compatibilité fournie par les dépendances androidx.window:window-rxjava2 et androidx.window:window-rxjava3 inclut les méthodes WindowInfoTracker#windowLayoutInfoFlowable() et WindowInfoTracker#windowLayoutInfoObservable(), qui permettent à votre application de recevoir des 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 the WindowLayoutInfo observable
        disposable?.dispose()
   }
}

Caractéristiques des écrans pliables

La classe WindowLayoutInfo de Jetpack WindowManager rend les caractéristiques d'une fenêtre d'affichage disponibles 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, par exemple :

  • state : position pliée de l'appareil (FLAT ou HALF_OPENED).
  • orientation : orientation du pli ou de la charnière (HORIZONTAL ou VERTICAL).
  • occlusionType : indique si le pli ou la charnière masque une partie de l'écran (NONE ou FULL).
  • 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 divisé en deux zones d'affichage. De plus, isSeparating est toujours vrai sur un appareil à double écran lorsque l'application recouvre les deux écrans.

La 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 windowInfoRepo 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()
                        .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
            }
        }
    }
}

Mode sur table

À l'aide des informations incluses dans l'objet FoldingFeature, votre application peut prendre en charge des positions telles que le mode sur table, dans lequel le téléphone est posé sur une surface, la charnière est en position horizontale et l'écran pliable est à moitié ouvert.

Le mode sur table offre aux utilisateurs la possibilité d'utiliser leur téléphone sans le tenir dans les mains. Le mode sur table est idéal pour regarder des contenus multimédias, prendre des photos et passer des appels vidéo.

Une application de lecteur vidéo en mode sur table

Utilisez FoldingFeature.State et FoldingFeature.Orientation pour déterminer si l'appareil est en mode sur table :

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 en mode sur table, 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.

Exemples

Mode Livre

Le mode Livre est une autre position unique de l'appareil pliable, où l'appareil est à moitié ouvert et la charnière est à la verticale. Le mode Livre est idéal pour lire des e-books. Avec une mise en page sur deux pages sur un appareil pliable à grand écran ouvert comme un livre relié, le mode 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 le mode Livre avec les mêmes techniques que celles utilisées pour le mode sur table. La seule différence est que le code doit vérifier que l'appareil est plié à la verticale et non à l'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 en raison d'une modification de la configuration de l'appareil, par exemple lorsque l'appareil est plié ou déployé, qu'il pivote ou lorsqu'une fenêtre est 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.

Pour savoir comment prendre en charge différentes tailles de fenêtre, consultez Assurer la compatibilité avec différentes tailles d'écran.

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