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

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

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

Модификаторы — это стандартные объекты 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 и передавали этот модификатор своему первому дочернему элементу, который генерирует пользовательский интерфейс. Это сделает ваш код более пригодным для повторного использования, а его поведение — более предсказуемым и интуитивно понятным. Дополнительные сведения см. в рекомендациях Compose API. Элементы принимают и учитывают параметр модификатора .

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

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

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

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

В приведенном выше коде вся область кликабельна, включая окружающие отступы, поскольку модификатор 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

В 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

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

Давайте возьмем Row , содержащую два составных элемента Box . Первая коробка имеет weight в два раза больше второй, поэтому ее ширина увеличивается в два раза. Поскольку ширина Row составляет 210.dp , ширина первого Box140.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
    )
}

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

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 .

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

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