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, которые используют ConstraintLayout .

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