ConstraintLayout dans Compose

ConstraintLayout est une mise en page qui vous permet de positionner des composables par rapport à d'autres composables sur l'écran. Il s'agit d'une alternative à l'utilisation de plusieurs éléments Row, Column et Box imbriqués et d'éléments de mise en page personnalisés. ConstraintLayout s'avère particulièrement utile pour implémenter des mises en page de grande taille qui présentent des exigences plus complexes en termes d'alignement.

Envisagez d'utiliser ConstraintLayout dans les scénarios suivants :

  • Pour éviter d'imbriquer plusieurs éléments Column et Row afin de positionner des éléments à l'écran et d'améliorer la lisibilité du code
  • Pour positionner des composables par rapport à d'autres composables ou conformément à des lignes, des barrières ou des chaînes.

Dans le système View, ConstraintLayout était la méthode recommandée pour créer des mises en page complexes et de grande taille, car une hiérarchie des vues plate était plus performante que les vues imbriquées. Toutefois, cela n'est pas un problème dans Compose qui est capable de gérer efficacement des hiérarchies de mise en page très profondes.

Inscrivez-vous à ConstraintLayout

Pour utiliser ConstraintLayout dans Compose, vous devez ajouter cette dépendance dans build.gradle (en plus de la configuration de Compose) :

implementation "androidx.constraintlayout:constraintlayout-compose:1.0.1"

Dans Compose, ConstraintLayout fonctionne de la manière suivante à l'aide d'un DSL :

  • Créez des références pour chaque composable dans ConstraintLayout à l'aide de createRefs() ou de createRefFor().
  • Les contraintes sont fournies à l'aide du modificateur constrainAs() qui utilise la référence comme paramètre et vous permet de spécifier ses contraintes dans le lambda "body".
  • Les contraintes sont spécifiées à l'aide de linkTo() ou d'autres méthodes utiles.
  • parent est une référence existante qui peut être utilisée pour spécifier des contraintes par rapport au composable ConstraintLayout proprement dit.

Voici un exemple de composable qui utilise ConstraintLayout :

@Composable
fun ConstraintLayoutContent() {
    ConstraintLayout {
        // Create references for the composables to constrain
        val (button, text) = createRefs()

        Button(
            onClick = { /* Do something */ },
            // Assign reference "button" to the Button composable
            // and constrain it to the top of the ConstraintLayout
            modifier = Modifier.constrainAs(button) {
                top.linkTo(parent.top, margin = 16.dp)
            }
        ) {
            Text("Button")
        }

        // Assign reference "text" to the Text composable
        // and constrain it to the bottom of the Button composable
        Text(
            "Text",
            Modifier.constrainAs(text) {
                top.linkTo(button.bottom, margin = 16.dp)
            }
        )
    }
}

Ce code contraint le haut du Button par rapport à l'élément parent avec une marge de 16.dp et un élément Text par rapport au bas du Button, également avec une marge de 16.dp.

Affiche un bouton et un élément textuel disposés dans un élément ConstraintLayout

API dissociée

Dans l'exemple ConstraintLayout, les contraintes sont spécifiées de manière intégrée, avec un modificateur dans le composable auquel elles s'appliquent. Toutefois, il est parfois préférable de dissocier les contraintes des mises en page auxquelles elles s'appliquent. Par exemple, vous pouvez modifier les contraintes en fonction de la configuration de l'écran ou animer deux ensembles de contraintes.

Dans de tels cas, vous pouvez utiliser ConstraintLayout d'une autre manière :

  1. Transmettez un ConstraintSet en tant que paramètre à ConstraintLayout.
  2. Attribuez des références créées dans le ConstraintSet à des composables à l'aide du modificateur layoutId.

@Composable
fun DecoupledConstraintLayout() {
    BoxWithConstraints {
        val constraints = if (minWidth < 600.dp) {
            decoupledConstraints(margin = 16.dp) // Portrait constraints
        } else {
            decoupledConstraints(margin = 32.dp) // Landscape constraints
        }

        ConstraintLayout(constraints) {
            Button(
                onClick = { /* Do something */ },
                modifier = Modifier.layoutId("button")
            ) {
                Text("Button")
            }

            Text("Text", Modifier.layoutId("text"))
        }
    }
}

private fun decoupledConstraints(margin: Dp): ConstraintSet {
    return ConstraintSet {
        val button = createRefFor("button")
        val text = createRefFor("text")

        constrain(button) {
            top.linkTo(parent.top, margin = margin)
        }
        constrain(text) {
            top.linkTo(button.bottom, margin)
        }
    }
}

Lorsque vous devrez modifier les contraintes, il vous suffira de transmettre un autre élément ConstraintSet.

Concepts ConstraintLayout

ConstraintLayout implique des concepts tels que des lignes, des barrières et des chaînes qui peuvent aider à positionner des éléments dans votre composable.

Lignes

Les lignes sont de petits outils visuels permettant de concevoir des mises en page. Les composables peuvent se limiter à une ligne. Les lignes sont utiles pour positionner des éléments à un certain niveau (dp ou percentage ) dans le composable parent.

Il existe deux types de lignes : verticales et horizontales. Les deux lignes horizontales sont top et bottom, et les deux verticales sont start et end.

ConstraintLayout {
    // Create guideline from the start of the parent at 10% the width of the Composable
    val startGuideline = createGuidelineFromStart(0.1f)
    // Create guideline from the end of the parent at 10% the width of the Composable
    val endGuideline = createGuidelineFromEnd(0.1f)
    //  Create guideline from 16 dp from the top of the parent
    val topGuideline = createGuidelineFromTop(16.dp)
    //  Create guideline from 16 dp from the bottom of the parent
    val bottomGuideline = createGuidelineFromBottom(16.dp)
}

Pour créer une ligne, utilisez createGuidelineFrom* avec le type approprié. Cette opération crée une référence qui pourra être utilisée dans le bloc Modifier.constrainAs().

Barrières

Les barrières référencent plusieurs composables pour créer une ligne virtuelle basée sur le widget le plus extrême du côté spécifié.

Pour créer une barrière, utilisez createTopBarrier() (ou createBottomBarrier(), createEndBarrier(), createStartBarrier()) et fournissez les références qui doivent la constituer.

ConstraintLayout {
    val constraintSet = ConstraintSet {
        val button = createRefFor("button")
        val text = createRefFor("text")

        val topBarrier = createTopBarrier(button, text)
    }
}

Vous pourrez ensuite utiliser la barrière dans un bloc Modifier.constrainAs().

Chaînes

Les chaînes fournissent un comportement semblable à celui d'un groupe sur un seul axe (horizontal ou vertical). L'autre axe peut être contraint indépendamment.

Pour créer une chaîne, utilisez createVerticalChain ou createHorizontalChain :

ConstraintLayout {
    val constraintSet = ConstraintSet {
        val button = createRefFor("button")
        val text = createRefFor("text")

        val verticalChain = createVerticalChain(button, text, chainStyle = ChainStyle.Spread)
        val horizontalChain = createHorizontalChain(button, text)
    }
}

La chaîne pourra ensuite être utilisée dans le bloc Modifier.constrainAs().

Une chaîne peut être configurée avec différents ChainStyles, qui déterminent comment gérer l'espace autour d'un composable, par exemple :

  • ChainStyle.Spread : l'espace est réparti uniformément entre tous les composables, y compris l'espace libre avant le premier composable et après le dernier.
  • ChainStyle.SpreadInside : l'espace est réparti uniformément entre tous les composables, sans espace libre avant le premier composable ou après le dernier.
  • ChainStyle.Packed : l'espace est distribué avant le premier composable et après le dernier. Les composables sont regroupés sans espace pour les séparer.

En savoir plus

Pour en savoir plus sur ConstraintLayout dans Compose à partir des API en action, consultez les exemples Compose qui utilisent ConstraintLayout.