Вопрос или проблема
Я являюсь частью команды, работающей над прототипом приложения для Android в рамках нашего дипломного проекта на курсе управления проектами в университете, и мы столкнулись с небольшой проблемой в изучении Jetpack Compose.
В общем, я стараюсь сделать код максимально чистым, модульным, легким для отладки и читаемым (много кода Jetpack выглядит некрасиво и болезненно для глаза для команды, которая не знакома с синтаксисом Kotlin), и из-за этого у меня есть привычка оборачивать компоненты пользовательского интерфейса в кастомные компоненты. Например:
@Composable
fun ColoredBackgroundTextBox(text: String, color: Color)
{
ColoredBackgroundBox(content = {
Text(text, modifier = Modifier.align(Alignment.Center));
}, color = Color.Gray, width = 200, height = 30)
}
@Composable
fun ColoredBackgroundBox(content: @Composable () -> Unit, color: Color, width: Int, height: Int)
{
Box(modifier = Modifier.size(width.dp, height.dp).background(color))
{
content();
}
}
В приведенном выше примере я пытаюсь создать элемент текста, заключенный в цветной фоновой коробке (в рамках большего компонента; в основном, мы получаем список установленных приложений и генерируем графический интерфейс для каждого приложения, установленного на вашем устройстве).
Проблема в том, что функция расширения Modifier.align
доступна только в контексте Box
, Row
или Column
. Как вы можете видеть, content
всегда создается/вызывается внутри коробки, но Android Studio/Kotlin, похоже, “не знают” об этом. Единственный обходной путь, похоже, заключается в том, чтобы убрать посреднические функции компонентов и просто делать все непосредственно внутри Box
, но это выглядит очень грубо и неэстетично, и усложняет дальнейшее модифицирование/расширение кода.
Есть ли способ просто “сказать” компилятору “эй, даже если это не выглядит так, как будто это вызывается из правильного контекста, это так, так что перестань выбрасывать ошибки и просто работай”?
Modifier.align()
является ограниченным Modifier
и доступен только в пределах области, в которой он был объявлен; для Box
это BoxScope
.
Если вы измените content : @Composable () -> Unit
в ColoredBackgroundBox
на content : @Composable BoxScope.() -> Unit
, чтобы иметь приемник BoxScope
, содержимое сможет получить доступ к модификаторам, принадлежащим BoxScope
. Это также будет означать, что вы сможете вызывать content()
только внутри Box
.
@Composable
fun ColoredBackgroundTextBox(text: String, color: Color) {
ColoredBackgroundBox(
color = Color.Gray,
width = 200,
height = 30,
content = {
Text(text, modifier = Modifier.align(Alignment.Center))
}
)
}
@Composable
fun ColoredBackgroundBox(
content: @Composable BoxScope.() -> Unit,
color: Color,
width: Int,
height: Int,
) {
Box(modifier = Modifier.size(width.dp, height.dp).background(color))
{
content()
}
}
Используйте BoxScope
.
Когда используется BoxScope
, он определяет область ассоциированного компонента.
content: @Composable BoxScope.() -> Unit,
Ответ или решение
Как установить выравнивание для пользовательских Composables в Jetpack Compose
Разработка Android-приложений с использованием Jetpack Compose открывает множество возможностей для создания чистого и модульного кода. Однако некоторые сложности, такие как правильное использование модификаторов для настройки выравнивания пользовательских Composables, могут вызвать затруднения. В этой статье мы рассмотрим, как правильно применить выравнивание в ваших пользовательских компонентах.
Пример ситуации
Предположим, вы разрабатываете пользовательский компонент ColoredBackgroundTextBox
, который включает в себя текст, обернутый в цветной фон. Ниже приведён пример вашего кода:
@Composable
fun ColoredBackgroundTextBox(text: String, color: Color) {
ColoredBackgroundBox(content = {
Text(text, modifier = Modifier.align(Alignment.Center))
}, color = Color.Gray, width = 200, height = 30)
}
@Composable
fun ColoredBackgroundBox(content: @Composable () -> Unit, color: Color, width: Int, height: Int) {
Box(modifier = Modifier.size(width.dp, height.dp).background(color)) {
content()
}
}
Проблема
Ваша проблема заключается в том, что функция Modifier.align()
доступна только в контексте Box
, Row
или Column
. Ваш компилятор не распознаёт, что content
вызывается внутри Box
, так как тип content
не указывает, что он должен быть вызван в BoxScope
.
Решение
Чтобы правильно настроить выравнивание в вашем пользовательском компоненте, измените объявление content
в функции ColoredBackgroundBox
так, чтобы оно принимало BoxScope
:
@Composable
fun ColoredBackgroundBox(
content: @Composable BoxScope.() -> Unit,
color: Color,
width: Int,
height: Int,
) {
Box(modifier = Modifier.size(width.dp, height.dp).background(color)) {
content() // Здесь контент будет получен из BoxScope
}
}
После этой модификации, способ создания вашего компонента ColoredBackgroundTextBox
будет выглядеть следующим образом:
@Composable
fun ColoredBackgroundTextBox(text: String, color: Color) {
ColoredBackgroundBox(
color = Color.Gray,
width = 200,
height = 30,
content = {
Text(text, modifier = Modifier.align(Alignment.Center))
}
)
}
Почему это работает?
Используя BoxScope
в качестве типа для параметра content
, вы позволяете вашему Composable получить доступ к модификаторам, специфичным для Box
, таким как align()
. Это позволяет создать более уложенный, модульный и чистый код, что является основным приоритетом в разработке приложений.
Заключение
Применение правильного контекста для пользовательских Composables в Jetpack Compose—это ключ к созданию чистого и поддерживаемого кода. Использование BoxScope
в вашей реализации, как было описано выше, позволит вам управлять выравниванием ваших компонентов из другого Composable, оставаясь в рамках заданного архитектурного подхода.
Если у вас есть дополнительные вопросы или вы хотите углубиться в другие аспекты Jetpack Compose, не стесняйтесь задавать вопросы. Ваш успех в разработке будет зависеть от точного понимания и применения возможностей, предоставляемых Jetpack Compose.