ConstraintLayout в Compose

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

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

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

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

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

Для использования ConstraintLayout в Compose необходимо добавить следующую зависимость в файл build.gradle (в дополнение к настройкам Compose ):

implementation "androidx.constraintlayout:constraintlayout-compose:$constraintlayout_compose_version"

В Compose ConstraintLayout работает следующим образом, используя 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

Кнопка расположена над текстом.
Рисунок 1. Button и Text которые можно скомпоновать, будучи связанными друг с другом в 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 содержит такие понятия, как направляющие, барьеры и цепочки, которые могут помочь в позиционировании элементов внутри вашего составного элемента.

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

Направляющие линии — это небольшие визуальные вспомогательные элементы для проектирования макетов. Элементы, являющиеся составными частями, могут быть привязаны к направляющей линии. Направляющие линии полезны для позиционирования элементов на определенном расстоянии 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 .

{% verbatim %} {% endverbatim %} {% verbatim %} {% endverbatim %}