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

Модификаторы позволяют вам украшать или дополнять компонуемый объект. Модификаторы позволяют вам делать такие вещи:

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

Модификаторы — это стандартные объекты Kotlin. Создайте модификатор, вызвав одну из функций класса Modifier :

@Composable
private fun Greeting(name: String) {
    Column(modifier = Modifier.padding(24.dp)) {
        Text(text = "Hello,")
        Text(text = name)
    }
}

Две строки текста на цветном фоне с отступами вокруг текста.

Вы можете объединить эти функции в цепочку, чтобы составить их:

@Composable
private fun Greeting(name: String) {
    Column(
        modifier = Modifier
            .padding(24.dp)
            .fillMaxWidth()
    ) {
        Text(text = "Hello,")
        Text(text = name)
    }
}

Цветной фон позади текста теперь занимает всю ширину устройства.

В приведенном выше коде обратите внимание на совместное использование различных функций-модификаторов.

  • padding создает пространство вокруг элемента.
  • fillMaxWidth задает для компонуемой заливки максимальную ширину, предоставленную ей ее родителем.

Лучше всего, чтобы все ваши компонуемые элементы принимали параметр- modifier и передавали этот модификатор своему первому потомку, который выдает UI. Это делает ваш код более пригодным для повторного использования и делает его поведение более предсказуемым и интуитивным. Для получения дополнительной информации см. руководство Compose API, Элементы принимают и уважают параметр-модификатор .

Порядок модификаторов имеет значение

Порядок функций-модификаторов имеет значение . Поскольку каждая функция вносит изменения в Modifier , возвращаемый предыдущей функцией, последовательность влияет на конечный результат. Давайте рассмотрим пример этого:

@Composable
fun ArtistCard(/*...*/) {
    val padding = 16.dp
    Column(
        Modifier
            .clickable(onClick = onClick)
            .padding(padding)
            .fillMaxWidth()
    ) {
        // rest of the implementation
    }
}

Вся область, включая отступы по краям, реагирует на нажатия.

В коде выше вся область кликабельна, включая окружающий padding, поскольку модификатор padding был применен после модификатора clickable . Если порядок модификаторов обратный, пространство, добавленное padding не реагирует на ввод пользователя:

@Composable
fun ArtistCard(/*...*/) {
    val padding = 16.dp
    Column(
        Modifier
            .padding(padding)
            .clickable(onClick = onClick)
            .fillMaxWidth()
    ) {
        // rest of the implementation
    }
}

Отступы по краям макета больше не реагируют на щелчки.

Встроенные модификаторы

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

padding и size

По умолчанию макеты, предоставляемые в Compose, оборачивают своих потомков. Однако вы можете задать размер, используя модификатор size :

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(/*...*/)
        Column { /*...*/ }
    }
}

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

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(
            /*...*/
            modifier = Modifier.requiredSize(150.dp)
        )
        Column { /*...*/ }
    }
}

Дочернее изображение больше ограничений, накладываемых его родителем

В этом примере, даже если родительская height установлена ​​на 100.dp , высота Image будет 150.dp , поскольку модификатор requiredSize имеет приоритет.

Если вы хотите, чтобы дочерний макет заполнил всю доступную высоту, разрешенную родительским, добавьте модификатор fillMaxHeight (Compose также предоставляет fillMaxSize и fillMaxWidth ):

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(
            /*...*/
            modifier = Modifier.fillMaxHeight()
        )
        Column { /*...*/ }
    }
}

Высота изображения такая же, как и у его родителя.

Чтобы добавить отступ вокруг элемента, установите модификатор padding .

Если вы хотите добавить отступ над базовой линией текста таким образом, чтобы получить определенное расстояние от верха макета до базовой линии, используйте модификатор paddingFromBaseline :

@Composable
fun ArtistCard(artist: Artist) {
    Row(/*...*/) {
        Column {
            Text(
                text = artist.name,
                modifier = Modifier.paddingFromBaseline(top = 50.dp)
            )
            Text(artist.lastSeenOnline)
        }
    }
}

Текст с отступом над ним

Компенсировать

Чтобы расположить макет относительно его исходного положения, добавьте модификатор offset и задайте смещение по осям x и y . Смещения могут быть как положительными, так и неположительными. Разница между padding и offset заключается в том, что добавление offset к компонуемому не изменяет его измерения:

@Composable
fun ArtistCard(artist: Artist) {
    Row(/*...*/) {
        Column {
            Text(artist.name)
            Text(
                text = artist.lastSeenOnline,
                modifier = Modifier.offset(x = 4.dp)
            )
        }
    }
}

Текст смещен в правую сторону родительского контейнера.

Модификатор offset применяется горизонтально в соответствии с направлением макета. В контексте слева направо положительное offset сдвигает элемент вправо, а в контексте справа налево — влево. Если вам нужно задать смещение без учета направления макета, см. модификатор absoluteOffset , в котором положительное значение смещения всегда сдвигает элемент вправо.

Модификатор offset предоставляет две перегрузки — offset , которое принимает смещения в качестве параметров, и offset , которое принимает лямбду. Для более подробной информации о том, когда использовать каждый из них и как оптимизировать производительность, прочитайте раздел Compose performance — Defer reads as long as possible .

Безопасность области действия в Compose

В Compose есть модификаторы, которые можно использовать только при применении к потомкам определенных компонуемых объектов. Compose обеспечивает это посредством пользовательских областей.

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

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

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

matchParentSize в Box

Как упоминалось выше, если вы хотите, чтобы дочерний макет имел тот же размер, что и родительский Box , не влияя на размер Box , используйте модификатор matchParentSize .

Обратите внимание, что matchParentSize доступен только в области действия Box , то есть он применяется только к прямым дочерним элементам составных элементов Box .

В примере ниже дочерний элемент Spacer берет свой размер из своего родительского Box , который, в свою очередь, берет свой размер из самого большого дочернего элемента, в данном случае ArtistCard .

@Composable
fun MatchParentSizeComposable() {
    Box {
        Spacer(
            Modifier
                .matchParentSize()
                .background(Color.LightGray)
        )
        ArtistCard()
    }
}

Серый фон заполняет свой контейнер

Если бы вместо matchParentSize использовалось fillMaxSize , Spacer занял бы все доступное пространство, предоставленное родительскому элементу, в свою очередь заставив родительский элемент расшириться и заполнить все доступное пространство.

Серый фон, заполняющий экран

weight в Row и Column

Как вы видели в предыдущем разделе Padding и size , по умолчанию компонуемый размер определяется содержимым, которое он оборачивает. Вы можете сделать компонуемый размер гибким в пределах его родителя, используя модификатор weight , который доступен только в RowScope и ColumnScope .

Давайте возьмем Row , содержащую два компонуемых Box . Первому блоку дается weight в два раза больше, чем второму, поэтому ему дается в два раза большая ширина. Поскольку Row имеет ширину 210.dp , первый Box имеет ширину 140.dp , а второй — 70.dp :

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.fillMaxWidth()
    ) {
        Image(
            /*...*/
            modifier = Modifier.weight(2f)
        )
        Column(
            modifier = Modifier.weight(1f)
        ) {
            /*...*/
        }
    }
}

Ширина изображения в два раза больше ширины текста.

Извлечение и повторное использование модификаторов

Несколько модификаторов могут быть объединены в цепочку для украшения или дополнения компонуемого объекта. Эта цепочка создается с помощью интерфейса Modifier , который представляет собой упорядоченный, неизменяемый список отдельных Modifier.Elements .

Каждый Modifier.Element представляет индивидуальное поведение, например, поведение макета, рисования и графики, все поведение, связанное с жестами, фокусом и семантикой, а также события ввода устройства. Их порядок имеет значение: элементы-модификаторы, которые добавляются первыми, будут применены первыми.

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

  • Перераспределение модификаторов не будет повторяться при перекомпоновке для компонуемых объектов, которые их используют.
  • Цепочки модификаторов могут быть потенциально очень длинными и сложными, поэтому повторное использование одного и того же экземпляра цепочки может облегчить нагрузку на среду выполнения Compose при их сравнении.
  • Такое извлечение способствует чистоте кода, согласованности и удобству обслуживания всей кодовой базы.

Лучшие практики повторного использования модификаторов

Создавайте собственные цепочки Modifier и извлекайте их для повторного использования в нескольких составных компонентах. Совершенно нормально просто сохранить модификатор, поскольку они являются объектами, подобными данным:

val reusableModifier = Modifier
    .fillMaxWidth()
    .background(Color.Red)
    .padding(12.dp)

Извлечение и повторное использование модификаторов при наблюдении за часто меняющимся состоянием

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

@Composable
fun LoadingWheelAnimation() {
    val animatedState = animateFloatAsState(/*...*/)

    LoadingWheel(
        // Creation and allocation of this modifier will happen on every frame of the animation!
        modifier = Modifier
            .padding(12.dp)
            .background(Color.Gray),
        animatedState = animatedState
    )
}

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

// Now, the allocation of the modifier happens here:
val reusableModifier = Modifier
    .padding(12.dp)
    .background(Color.Gray)

@Composable
fun LoadingWheelAnimation() {
    val animatedState = animateFloatAsState(/*...*/)

    LoadingWheel(
        // No allocation, as we're just reusing the same instance
        modifier = reusableModifier,
        animatedState = animatedState
    )
}

Извлечение и повторное использование модификаторов без области действия

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

val reusableModifier = Modifier
    .fillMaxWidth()
    .background(Color.Red)
    .padding(12.dp)

@Composable
fun AuthorField() {
    HeaderText(
        // ...
        modifier = reusableModifier
    )
    SubtitleText(
        // ...
        modifier = reusableModifier
    )
}

Это может быть особенно полезно в сочетании с Lazy layouts. В большинстве случаев вы захотите, чтобы все ваши потенциально значительные количества элементов имели одинаковые модификаторы:

val reusableItemModifier = Modifier
    .padding(bottom = 12.dp)
    .size(216.dp)
    .clip(CircleShape)

@Composable
private fun AuthorList(authors: List<Author>) {
    LazyColumn {
        items(authors) {
            AsyncImage(
                // ...
                modifier = reusableItemModifier,
            )
        }
    }
}

Извлечение и повторное использование модификаторов области действия

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

Column(/*...*/) {
    val reusableItemModifier = Modifier
        .padding(bottom = 12.dp)
        // Align Modifier.Element requires a ColumnScope
        .align(Alignment.CenterHorizontally)
        .weight(1f)
    Text1(
        modifier = reusableItemModifier,
        // ...
    )
    Text2(
        modifier = reusableItemModifier
        // ...
    )
    // ...
}

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

Column(modifier = Modifier.fillMaxWidth()) {
    // Weight modifier is scoped to the Column composable
    val reusableItemModifier = Modifier.weight(1f)

    // Weight will be properly assigned here since this Text is a direct child of Column
    Text1(
        modifier = reusableItemModifier
        // ...
    )

    Box {
        Text2(
            // Weight won't do anything here since the Text composable is not a direct child of Column
            modifier = reusableItemModifier
            // ...
        )
    }
}

Дальнейшее объединение извлеченных модификаторов

Вы можете дополнительно объединить или добавить извлеченные цепочки модификаторов, вызвав функцию .then() :

val reusableModifier = Modifier
    .fillMaxWidth()
    .background(Color.Red)
    .padding(12.dp)

// Append to your reusableModifier
reusableModifier.clickable { /*...*/ }

// Append your reusableModifier
otherModifier.then(reusableModifier)

Просто помните, что порядок модификаторов имеет значение!

Узнать больше

Мы предоставляем полный список модификаторов с их параметрами и областями действия.

Для получения дополнительной практики использования модификаторов вы также можете изучить базовые макеты в кодовой лаборатории Compose или обратиться к репозиторию Now in Android .

Дополнительную информацию о пользовательских модификаторах и способах их создания см. в документации Пользовательские макеты — Использование модификатора макета .

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