Ограничения и порядок применения модификаторов

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

На этой странице описывается, как цепочки модификаторов влияют на ограничения и, в свою очередь, на измерение и размещение составных элементов.

Модификаторы в дереве интерфейса

Чтобы понять, как модификаторы влияют друг на друга, полезно визуализировать, как они появляются в дереве пользовательского интерфейса, которое генерируется на этапе композиции. Подробнее об этом рассказывается в разделе Состав.

В дереве пользовательского интерфейса модификаторы можно представить как узлы-оболочки для узлов макета:

Код для composable-функций и модификаторов, а также их визуальное представление в виде дерева пользовательского интерфейса.
Рисунок 1. Модификаторы, которые оборачивают узлы макета в дереве пользовательского интерфейса.

Если добавить к composable-функции несколько модификаторов, они образуют цепочку. Когда вы связываете несколько модификаторов, каждый узел модификатора оборачивает остальную часть цепочки и узел макета внутри. Например, если вы объединяете модификаторы clip и size, узел модификатора clip оборачивает узел модификатора size, который, в свою очередь, оборачивает узел макета Image.

На этапе макета алгоритм, который обходит дерево, остается прежним, но также посещается каждый узел модификатора. Таким образом, модификатор может изменить требования к размеру и размещение модификатора или узла макета, который он оборачивает.

Как показано на рисунке 2, реализация composable-функций Image и Text состоит из цепочки модификаторов, которые оборачивают один узел макета.

Реализации Row и Column – это узлы макета, которые описывают, как размещать дочерние элементы.

Древовидная структура, как и раньше, но теперь каждый узел представляет собой простой макет с множеством узлов-оберток модификаторов вокруг него.
Рисунок 2. Та же древовидная структура, что и на рисунке 1, но с composable-функциями в дереве пользовательского интерфейса, визуализированными в виде цепочек модификаторов.

Подведем итоги:

  • Модификаторы оборачивают один модификатор или узел макета.
  • Узлы макета могут содержать несколько дочерних узлов.

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

Ограничения на этапе макета

Этап макета использует трехшаговый алгоритм для определения ширины, высоты и координат x, y каждого узла макета:

  1. Измерение дочерних элементов. Узел измеряет свои дочерние элементы, если они есть.
  2. Определение собственного размера. На основе этих измерений узел определяет собственный размер.
  3. Размещение дочерних элементов. Каждый дочерний узел размещается относительно положения узла.

Constraints помогает найти правильные размеры узлов на первых двух шагах алгоритма. Ограничения определяют минимальные и максимальные границы ширины и высоты узла. Когда узел определяет свой размер, его измеренный размер должен находиться в этом диапазоне.

Типы ограничений

Ограничение может быть одним из следующих:

  • Ограниченный. Узел имеет максимальную и минимальную ширину и высоту.
Ограничения разных размеров в контейнере.
Рисунок 3. Ограничения.
  • Unbounded. Узел не ограничен по размеру. Максимальная ширина и высота не ограничены.
Неограниченные ограничения, ширина и высота которых установлены на бесконечность. Ограничения выходят за пределы контейнера.
Рисунок 4. Неограниченные ограничения.
  • Точный. Узел должен соответствовать точному размеру. Минимальное и максимальное значения совпадают.
Точные ограничения, которые соответствуют требованиям к размеру в контейнере.
Рисунок 5. Точные ограничения.
  • Комбинированное ограничение. Узел следует комбинации предыдущих типов ограничений. Например, можно ограничить ширину, но не высоту, или задать точную ширину и ограничить высоту.
Два контейнера, которые показывают комбинации ограниченных и неограниченных ограничений, а также точную ширину и высоту.
Рисунок 6. Комбинации ограниченных и неограниченных ограничений, а также точной ширины и высоты.

В следующем разделе описывается, как эти ограничения передаются от родительского элемента к дочернему.

Как ограничения передаются от родительского элемента к дочернему

На первом этапе алгоритма, описанном в разделе Ограничения на этапе макета, ограничения передаются от родительского элемента к дочернему в дереве пользовательского интерфейса.

Когда родительский узел измеряет свои дочерние элементы, он предоставляет эти ограничения каждому дочернему элементу, чтобы сообщить им, насколько большими или маленькими они могут быть. Затем, когда он определяет свой собственный размер, он также придерживается ограничений, которые были переданы его собственными родителями.

В общих чертах алгоритм работает следующим образом:

  1. Чтобы определить размер, который он на самом деле хочет занять, корневой узел в дереве пользовательского интерфейса измеряет свои дочерние элементы и пересылает те же ограничения своему первому дочернему элементу.
  2. Если дочерний элемент является модификатором, который не влияет на измерение, он пересылает ограничения следующему модификатору. Ограничения передаются по цепочке модификаторов как есть, пока не будет достигнут модификатор, влияющий на измерение. Затем ограничения изменяются соответствующим образом.
  3. Когда узел не имеет дочерних элементов (так называемый листовой узел), он определяет свой размер на основе переданных ограничений и возвращает этот разрешенный размер своему родительскому элементу.
  4. Родительский виджет адаптирует свои ограничения на основе размеров дочернего виджета и вызывает следующий дочерний виджет с этими скорректированными ограничениями.
  5. После того как все дочерние элементы родительского элемента будут измерены, родительский узел принимает решение о своем собственном размере и сообщает об этом своему родительскому элементу.
  6. Таким образом, все дерево обходится в глубину. В конце концов все узлы определили свои размеры, и этап измерения завершен.

Подробный пример смотрите в видео Ограничения и порядок модификаторов.

Модификаторы, влияющие на ограничения

В предыдущем разделе вы узнали, что некоторые модификаторы могут влиять на размер ограничений. В следующих разделах описаны конкретные модификаторы, влияющие на ограничения.

Модификатор size

Модификатор size объявляет предпочтительный размер контента.

Например, следующее дерево пользовательского интерфейса должно быть отрисовано в контейнере размером 300dp на 200dp. Ограничения заданы в виде диапазонов: ширина от 100dp до 300dp, высота от 100dp до 200dp.

Часть дерева пользовательского интерфейса с модификатором размера, который оборачивает узел макета, и представление ограниченных ограничений, заданных модификатором размера в контейнере.
Рисунок 7. Ограничения в дереве интерфейса и его представление в контейнере.

Модификатор size адаптирует входящие ограничения в соответствии с переданным ему значением. В этом примере значение – 150dp:

То же, что и на рисунке 7, за исключением модификатора размера, адаптирующего входящие ограничения в соответствии с переданным ему значением.
Рисунок 8. Модификатор size, корректирующий ограничения до 150dp.

Если ширина и высота меньше наименьшей границы ограничения или больше наибольшей границы ограничения, модификатор максимально точно соответствует переданным ограничениям, при этом соблюдая переданные ограничения:

Два дерева пользовательского интерфейса и их представления в контейнерах. В первом случае модификатор размера принимает входящие ограничения, а во втором – адаптируется к слишком большим ограничениям, в результате чего ограничения заполняют контейнер.
Рисунок 9. Модификатор size, максимально соответствующий переданному ограничению.

Обратите внимание, что объединять несколько модификаторов size нельзя. Первый модификатор size устанавливает фиксированное значение для минимального и максимального ограничений. Даже если второй модификатор размера запрашивает меньший или больший размер, он все равно должен придерживаться точных переданных границ, поэтому он не будет переопределять эти значения:

Цепочка из двух модификаторов размера в дереве пользовательского интерфейса и ее представление в контейнере, которое является результатом первого переданного значения, а не второго.
Рисунок 10. Цепочка из двух модификаторов size, в которой второе переданное значение (50dp) не переопределяет первое (100dp).

Модификатор requiredSize

Если вам нужно, чтобы узел переопределял входящие ограничения, используйте модификатор requiredSize вместо size. Модификатор requiredSize заменяет входящие ограничения и передает указанный вами размер в качестве точных границ.

Когда размер передается обратно вверх по дереву, дочерний узел будет центрирован в доступном пространстве:

Модификаторы size и requiredSize, связанные в дереве пользовательского интерфейса, и соответствующее представление в контейнере. Ограничения модификатора requiredSize переопределяют ограничения модификатора size.
Рисунок 11. Модификатор requiredSize переопределяет ограничения, заданные модификатором size.

Модификаторы width и height

Модификатор size адаптирует как ширину, так и высоту ограничений. С помощью модификатора width можно задать фиксированную ширину, но оставить высоту неопределенной. Аналогично с помощью модификатора height можно задать фиксированную высоту, но оставить ширину неопределенной:

Два дерева пользовательского интерфейса: одно с модификатором ширины и его представлением контейнера, а другое с модификатором высоты и его представлением.
Рисунок 12. Модификаторы width и height, задающие фиксированную ширину и высоту соответственно.

Модификатор sizeIn

Модификатор sizeIn позволяет задать точные минимальные и максимальные ограничения для ширины и высоты. Если вам нужен точный контроль над ограничениями, используйте модификатор sizeIn.

Дерево пользовательского интерфейса с модификатором sizeIn с заданными минимальной и максимальной шириной и высотой и его представлением в контейнере.
Рисунок 13. Модификатор sizeIn с заданными значениями minWidth, maxWidth, minHeight и maxHeight.

Примеры

В этом разделе показаны и объяснены выходные данные нескольких фрагментов кода с цепочками модификаторов.

Image(
    painterResource(R.drawable.hero),
    contentDescription = null,
    Modifier
        .fillMaxSize()
        .size(50.dp)
)

Этот фрагмент кода дает следующий результат:

Синий квадрат, который заполняет родительский контейнер.
Рисунок 14. В результате применения цепочки модификаторов Image заполняет максимальный размер.
  • Модификатор fillMaxSize изменяет ограничения, устанавливая минимальную ширину и высоту на максимальное значение – 300dp по ширине и 200dp по высоте.
  • Хотя модификатор size пытается использовать размер 50dp, он все равно должен соблюдать входящие минимальные ограничения. Поэтому модификатор size также выведет точные границы ограничений 300 на 200, фактически игнорируя значение, указанное в модификаторе size.
  • Виджет Image следует этим ограничениям и сообщает о размере 300 на 200, который передается вверх по дереву.

Image(
    painterResource(R.drawable.hero),
    contentDescription = null,
    Modifier
        .fillMaxSize()
        .wrapContentSize()
        .size(50.dp)
)

Этот фрагмент кода дает следующий результат:

Маленький синий квадрат в центре родительского контейнера.
Рисунок 15. Image центрируется и масштабируется до размера 50dp.
  • Модификатор fillMaxSize адаптирует ограничения, чтобы установить минимальную ширину и высоту на максимальное значение – 300dp по ширине и 200dp по высоте.
  • Модификатор wrapContentSize сбрасывает минимальные ограничения. Таким образом, если fillMaxSize приводит к фиксированным ограничениям, то wrapContentSize сбрасывает их обратно к ограниченным. Следующий узел может снова занять все пространство или быть меньше всего пространства.
  • Модификатор size задает ограничения для минимальных и максимальных границ 50.
  • Image определяет размер 50 на 50, а модификатор size передает его дальше.
  • Модификатор wrapContentSize имеет особое свойство. Он берет дочерний элемент и помещает его в центр доступных минимальных границ, которые ему были переданы. Размер, который он передает своим родителям, равен минимальным границам, которые были ему переданы.

Объединив всего три модификатора, вы можете определить размер составного элемента и центрировать его в родительском элементе.

Image(
    painterResource(R.drawable.hero),
    contentDescription = null,
    Modifier
        .clip(CircleShape)
        .padding(10.dp)
        .size(100.dp)
)

Этот фрагмент кода дает следующий результат:

Круглая фигура, которая неправильно обрезана из-за порядка модификаторов.
Рисунок 16. Неправильно обрезанная фигура из-за порядка модификаторов.
  • Модификатор clip не меняет ограничения.
  • Модификатор padding снижает максимальные ограничения.
  • Модификатор size задает для всех ограничений значение 100dp.
  • Image соблюдает эти ограничения и сообщает о размере 100dp на 100dp.
  • Модификатор padding добавляет 10dp со всех сторон к размеру, сообщаемому Image, поэтому макет с отступами сообщает ширину и высоту 120dp.
  • Теперь, на этапе рисования, модификатор clip действует на холсте размером 120dp на 120dp. Будет создана круглая маска указанного размера.
  • Затем модификатор padding вставляет свое содержимое на 10dp со всех сторон, что уменьшает размер холста для Image до 100dp на 100dp.
  • Image рисуется на этом меньшем холсте. Изображение обрезается по исходному кругу 120dp, поэтому результат получается некруглым.