Архитектурные уровни Jetpack Compose

На этой странице представлен общий обзор архитектурных уровней, составляющих Jetpack Compose, и основных принципов, лежащих в основе этого дизайна.

Jetpack Compose – это не единый монолитный проект, а набор модулей, которые вместе образуют полный стек. Понимание различных модулей, из которых состоит Jetpack Compose, позволяет:

  • Используйте подходящий уровень абстракции для создания приложения или библиотеки
  • Понимать, когда можно перейти на более низкий уровень для большего контроля или настройки
  • Минимизируйте зависимости

Слои

Основные уровни Jetpack Compose:

Рисунок 1. Основные уровни Jetpack Compose.

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

Время выполнения
Этот модуль содержит основные элементы среды выполнения Compose, такие как remember, mutableStateOf, аннотация @Composable и SideEffect. Вы можете создавать приложения непосредственно на основе этого уровня, если вам нужны только возможности управления деревом Compose, а не его пользовательский интерфейс.
Интерфейс
Уровень пользовательского интерфейса состоит из нескольких модулей (ui-text, ui-graphics, ui-tooling и т. д.). Эти модули реализуют основы набора инструментов пользовательского интерфейса, такие как LayoutNode, Modifier, обработчики ввода, пользовательские макеты и рисование. Вы можете использовать этот слой, если вам нужны только базовые концепции набора инструментов для создания интерфейса.
Foundation
Этот модуль предоставляет независимые от системы проектирования строительные блоки для Compose UI, такие как Row и Column, LazyColumn, распознавание определенных жестов и т. д. Вы можете использовать базовый уровень для создания собственной системы проектирования.
Материал
Этот модуль предоставляет реализацию системы проектирования Material Design для Compose UI, предоставляя систему тем, стилизованные компоненты, индикаторы пульсации и значки. Используйте этот слой при работе с Material Design в своем приложении.

Принципы дизайна

Основной принцип Jetpack Compose – предоставлять небольшие, узкоспециализированные фрагменты функциональности, которые можно собирать (или компоновать) вместе, а не несколько монолитных компонентов. У этого подхода есть ряд преимуществ.

Управление

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

Например, если вы хотите анимировать цвет компонента, вы можете использовать API animateColorAsState:

val color = animateColorAsState(if (condition) Color.Green else Color.Red)

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

val color = remember { Animatable(Color.Gray) }
LaunchedEffect(condition) {
    color.animateTo(if (condition) Color.Green else Color.Red)
}

API более высокого уровня animateColorAsState основан на API более низкого уровня Animatable. Использовать API более низкого уровня сложнее, но он предоставляет больше возможностей. Выберите уровень абстракции, который лучше всего подходит для ваших целей.

Настройка канала

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

@Composable
fun Button(
    // …
    content: @Composable RowScope.() -> Unit
) {
    Surface(/* … */) {
        CompositionLocalProvider(/* … */) { // set LocalContentAlpha
            ProvideTextStyle(MaterialTheme.typography.button) {
                Row(
                    // …
                    content = content
                )
            }
        }
    }
}

Button состоит из четырех компонентов:

  1. Материал Surface обеспечивает фон, форму, обработку кликов и т. д.

  2. CompositionLocalProvider, который изменяет альфа-канал контента, когда кнопка включена или отключена.

  3. ProvideTextStyle задает стиль текста по умолчанию.

  4. Row предоставляет стандартные правила макета для контента кнопки.

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

Если же вы хотите настроить компонент, не меняя его параметры, то можете перейти на уровень ниже и создать его копию. Например, в Material Design указано, что у кнопок должен быть сплошной цветной фон. Если вам нужен градиентный фон, этот вариант не поддерживается параметрами Button. В этом случае вы можете использовать реализацию Material Button в качестве примера и создать собственный компонент:

@Composable
fun GradientButton(
    // …
    background: List<Color>,
    modifier: Modifier = Modifier,
    content: @Composable RowScope.() -> Unit
) {
    Row(
        // …
        modifier = modifier
            .clickable(onClick = {})
            .background(
                Brush.horizontalGradient(background)
            )
    ) {
        CompositionLocalProvider(/* … */) { // set material LocalContentAlpha
            ProvideTextStyle(MaterialTheme.typography.button) {
                content()
            }
        }
    }
}

В приведенной выше реализации по-прежнему используются компоненты из слоя Material, такие как концепции Material текущей альфы контента и текущего стиля текста. Однако он заменяет материал Surface на Row и стилизует его, чтобы добиться желаемого внешнего вида.

Если вы не хотите использовать концепции Material, например при создании собственной системы проектирования, вы можете использовать только компоненты базового уровня:

@Composable
fun BespokeButton(
    // …
    backgroundColor: Color,
    modifier: Modifier = Modifier,
    content: @Composable RowScope.() -> Unit
) {
    Row(
        // …
        modifier = modifier
            .clickable(onClick = {})
            .background(backgroundColor)
    ) {
        // No Material components used
        content()
    }
}

Jetpack Compose резервирует самые простые названия для компонентов самого высокого уровня. Например, androidx.compose.material.Text основан на androidx.compose.foundation.text.BasicText. Это позволяет вам использовать собственную реализацию с наиболее заметным названием, если вы хотите заменить более высокие уровни.

Выбор правильной абстракции

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

Например, если вы хотите добавить поддержку жестов в свой компонент, вы можете создать ее с нуля, используя Modifier.pointerInput. Однако есть и другие компоненты более высокого уровня, созданные на основе этого, которые могут стать лучшей отправной точкой, например Modifier.draggable, Modifier.scrollable или Modifier.swipeable.

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

Подробнее…

Пример создания собственной системы проектирования можно найти в примере Jetsnack.