Хотя мы можем так делать, как в примере выше, но в этом смысла нет, поскольку в этом случае проще воспользоваться стандартными типами контейнеров типа Box. Ключевой момент ConstraintLayout заключается именно в установке ограничений для вложенных компонентов. Наиболее распространенная форма ограничения — это ограничения относительно родительского контейнера ConstraintLayout или другого компонента.
Для установки для компонента ограничений этому компоненту необходимо назначить ссылку. Назначение ссылки представляет двухэтапный процесс, который состоит из создания ссылки и последующего ее назначения компонентам перед применением ограничений. Ccылка представляет объект класса ConstrainedLayoutReference, и одну ссылку можно создать с помощью вызова функции createRef() и затем присвоить результат константе:
val box1 = createRef()
Для создания нескольких ссылок можно использовать функцию createRefs():
val (box1, text1, text2) = createRefs()
После создания ссылок они применяются к отдельным компонентам с помощью функции-модификатора constrainAs():
Modifier.constrainAs( ref: ConstrainedLayoutReference, constrainBlock: ConstrainScope.() -> Unit )
Этот модификатор принимает два параметра:
Первый параметр - это созданная ссылка в виде объекта ConstrainedLayoutReference
Второй параметр представляет функцию, которая устанавливает ограничения.
Например, следующий код присваивает ссылку box1 компоненту Box:
ConstraintLayout { val box1 = createRef() Box(modifier = Modifier.constrainAs(box1) { // здесь идут ограничения }) }
Здесь в компоненте Box модификатору constrainAs() передается ссылка "box1". Таким образом, далее мы сможем ссылаться на этот компонент Box через ссылку box1.
Второй параметр модификатора constrainAs() представляет функцию, в которой и устанавливаются ограничения. Для этого внутри данной функции можно использовать свойства класса ConstrainScope/ConstrainedLayoutReference:
absoluteLeft
: левый край компонента
absoluteRight
: правый край компонента
baseline
: базовая линия компонента
bottom
: нижний край компонента
top
: верхний край компонента
start
: начало компонента (правый или левый крайв зависимости от направления текста)
end
: конец компонента (правый или левый крайв зависимости от направления текста)
Кроме того, ConstrainScope предоставляет специальное свойство parent, который представляет ссылку на родительский контейнер в виде объекта
ConstrainedLayoutReference
и через который можно обратиться к этим же свойствам родительского контейнера.
Одним из способов установить ограничения представляет вызов функции linkTo(). Она вызывается у выше рассмотренных свойств. Например:
package com.example.helloapp import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstraintLayout import androidx.compose.ui.tooling.preview.Preview class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent{ ConstraintLayout { val rect = createRef() Box(Modifier.size(200.dp).background(Color.DarkGray).constrainAs(rect) { top.linkTo(parent.top, margin = 16.dp) }) } } } }
В данном случае строка
top.linkTo(parent.top, margin = 16.dp)
представляет установку ограничения для верхнего края компонента Box (свойство top
). Соответственно выражение top.linkTo()
говорит установить ограничение для
верхнего края компонента Box.
Настройки ограничения передаются в вызов функции. Первый аргумент - относительно какой стороны надо установить ограничение, а второй аргумент - отступ. В данном случае первый аргумент -
parent.top
указывает на верхний край родительского контейнера, а второй аргумент - margin = 16.dp
задает отступ в 16 пикселей.
Таким образом, этот код ограничивает верхний край компонента Box верхним краем родительского экземпляра ConstraintLayout с отступом в 16 пикселей:
Подобным образом можно установить и другие ограничения:
package com.example.helloapp import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstraintLayout class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent{ ConstraintLayout(Modifier.fillMaxSize()) { val box1 = createRef() Box(Modifier.size(200.dp).background(Color.DarkGray).constrainAs(box1) { absoluteLeft.linkTo(parent.absoluteLeft, margin = 16.dp) // отступ слева top.linkTo(parent.top, margin = 16.dp) // отступ сверху }) } } } }
Для установки ограничений ConstrainScope также предоставляет другую форму функции linkTo(), которая может передавать несколько ограничений в качестве параметров. Однак из ее форм:
fun linkTo( start: ConstraintLayoutBaseScope.VerticalAnchor, top: ConstraintLayoutBaseScope.HorizontalAnchor, end: ConstraintLayoutBaseScope.VerticalAnchor, bottom: ConstraintLayoutBaseScope.HorizontalAnchor, startMargin: Dp = 0.dp, topMargin: Dp = 0.dp, endMargin: Dp = 0.dp, bottomMargin: Dp = 0.dp, startGoneMargin: Dp = 0.dp, topGoneMargin: Dp = 0.dp, endGoneMargin: Dp = 0.dp, bottomGoneMargin: Dp = 0.dp, horizontalBias: @FloatRange(from = 0.0, to = 1.0) Float = 0.5f, verticalBias: @FloatRange(from = 0.0, to = 1.0) Float = 0.5f ): Unit
Первый 4 параметра устанавливаются ограничения для соответственно сторон start, top, end, bottom текущего компонента, а следующие 4 параметра настраивают отступы.
Пример применения:
package com.example.helloapp import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstraintLayout class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent{ ConstraintLayout(Modifier.fillMaxSize()) { val box1 = createRef() Box(Modifier.size(200.dp).background(Color.DarkGray).constrainAs(box1) { linkTo(start=parent.start,top=parent.top, end = parent.end, bottom=parent.bottom, 16.dp, 16.dp, 16.dp, 16.dp) }) } } } }
Аналогичным образом можно устанавливать ограничения относительно других компонентов:
package com.example.helloapp import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstraintLayout class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent{ ConstraintLayout(Modifier.fillMaxSize()) { val box1 = createRef() val box2 = createRef() Box(Modifier.size(150.dp).background(Color.DarkGray).constrainAs(box1) { absoluteLeft.linkTo(parent.absoluteLeft, margin = 16.dp) // отступ слева top.linkTo(parent.top, margin = 16.dp) // отступ сверху }) Box(Modifier.size(150.dp).background(Color.Red).constrainAs(box2) { absoluteLeft.linkTo(box1.absoluteRight, margin = 16.dp) // отступ слева от box1 top.linkTo(parent.top, margin = 16.dp) // отступ сверху }) } } } }
Здесь создается две ссылки - box1 и box2 для двух компонентов Box. Компонент box1 устанавливает ограничения относительно верхней и левой стороны контейнера. А компонент box2 устанавливает ограничение левой стороны относительно правой стороны компонента box1 с отступом в 16 пикселей.
Кроме функции linkTo()
для установки ограничений можно применять ряд функций, который центрируют компонент относительно родительского контейнера или других компонентов:
centerHorizontallyTo()
: располагает по центру по горизонтали
centerVerticallyTo()
: располагает по центру по вертикали
centerAround()
: центрирует по определенной стороне контейнера или другого компонента
centerTo()
: центрирует одновременно и по горизонтали и по вертикали
Например,
package com.example.helloapp import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstraintLayout class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent{ ConstraintLayout(Modifier.fillMaxSize()) { val box1 = createRef() val box2 = createRef() Box(Modifier.size(150.dp).background(Color.DarkGray).constrainAs(box1) { absoluteLeft.linkTo(parent.absoluteLeft, margin = 16.dp) // отступ слева top.linkTo(parent.top, margin = 16.dp) // отступ сверху }) Box(Modifier.size(100.dp).background(Color.Red).constrainAs(box2) { centerVerticallyTo(box1) centerHorizontallyTo(parent) }) } } } }
В данном случае box2 центрируется по горизонтали относительно родительского контейнера, а по вертикали располагается по центру box1:
Центрирование с помощью centerAround()
ConstraintLayout(Modifier.fillMaxSize()) { val box1 = createRef() val box2 = createRef() Box(Modifier.size(150.dp).background(Color.DarkGray).constrainAs(box1) { absoluteLeft.linkTo(parent.absoluteLeft, margin = 16.dp) top.linkTo(parent.top, margin = 16.dp) }) Box(Modifier.size(100.dp).background(Color.Red).constrainAs(box2) { centerAround(box1.absoluteRight) centerAround(box1.bottom) }) }
Различные компоненты могут взаимно устанавливать ограничения относительно друг друга. Например:
package com.example.helloapp import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.constraintlayout.compose.ConstraintLayout import androidx.compose.ui.unit.sp class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent{ ConstraintLayout(Modifier.fillMaxSize()) { val (text1, text2) = createRefs() Text("TV", fontSize = 54.sp, modifier = Modifier.constrainAs(text1) { centerHorizontallyTo(parent) top.linkTo(parent.top) bottom.linkTo(text2.top) }) Text("Radio", fontSize = 54.sp, modifier = Modifier.constrainAs(text2) { centerHorizontallyTo(parent) top.linkTo(text1.bottom) bottom.linkTo(parent.bottom) }) } } } }
Здесь оба компонента Text располагаются по центру контейнера по горизонтали. Но при этом верхняя граница text2 проходит по нижней границе text1, тогда как нижняя граница text1 - по верхней границе text2.
Применение смещения позволяет перемещать компонент относительно доступного пространства:
package com.example.helloapp import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstraintLayout class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent{ ConstraintLayout(Modifier.fillMaxSize()) { val (box1, box2) = createRefs() Box(Modifier.size(150.dp).background(Color.DarkGray).constrainAs(box1) { top.linkTo(parent.top, margin = 60.dp) linkTo(parent.start, parent.end) }) Box(Modifier.size(150.dp).background(Color.Red).constrainAs(box2) { top.linkTo(parent.top, margin = 60.dp) linkTo(parent.start, parent.end, bias = 0.75f) }) } } } }
Здесь оба компонента Box по умолчанию позиционируются в одно и то же место, однако box2 при этом сдвинут на 75% доступной ширины: