Кастомные контейнеры компоновки

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

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

Каждый раз, когда вызывается родительский компонуемый объект, он отвечает за управление размером и расположением всех своих дочерних элементов. Положение дочернего элемента определяется с использованием координат x и y относительно положения родителя. Что касается размера, родительский элемент накладывает ограничения, которые определяют максимально и минимально допустимые размеры высоты и ширины дочернего элемента. В зависимости от конфигурации размер родительского компонента может быть либо фиксированным (например, с помощью модификатора size()), либо рассчитан на основе размера и расположения его дочерних элементов. Все встроенные контейнеры - Box, Row и Column содержат логику, которая измеряет каждого дочернего компонента и вычисляет, как расположить каждый из них. И мы также можем использовать для создания собственных контейнеров все те же методы, которые применяются встроенными контейнерами.

Кастомные контейнеры компоновки можно разделить на две категории:

  • В самой базовой форме пользовательский контейнер может быть реализован как модификатор компоновк, который можно применить к одному элементу пользовательского интерфейса (что-то похожее на стандартный модификатор padding())

  • Второй способ представляет создание своего объекта Layout, который будет применяться ко всем дочерним компонентам (аналогично Box, Column и Row)

Создание кастомного модификатора компоновки

Общий синтаксис реализации кастомного модификатора компоновки выглядит следующим образом:

fun Modifier.имя_модификатора (необязательные_параметры) = layout { measurable, constraints ->

    // код настройки позиции и размера компонента
}

Концевой лямбде передаются два параметра с именами measurable и constraints. Параметр measurable представляет дочерний компонент, для которого был вызван модификатор, а параметр constraints содержит максимальную и минимальную ширину и высоту, разрешенные для дочернего компонента.

Когда модификатор размещает дочерний компонент, ему необходимо знать размеры этого компонента, чтобы убедиться, что он соответствует ограничениям constraints, переданным в лямбда-выражение. Эти значения можно получить, вызвав у объекта measurable метод measure(), в который передается параметр constraints:

val placeable = measurable.measure(constraints)

Этот вызов вернет объект класса Placeable, который в свойствах width и height содержит значения высоты и ширины. Тип Placeable предоставляет ряд методов для установки новой позиции компонента в контейнере:

  • place()

  • placeRelative()

  • placeRelativeWithLayer()

Эти методы имеют множество различных версий, но все они помещают компонент на определенную позицию внутри контейнера.

Например определим модификатор компоновки, который помещает компонент на определенную позицию с координатами X и Y относительно позиции по умолчанию:

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.runtime.Composable
import androidx.compose.ui.layout.layout

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent{
            Box(Modifier.fillMaxSize()) {
                MyBox(Modifier.positionLayout(100, 50).background(Color.DarkGray))
            }
        }
    }
}

fun Modifier.positionLayout(x: Int, y: Int) = layout { measurable, constraints ->
    val placeable = measurable.measure(constraints)
    layout(placeable.width, placeable.height) {
        placeable.placeRelative(x, y)
    }
}

@Composable
fun MyBox(modifier: Modifier) {
    Box(Modifier.size(200.dp).then(modifier))
}

Здесь определен кастомный модификатор компоновки positionLayout (название произвольное). Пусть в качестве параметров он принимает координаты верхнего левого угла компонента относительно позиции по умолчанию (если компонент один, то это будет позиция верхнего левого угла контейнера )- параметры x и y.

fun Modifier.positionLayout(x: Int, y: Int) = layout { measurable, constraints ->
    val placeable = measurable.measure(constraints)
    layout(placeable.width, placeable.height) {
        placeable.placeRelative(x, y)
    }
}

В концевой лямбде получаем объект Placeable

val placeable = measurable.measure(constraints)

Затем вызывается функция layout():

layout(placeable.width, placeable.height) {
    placeable.placeRelative(x, y)
}

Эта функция получает высоту и ширину объекта Placeable. В качестве последнего параметра в функцию layout передается лямбда-выражение, которое собственно и выполняет позиционирование компонента. В частности, для этого мы используем метод placeable.placeRelative(), который принимает координаты x и y, по которым надо поместить компонент (координаты относительно верхнего левого угла контейнера).

Для примера здесь определяется кастомный компонент MyBox, который по сути является оберткой над Box:

@Composable
fun MyBox(modifier: Modifier) {
    Box(Modifier.size(200.dp).then(modifier))
}

К Box применяется модификатор, который устанавливает размер прямоугольной области в 200 пикселей и добавляет функции модификаторов, которые передаются в компонент MyBox через единственный параметр.

ПРи вызове метода setContent() применяем наш модификатор positionLayout:

MyBox(Modifier.positionLayout(100, 50).background(Color.DarkGray))

ТО есть в данном случае компонент будет позиционироваться на координату с x=100 и y=50. И кроме того, к нему применяется темно-серый фон:

Создание кастомного модификатора компоновки в мобильном приложении на Jetpack Compose на Kotlin на Android

Причем опять же подчеркну, что позиция устанавливается относительно точки по умолчанию (в данном случае верхний левый угол контейнера). И если наш контейнер MyBox помещается в другой контейнер, то к MyBox применяются также отступы родительского контейнера. Например:

Box(Modifier.padding(10.dp, 40.dp)) {
    MyBox(Modifier.positionLayout(100, 50).background(Color.DarkGray))
}

В данном случае реальная позиция MyBox будет на точке x=(10+100)=110 и y=(40+50)=90.

Также при разработке своих макетов компоновки следует помнить, что дочерний компонент необходимо измерять только один раз при каждом вызове модификатора. Это так называемое однократное измерение (single-pass measurement) необходимо для обеспечения быстрой и эффективной визуализации иерархий дерева пользовательского интерфейса.

Другой пример - кастомный модификатор margin(), который устанавливает внешние отступы, но его мезаника будет аналогична:

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.runtime.Composable
import androidx.compose.ui.layout.layout

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent{
            Box(Modifier.fillMaxSize()) {
                MyBox(Modifier.margin(200).background(Color.DarkGray))
            }
        }
    }
}

fun Modifier.margin(all:Int) = layout { measurable, constraints ->
    val placeable = measurable.measure(constraints)
    layout(placeable.width, placeable.height) {
        placeable.placeRelative(all, all)
    }
}

@Composable
fun MyBox(modifier: Modifier) {
    Box(Modifier.size(200.dp).then(modifier))
}
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850