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

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

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

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

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

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

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

Функция защиты прицела предотвращает добавление модификаторов, которые не будут работать в других компонуемых объектах и ​​прицелах, и экономит время, затрачиваемое на пробные попытки.

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

matchParentSize in 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 . Первому объекту 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 codelab или обратиться к репозиторию Now in Android .

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

{% verbatim %} {% endverbatim %} {% verbatim %} {% endverbatim %}