Создание контейнеров компоновки

Последнее обновление: 07.04.2024

Jetpack Compose позволяет создавать собственные компоненты компоновки с полным контролем над размером и расположением всех дочерних компонентов. Для создания контейнеров компоновки применяется встроенная функция Layout, которая предоставляет способ измерения и позиционирования дочерних компонентов. Функция Layout имеет ряд версий, которые похожи. Возьмем одну из них:

@UiComposable
@Composable
inline fun Layout(
    content: @Composable @UiComposable () -> Unit,
    modifier: Modifier = Modifier,
    measurePolicy: MeasurePolicy
): Unit

В общем случае функция-компонент Layout принимает три параметра:

  • content: содержимое

  • modifier: функции-модификаторы

  • measurePolicy: политика компоновки вложенного содержимого в виде объекта MeasurePolicy

В начале рассмотрим общий принцип создания контейнера компоновки. Для демонстрации возьмем самое просто определение контейнера:

@Composable
fun MyLayout(modifier: Modifier = Modifier, content: @Composable () -> Unit){
    Layout(modifier = modifier, content = content) {
        measurables, constraints ->
            val placeables = measurables.map { measurable ->
                // измеряем каждый дочерний компонент
                measurable.measure(constraints)
            }
            layout(constraints.maxWidth, constraints.maxHeight) {
                placeables.forEach { placeable ->
                    placeable.placeRelative(x = 0, y = 0)
                }
            }
    }
}

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

Далее функция MyLayout вызывает компонент Layout, в который передаются модификаторы и содержимое. Третий параметр функции Layout в данном случае представляет концевую лямбду, которая принимает два параметра: measurables и constraints. Параметр measurables содержит все дочерние компоненты, которые передаются в Layout через content (список объектов Measurable). А параметр constraints содержит максимальную и минимальную ширину и высоту, допустимую для дочерних компонентов.

 Layout(modifier = modifier, content = content) {
    measurables, constraints -> 
    ..................

Далее выполняется измерение дочерних компонентов, а результаты помещаются в список объектов Placeable:

val placeables = measurables.map { measurable ->
    // измеряем каждый дочерний компонент
    measurable.measure(constraints)
}

Здесь метод map() проходит по всем дочерним компонентам (объектам Measurable) и для каждого из них выполняет метод measure(), который, в свою очередь, измеряет дочерний компонент. Результатом является список экземпляров Placeable (по одному для каждого дочернего компонента).

В конце вызывается функция layout(), которой передаются максимальные значения высоты и ширины, разрешенные родительским элементом:

layout(constraints.maxWidth, constraints.maxHeight) {
    placeables.forEach { placeable ->
        placeable.placeRelative(x = 0, y = 0)
    }
}

Концевая лямбда перебирает все дочерние компоненты (которые теперь представляют объекты Placeable) с помощью функции placeables.forEach() и позиционирует их относительно позиции по умолчанию, назначенной родительским элементом. В данном случае для простоты позиционирование производится на точку с координатами x=0 и y=0.

Рассмотрим применение своего контейнера компоновки:

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.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.runtime.Composable
import androidx.compose.ui.layout.Layout

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent{
            Box(Modifier.fillMaxSize()) {
                MyLayout(spacing = 20){
                    Box(Modifier.size(100.dp).background(Color.DarkGray))
                    Box(Modifier.size(100.dp).background(Color.Red))
                    Box(Modifier.size(100.dp).background(Color.Blue))
                    Box(Modifier.size(100.dp).background(Color.Magenta))
                    Box(Modifier.size(100.dp).background(Color.Green))
                }
            }
        }
    }
}

@Composable
fun MyLayout(modifier: Modifier = Modifier, spacing: Int = 0, content: @Composable () -> Unit){
    Layout(modifier = modifier,content = content) {
        measurables, constraints ->
            val placeables = measurables.map { measurable ->
                measurable.measure(constraints)
            }
            layout(constraints.maxWidth, constraints.maxHeight) {
                var yCoord = 0
                var xCoord = 0
                placeables.forEach { placeable ->
                    // размещаем текущий дочерний компонент
                    placeable.placeRelative(x = xCoord, y = yCoord)
                    // устанавливаем x-координату для следующего компонента
                    xCoord += placeable.width + spacing
                    // если дошли до края контейнера
                    if((xCoord + placeable.width) > constraints.maxWidth) {
                        // увеличиваем y-координату (переход на следующую строку)
                        yCoord += placeable.height + spacing
                        // и сбрасываем x-координату
                        xCoord = 0
                    }
                }
            }
    }
}

Здесь наш кастомный контейнер MyLayout принимает три параметра:

@Composable
fun MyLayout(modifier: Modifier = Modifier, spacing: Int = 0, content: @Composable () -> Unit){

Кроме стандартных параметров типа модификатора и содержимого контейнер также принимает параметр spacing - отступ между вложенными компонентами. По умолчанию он равен 0, соответственно параметр является необязательным для установки.

Внутри MyLayout в компоненте Layout выполняем размещение каждого вложенного компонента с помощью следующего кода:

layout(constraints.maxWidth, constraints.maxHeight) {
    var yCoord = 0
    var xCoord = 0
    placeables.forEach { placeable ->
        placeable.placeRelative(x = xCoord, y = yCoord)
        xCoord += placeable.width + spacing
        if((xCoord + placeable.width) > constraints.maxWidth) {
            yCoord += placeable.height + spacing
            xCoord = 0
        }
    }
}

Для установки координат x и y здесь определены дополнительные переменные xCoord и yCoord соответственно. ПРи переборе каждого вложенного компонента вначале устанавливаем его позицию на основании переменных xCoord и yCoord:

placeable.placeRelative(x = xCoord, y = yCoord)

Затем увеличиваем значение координаты x на ширину размещенного компонента плюс отступ:

xCoord += placeable.width + spacing

Если координата x вышла за диапазон допустимой ширины, то сбрасываем переменную xCoord и увеличиваем переменную yCoord на высоту размещенного компонента плюс отступ. ТО есть таким образом мы перейдем на следующую строку:

if((xCoord + placeable.width) > constraints.maxWidth) {
    yCoord += placeable.height + spacing
    xCoord = 0
}

Для установки следующего компонента, таким образом, будут применяться уже измененные значения xCoord и yCoord.

ПРименение нашего контейнера компоновки аналогично другим компонентам. В данном случае устанавливаем параметр spacing и размещаем внутри несколько типовых компонентов:

MyLayout(spacing = 20){
    Box(Modifier.size(100.dp).background(Color.DarkGray))
    Box(Modifier.size(100.dp).background(Color.Red))
    Box(Modifier.size(100.dp).background(Color.Blue))
    Box(Modifier.size(100.dp).background(Color.Magenta))
    Box(Modifier.size(100.dp).background(Color.Green))
}
Создание кастомного контейнера компоновки в мобильном приложении на Jetpack Compose на Kotlin на Android
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850