Composant TwoPaneLayout Jetpack Compose pour les appareils pliables
Important
Les fonctionnalités et l’aide décrites dans cet article sont en préversion publique et peuvent faire l’objet de modifications importantes avant leur lancement en disponibilité générale. Microsoft ne donne aucune garantie, expresse ou implicite, concernant les informations fournies ici.
TwoPaneLayout est un composant Jetpack Compose qui vous aide à créer une interface utilisateur pour des appareils double écran, pliables et grand écran. TwoPaneLayout fournit une disposition à deux volets à utiliser au niveau supérieur d’une IU. Le composant place deux volets côte à côte quand l’application s’étend sur des appareils double écran, pliables et grand écran, sinon un seul volet s’affiche. Ces volets peuvent être horizontaux ou verticaux, en fonction de l’orientation de l’appareil et du paneMode
sélectionné.
Notes
TwoPaneLayout considère qu’un appareil est à écran large quand la catégorie de taille de fenêtre est étendue, à savoir au-delà de 840 dp.
Quand l’application s’étend sur une charnière ou pliure verticale, ou quand la largeur de l’écran est supérieure à la hauteur sur un appareil grand écran, le volet 1 est placé à gauche et le volet 2 à droite. Si l’appareil pivote, que l’application s’étend sur une charnière ou pliure horizontale, ou quand la largeur de l’écran est inférieure à la hauteur sur un appareil grand écran, le volet 1 est placé en haut et le volet 2 en bas.
Ajouter une dépendance
Vérifiez que vous disposez du référentiel
mavenCentral()
dans votre fichier build.gradle de premier niveau :allprojects { repositories { google() mavenCentral() } }
Ajoutez des dépendances au fichier build.gradle au niveau module (la version actuelle peut être différente de ce qui est montré ici) :
implementation "com.microsoft.device.dualscreen:twopanelayout:1.0.1-alpha05"
Vérifiez également que est défini sur
compileSdkVersion
API 33 et que esttargetSdkVersion
défini sur API 32 ou version ultérieure dans le fichier build.gradle au niveau du module :android { compileSdkVersion 33 defaultConfig { targetSdkVersion 32 } ... }
Générez la disposition avec
TwoPaneLayout
ouTwoPaneLayoutNav
.Pour plus de détails, consultez l’exemple de TwoPaneLayout et l’exemple de TwoPaneLayoutNav.
Utiliser TwoPaneLayout dans votre projet
Plusieurs concepts importants sont à prendre en compte quand vous utilisez TwoPaneLayout dans vos projets :
-
Selon votre application, vous pouvez choisir entre trois constructeurs TwoPaneLayout différents au niveau supérieur de votre projet : TwoPaneLayout de base, TwoPaneLayout avec navController et TwoPaneLayoutNav.
Personnaliser votre disposition
TwoPaneLayout offre deux façons de personnaliser l’affichage des volets : pondération et mode volet.
-
TwoPaneLayout propose également des méthodes de navigation internes qui peuvent contrôler le contenu affiché dans chaque volet. Selon le constructeur utilisé, vous aurez accès aux
TwoPaneScope
méthodes ouTwoPaneNavScope
. Tester les composables TwoPaneLayout
Pour aider à tester les composables qui utilisent
TwoPaneScope
ouTwoPaneNavScope
, TwoPaneLayout propose des implémentations de test des deux étendues à utiliser dans les tests d’interface utilisateur.
Constructeurs TwoPaneLayout
TwoPaneLayout doit toujours être le composable de niveau supérieur dans votre application, pour que la taille du volet soit calculée correctement. Trois constructeurs TwoPaneLayout différents sont disponibles et correspondent à différents scénarios. Pour plus d’informations de référence sur l’API, consultez le fichier README.md TwoPaneLayout.
TwoPaneLayout de base
@Composable
fun TwoPaneLayout(
modifier: Modifier = Modifier,
paneMode: TwoPaneMode = TwoPaneMode.TwoPane,
pane1: @Composable TwoPaneScope.() -> Unit,
pane2: @Composable TwoPaneScope.() -> Unit
)
Le constructeur TwoPaneLayout de base doit être utilisé quand vous n’avez pas besoin de plus de deux écrans de contenu. Dans les composables pane1
et pane2
, vous pouvez accéder aux champs et méthodes fournis par l’interface TwoPaneScope.
Exemple d'utilisation :
TwoPaneLayout(
pane1 = { Pane1Content() },
pane2 = { Pane2Content() }
)
TwoPaneLayout avec navController
@Composable
fun TwoPaneLayout(
modifier: Modifier = Modifier,
paneMode: TwoPaneMode = TwoPaneMode.TwoPane,
navController: NavHostController,
pane1: @Composable TwoPaneScope.() -> Unit,
pane2: @Composable TwoPaneScope.() -> Unit
)
Le constructeur TwoPaneLayout avec navController doit être utilisé quand :
- vous n’avez pas besoin d’afficher plus de deux écrans de contenu
- vous avez besoin d’accéder aux informations de navigation dans votre application
Dans les composables pane1
et pane2
, vous pouvez accéder aux champs et méthodes fournis par l’interface TwoPaneScope.
Exemple d'utilisation :
val navController = rememberNavController()
TwoPaneLayout(
navController = navController,
pane1 = { Pane1Content() },
pane2 = { Pane2Content() }
)
TwoPaneLayoutNav
@Composable
fun TwoPaneLayoutNav(
modifier: Modifier = Modifier,
navController: NavHostController,
paneMode: TwoPaneMode = TwoPaneMode.TwoPane,
singlePaneStartDestination: String,
pane1StartDestination: String,
pane2StartDestination: String,
builder: NavGraphBuilder.() -> Unit
)
Le constructeur TwoPaneLayoutNav doit être utilisé quand vous voulez afficher plus de deux écrans de contenu et que vous avez besoin d’une prise en charge de navigation personnalisable. Dans chaque composable de destination, vous pouvez accéder aux champs et méthodes fournis par l’interface TwoPaneNavScope.
Exemple d'utilisation :
val navController = rememberNavController()
TwoPaneLayoutNav(
navController = navController,
singlePaneStartDestination = "A",
pane1StartDestination = "A",
pane2StartDestination = "B"
) {
composable("A") {
ContentA()
}
composable("B") {
ContentB()
}
composable("C") {
ContentC()
}
}
Personnaliser votre disposition
Il existe deux façons de personnaliser TwoPaneLayout :
-
weight
: détermine comment disposer les deux volets de manière proportionnelle -
paneMode
: détermine s’il faut montrer un ou deux volets en mode double écran horizontalement et verticalement
Poids
TwoPaneLayout peut attribuer des largeurs ou des hauteurs enfants en fonction de la pondération fournie par les modificateurs TwoPaneScope.weight
et TwoPaneNavScope.weight
.
Exemple d'utilisation :
TwoPaneLayout(
pane1 = { Pane1Content(modifier = Modifier.weight(.3f)) },
pane2 = { Pane2Content(modifier = Modifier.weight(.7f)) }
)
La pondération affecte la disposition différemment sur ces divers appareils :
- grands écrans
- pliables
Grands écrans
Quand aucune pondération n’est fournie, les deux volets sont divisés de manière égale.
Quand la pondération est fournie, la disposition est divisée proportionnellement en fonction du rapport de pondération.
Par exemple, cette capture d’écran montre TwoPaneLayout sur une tablette avec un ratio de pondération de 3:7 :
Pliables
Quand une pliure de séparation est présente, la disposition est divisée en fonction des limites de la pliure, que la pondération soit fournie ou non.
Si la pliure ne marque pas de séparation, l’appareil est traité comme un grand écran ou un seul écran, en fonction de sa taille.
Par exemple, cette image montre la disposition TwoPaneLayout sur un appareil double écran, qui a une pliure de séparation :
Mode volet
Le mode de volet affecte lorsque deux volets sont affichés pour TwoPaneLayout. Par défaut, chaque fois qu’il existe un pli séparant ou une grande fenêtre, deux volets s’affichent, mais vous pouvez choisir d’afficher un seul volet dans ces cas en modifiant le mode de volet.
Un pli de séparation signifie qu’il existe une propriété FoldingFeature qui retourne true pour la propriété isSeparating .
Une grande fenêtre est une fenêtre avec une largeur WindowSizeClass de et des classes de EXPANDED
taille de hauteur d’au moins MEDIUM
.
Exemple d’utilisation :
TwoPaneLayout(
paneMode = TwoPaneMode.HorizontalSingle,
pane1 = { Pane1Content() },
pane2 = { Pane2Content() }
)
Il existe quatre valeurs possibles paneMode
:
TwoPane
HorizontalSingle
VerticalSingle
SinglePane
TwoPane
TwoPane
est le mode de volet par défaut, et il affiche toujours deux volets lorsqu’il existe un pli séparant ou une grande fenêtre, quelle que soit l’orientation
HorizontalSingle
HorizontalSingle
affiche un grand volet lorsqu’il existe un pli de séparation horizontal ou une grande fenêtre portrait (combine les volets supérieur/inférieur).
VerticalSingle
VerticalSingle
affiche un grand volet lorsqu’il existe un pli de séparation vertical ou une grande fenêtre paysage (combine des volets gauche/droit).
SinglePane
SinglePane
affiche toujours un volet, quelles que soient les caractéristiques et l’orientation de la fenêtre.
Tableau de comportement en mode volet
Pour résumer, ce tableau explique quand un 🟩 ou deux 🟦🟦 volets seront affichés pour différents modes de volet et configurations d’appareil :
Mode volet | Petite fenêtre sans pli séparant | Fenêtre grand portrait / pli de séparation horizontal | Grande fenêtre paysage / pli de séparation verticale |
---|---|---|---|
TwoPane |
🟩 | 🟦🟦 | 🟦🟦 |
HorizontalSingle |
🟩 | 🟩 | 🟦🟦 |
VerticalSingle |
🟩 | 🟦🟦 | 🟩 |
SinglePane |
🟩 | 🟩 | 🟩 |
Naviguer dans TwoPaneLayout
TwoPaneLayout fournit deux interfaces avec des options de navigation interne. Selon le constructeur que vous utilisez, vous avez accès à différents champs et méthodes.
-
TwoPaneScope
: peut être utilisé avec les constructeurs TwoPaneLayout de base et TwoPaneLayout avec navController -
TwoPaneNavScope
: peut être utilisé avec le constructeur TwoPaneLayoutNav
TwoPaneScope
interface TwoPaneScope {
...
fun navigateToPane1()
fun navigateToPane2()
val currentSinglePaneDestination: String
...
}
Avec TwoPaneScope
, vous pouvez naviguer entre les volets 1 et 2 en mode mono-volet.
Vous pouvez également accéder à la route de la destination du seul volet actuel, qui est Screen.Pane1.route
ou Screen.Pane2.route
.
Exemple d'utilisation :
TwoPaneLayout(
pane1 = { Pane1Content(modifier = Modifier.clickable { navigateToPane2() }) },
pane2 = { Pane2Content(modifier = Modifier.clickable { navigateToPane1() }) }
)
TwoPaneNavScope
interface TwoPaneNavScope {
...
fun NavHostController.navigateTo(
route: String,
launchScreen: Screen,
builder: NavOptionsBuilder.() -> Unit = { }
)
fun NavHostController.navigateBack(): Boolean
val twoPaneBackStack: MutableList<TwoPaneBackStackEntry>
val currentSinglePaneDestination: String
val currentPane1Destination: String
val currentPane2Destination: String
val isSinglePane: Boolean
...
}
Avec TwoPaneNavScope
, vous pouvez accéder à différentes destinations en mode mono-volet ou bi-volet.
Vous pouvez également accéder aux routes des destinations actuelles, aussi bien la destination d’un seul volet que les destinations des volets 1 et 2. Ces valeurs dépendent des routes des destinations passées dans le constructeur TwoPaneLayoutNav
.
TwoPaneLayoutNav
gère une backstack interne, de sorte que l’historique de navigation est enregistré lors du basculement entre un et deux volets. Le comportement du composant par défaut prend en charge l’appui arrière uniquement en mode volet unique. Si vous souhaitez ajouter la gestion des appuis précédents en mode à deux volets, ou remplacer le comportement par défaut en mode à volet unique, créez un backHandler personnalisé dans votre composant composable qui appelle navigateBack
. Cela garantit que la backstack interne est correctement gérée. La backstack est également exposée via l’interface avec le champ, ce qui vous permet d’accéder à la twoPaneBackStack
taille et au contenu de la pile arrière si nécessaire.
Exemple d'utilisation :
val navController = rememberNavController()
TwoPaneLayoutNav(
navController = navController,
singlePaneStartDestination = "A",
pane1StartDestination = "A",
pane2StartDestination = "B"
) {
composable("A") {
ContentA(Modifier.clickable { navController.navigateTo("B", Screen.Pane2) })
}
composable("B") {
ContentB(Modifier.clickable { navController.navigateTo("C", Screen.Pane2) })
}
composable("C") {
ContentC(Modifier.clickable { navController.navigateTo("A", Screen.Pane1) })
}
}
Tester les composables TwoPaneLayout
Quand vous écrivez des tests d’interface utilisateur pour les composables utilisés dans TwoPaneLayout, vous pouvez utiliser des classes d’étendue de test pour configurer vos tests. Ces classes, TwoPaneScopeTest
et TwoPaneNavScopeTest
, fournissent des implémentations vides pour toutes les méthodes d’interface et vous permettent de définir des valeurs de champ dans le constructeur de classe.
class TwoPaneScopeTest(
currentSinglePaneDestination: String = "",
isSinglePane: Boolean = true
) : TwoPaneScope
class TwoPaneNavScopeTest(
currentSinglePaneDestination: String = "",
currentPane1Destination: String = "",
currentPane2Destination: String = "",
isSinglePane: Boolean = true
) : TwoPaneNavScope
Exemple d'utilisation :
// Composable function in app
@Composable
fun TwoPaneScope.Example() {
if (isSinglePane)
Text("single pane")
else
Text("two pane")
}
...
// UI test in androidTest directory
@Test
fun exampleTest() {
composeTestRule.setContent {
val twoPaneScope = TwoPaneScopeTest(isSinglePane = true)
twoPaneScope.Example()
}
composeTestRule.onNodeWithText("single pane").assertIsDisplayed()
composeTestRule.onNodeWithText("two pane").assertDoesNotExist()
}