ConstraintLayout в Compose

ConstraintLayout — это макет, позволяющий размещать компонуемые элементы относительно других компонуемых элементов на экране. Это альтернатива использованию нескольких вложенных Row , Column , Box и других пользовательских элементов макета . ConstraintLayout полезен при реализации более крупных макетов с более сложными требованиями к выравниванию.

Рассмотрите возможность использования ConstraintLayout в следующих сценариях:

  • Чтобы избежать вложения нескольких Column и Row для позиционирования элементов на экране и улучшить читаемость кода.
  • Для позиционирования компонуемых объектов относительно других компонуемых объектов или для позиционирования компонуемых объектов на основе направляющих, барьеров или цепей.

В системе View ConstraintLayout был рекомендуемым способом создания больших и сложных макетов, поскольку плоская иерархия представлений обеспечивала лучшую производительность, чем вложенные представления. Однако в Compose это не проблема, поскольку он способен эффективно обрабатывать глубокие иерархии макетов.

Начните работу с ConstraintLayout

Чтобы использовать ConstraintLayout в Compose, вам необходимо добавить эту зависимость в ваш build.gradle (в дополнение к настройке Compose ):

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

ConstraintLayout в Compose работает следующим образом с использованием DSL :

  • Создайте ссылки для каждого компонуемого объекта в ConstraintLayout с помощью createRefs() или createRefFor()
  • Ограничения предоставляются с помощью модификатора constrainAs() , который принимает ссылку как параметр и позволяет указать ее ограничения в лямбда-выражении тела.
  • Ограничения задаются с помощью linkTo() или других полезных методов.
  • parent — существующая ссылка, которая может использоваться для указания ограничений для самого компонуемого объекта ConstraintLayout .

Вот пример компонуемого объекта с использованием 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)
            }
        )
    }
}

Этот код ограничивает верхнюю часть Button родительским элементом с отступом 16.dp и Text нижней части Button также с отступом 16.dp

Показывает кнопку и текстовый элемент, расположенные в ConstraintLayout.

Разделенный API

В примере ConstraintLayout ограничения задаются в строке, с модификатором в компонуемом элементе, к которому они применяются. Однако бывают ситуации, когда предпочтительнее отделить ограничения от макетов, к которым они применяются. Например, может потребоваться изменить ограничения в зависимости от конфигурации экрана или реализовать анимацию между двумя наборами ограничений.

В подобных случаях можно использовать ConstraintLayout другим способом:

  1. Передайте ConstraintSet в качестве параметра в ConstraintLayout .
  2. Назначьте ссылки, созданные в ConstraintSet , компонуемым элементам с помощью модификатора 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)
        }
    }
}

Затем, когда вам потребуется изменить ограничения, вы можете просто передать другой ConstraintSet .

Концепции ConstraintLayout

ConstraintLayout содержит такие концепции, как направляющие, барьеры и цепи, которые могут помочь в позиционировании элементов внутри Composable.

Руководящие принципы

Направляющие — это небольшие визуальные помощники для проектирования макетов. Компонуемые элементы можно ограничить направляющей. Направляющие полезны для позиционирования элементов на определённом dp или percentage внутри родительского компонуемого элемента.

Существует два типа направляющих : вертикальные и горизонтальные. Две горизонтальные — top и bottom , а две вертикальные — start и 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)
}

Чтобы создать направляющую, используйте createGuidelineFrom* с нужным типом направляющей. Это создаст ссылку, которую можно использовать в блоке Modifier.constrainAs() .

Барьеры

Барьеры ссылаются на несколько компонуемых элементов для создания виртуальной направляющей на основе самого крайнего виджета на указанной стороне.

Чтобы создать барьер, используйте createTopBarrier() (или: createBottomBarrier() , createEndBarrier() , createStartBarrier() ) и укажите ссылки, которые должны составлять барьер.

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

        val topBarrier = createTopBarrier(button, text)
    }
}

Затем барьер можно использовать в блоке Modifier.constrainAs() .

Цепи

Цепи обеспечивают групповое поведение по одной оси (горизонтальной или вертикальной). Другая ось может быть ограничена независимо.

Чтобы создать цепочку, используйте createVerticalChain или 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)
    }
}

Затем цепочку можно использовать в блоке Modifier.constrainAs() .

Цепочку можно настроить с помощью различных ChainStyles , которые определяют, как работать с пространством вокруг компонуемого элемента, например:

  • ChainStyle.Spread : Пространство равномерно распределяется по всем компонуемым элементам, включая свободное пространство перед первым компонуемым элементом и после последнего компонуемого элемента.
  • ChainStyle.SpreadInside : Пространство равномерно распределяется по всем компонуемым элементам, без свободного пространства перед первым компонуемым элементом или после последнего компонуемого элемента.
  • ChainStyle.Packed : Пространство распределяется перед первым и после последнего компонуемого элемента, компонуемые элементы упаковываются вместе без пробелов между ними.

Узнать больше

Узнайте больше о ConstraintLayout в Compose из API-интерфейсов, используемых в примерах Compose, которые используют ConstraintLayout .

{% дословно %} {% endverbatim %} {% дословно %} {% endverbatim %}