ConstraintLayout в Compose

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

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

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

В системе представлений 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 .

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