Основы составления макета

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

  1. Состав элементов
  2. Расположение элементов
  3. Чертеж элементов

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

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

Цели макетов в Compose

Реализация системы компоновки Jetpack Compose преследует две основные цели:

Основы компонуемых функций

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

Компонуемая функция может выдавать несколько элементов пользовательского интерфейса. Однако, если вы не предоставите указания о том, как их следует расположить, Compose может расположить элементы так, как вам не нравится. Например, этот код генерирует два текстовых элемента:

@Composable
fun ArtistCard() {
    Text("Alfred Sisley")
    Text("3 minutes ago")
}

Без указания того, как именно вы хотите их расположить, Compose накладывает текстовые элементы друг на друга, делая их нечитаемыми:

Два текстовых элемента, нарисованных друг на друге, делают текст нечитаемым

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

Стандартные компоненты компоновки

Во многих случаях можно просто использовать стандартные элементы макета Compose .

Используйте Column для вертикального размещения элементов на экране.

@Composable
fun ArtistCardColumn() {
    Column {
        Text("Alfred Sisley")
        Text("3 minutes ago")
    }
}

Два текстовых элемента расположены в виде столбца, что позволяет легко читать текст

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

@Composable
fun ArtistCardRow(artist: Artist) {
    Row(verticalAlignment = Alignment.CenterVertically) {
        Image(bitmap = artist.image, contentDescription = "Artist image")
        Column {
            Text(artist.name)
            Text(artist.lastSeenOnline)
        }
    }
}

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

Используйте Box для размещения элементов друг над другом. Box также поддерживает настройку определенного выравнивания содержащихся в нем элементов.

@Composable
fun ArtistAvatar(artist: Artist) {
    Box {
        Image(bitmap = artist.image, contentDescription = "Artist image")
        Icon(Icons.Filled.Check, contentDescription = "Check mark")
    }
}

Показывает два элемента, наложенных друг на друга.

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

Сравнивает три простых компоновочных макета: столбец, строка и блок.

Чтобы задать положение дочерних элементов в Row , задайте аргументы horizontalArrangement и verticalAlignment . Для Column , задайте аргументы verticalArrangement и horizontalAlignment :

@Composable
fun ArtistCardArrangement(artist: Artist) {
    Row(
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.End
    ) {
        Image(bitmap = artist.image, contentDescription = "Artist image")
        Column { /*...*/ }
    }
}

Элементы выровнены по правому краю.

Модель макета

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

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

Рассмотрим следующую функцию SearchResult .

@Composable
fun SearchResult() {
    Row {
        Image(
            // ...
        )
        Column {
            Text(
                // ...
            )
            Text(
                // ...
            )
        }
    }
}

Эта функция создает следующее дерево пользовательского интерфейса.

SearchResult
  Row
    Image
    Column
      Text
      Text

В примере SearchResult структура дерева пользовательского интерфейса имеет следующий порядок:

  1. Корневой узел Row просят измерить.
  2. Корневой узел Row запрашивает у своего первого дочернего узла Image измерение.
  3. Image является конечным узлом (то есть не имеет дочерних элементов), поэтому оно сообщает размер и возвращает инструкции по размещению.
  4. Корневой узел Row запрашивает у своего второго дочернего узла Column измерение.
  5. Узел Column запрашивает у своего первого дочернего элемента Text измерение.
  6. Первый узел Text является конечным узлом, поэтому он сообщает размер и возвращает инструкции по размещению.
  7. Узел Column запрашивает у своего второго дочернего элемента Text измерение.
  8. Второй узел Text является конечным узлом, поэтому он сообщает размер и возвращает инструкции по размещению.
  9. Теперь, когда узел Column измерил, определил размер и разместил свои дочерние элементы, он может определить свой собственный размер и размещение.
  10. Теперь, когда корневой узел Row измерил, определил размер и разместил свои дочерние элементы, он может определить свой собственный размер и размещение.

Порядок измерения, определения размера и размещения в дереве пользовательского интерфейса результатов поиска

Производительность

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

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

Поскольку измерение и размещение являются отдельными подэтапами этапа компоновки, любые изменения, которые влияют только на размещение элементов, а не на измерения, могут быть выполнены отдельно.

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

Как обсуждалось в разделе Модификаторы Compose , вы можете использовать модификаторы для украшения или дополнения ваших компонуемых объектов. Модификаторы необходимы для настройки вашего макета. Например, здесь мы объединяем несколько модификаторов для настройки ArtistCard :

@Composable
fun ArtistCardModifiers(
    artist: Artist,
    onClick: () -> Unit
) {
    val padding = 16.dp
    Column(
        Modifier
            .clickable(onClick = onClick)
            .padding(padding)
            .fillMaxWidth()
    ) {
        Row(verticalAlignment = Alignment.CenterVertically) { /*...*/ }
        Spacer(Modifier.size(padding))
        Card(
            elevation = CardDefaults.cardElevation(defaultElevation = 4.dp),
        ) { /*...*/ }
    }
}

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

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

  • clickable заставляет компонуемый объект реагировать на ввод пользователя и отображать рябь.
  • padding создает пространство вокруг элемента.
  • fillMaxWidth задает для компонуемой заливки максимальную ширину, предоставленную ей ее родителем.
  • size() указывает предпочтительную ширину и высоту элемента.

Прокручиваемые макеты

Подробнее о прокручиваемых макетах читайте в документации по жестам создания сообщений .

Информацию о списках и ленивых списках см. в документации по составлению списков .

Адаптивные макеты

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

Ограничения

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

@Composable
fun WithConstraintsComposable() {
    BoxWithConstraints {
        Text("My minHeight is $minHeight while my maxWidth is $maxWidth")
    }
}

Макеты на основе слотов

Compose предоставляет большое разнообразие компонуемых элементов на основе Material Design с зависимостью androidx.compose.material:material (включается при создании проекта Compose в Android Studio) для упрощения разработки пользовательского интерфейса. Предоставляются такие элементы, как Drawer , FloatingActionButton и TopAppBar .

Компоненты материала активно используют API слотов , шаблон Compose, который вводит для добавления слоя настройки поверх компонуемых. Такой подход делает компоненты более гибкими, поскольку они принимают дочерний элемент, который может настраиваться сам, а не раскрывать каждый параметр конфигурации дочернего элемента. Слоты оставляют пустое место в пользовательском интерфейсе, которое разработчик может заполнить по своему усмотрению. Например, вот слоты, которые можно настроить в TopAppBar :

Диаграмма, показывающая доступные слоты на панели приложений Material Components

Компонуемые элементы обычно принимают компонуемую лямбду content ( content: @Composable () -> Unit ). API слотов предоставляют несколько параметров content для конкретных целей. Например, TopAppBar позволяет вам предоставлять контент для title , navigationIcon и actions .

Например, Scaffold позволяет реализовать пользовательский интерфейс с базовой структурой макета Material Design. Scaffold предоставляет слоты для наиболее распространенных компонентов Material верхнего уровня, таких как TopAppBar , BottomAppBar , FloatingActionButton и Drawer . Используя Scaffold , легко убедиться, что эти компоненты правильно размещены и работают вместе правильно.

Пример приложения JetNews, которое использует Scaffold для позиционирования нескольких элементов

@Composable
fun HomeScreen(/*...*/) {
    ModalNavigationDrawer(drawerContent = { /* ... */ }) {
        Scaffold(
            topBar = { /*...*/ }
        ) { contentPadding ->
            // ...
        }
    }
}

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

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

  1. Состав элементов
  2. Расположение элементов
  3. Чертеж элементов

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

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

Цели макетов в Compose

Реализация системы компоновки Jetpack Compose преследует две основные цели:

Основы компонуемых функций

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

Компонуемая функция может выдавать несколько элементов пользовательского интерфейса. Однако, если вы не предоставите указания о том, как их следует расположить, Compose может расположить элементы так, как вам не нравится. Например, этот код генерирует два текстовых элемента:

@Composable
fun ArtistCard() {
    Text("Alfred Sisley")
    Text("3 minutes ago")
}

Без указания того, как именно вы хотите их расположить, Compose накладывает текстовые элементы друг на друга, делая их нечитаемыми:

Два текстовых элемента, нарисованных друг на друге, делают текст нечитаемым

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

Стандартные компоненты компоновки

Во многих случаях можно просто использовать стандартные элементы макета Compose .

Используйте Column для вертикального размещения элементов на экране.

@Composable
fun ArtistCardColumn() {
    Column {
        Text("Alfred Sisley")
        Text("3 minutes ago")
    }
}

Два текстовых элемента расположены в виде столбца, что позволяет легко читать текст

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

@Composable
fun ArtistCardRow(artist: Artist) {
    Row(verticalAlignment = Alignment.CenterVertically) {
        Image(bitmap = artist.image, contentDescription = "Artist image")
        Column {
            Text(artist.name)
            Text(artist.lastSeenOnline)
        }
    }
}

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

Используйте Box для размещения элементов друг над другом. Box также поддерживает настройку определенного выравнивания содержащихся в нем элементов.

@Composable
fun ArtistAvatar(artist: Artist) {
    Box {
        Image(bitmap = artist.image, contentDescription = "Artist image")
        Icon(Icons.Filled.Check, contentDescription = "Check mark")
    }
}

Показывает два элемента, наложенных друг на друга.

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

Сравнивает три простых компоновочных макета: столбец, строка и блок.

Чтобы задать положение дочерних элементов в Row , задайте аргументы horizontalArrangement и verticalAlignment . Для Column , задайте аргументы verticalArrangement и horizontalAlignment :

@Composable
fun ArtistCardArrangement(artist: Artist) {
    Row(
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.End
    ) {
        Image(bitmap = artist.image, contentDescription = "Artist image")
        Column { /*...*/ }
    }
}

Элементы выровнены по правому краю.

Модель макета

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

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

Рассмотрим следующую функцию SearchResult .

@Composable
fun SearchResult() {
    Row {
        Image(
            // ...
        )
        Column {
            Text(
                // ...
            )
            Text(
                // ...
            )
        }
    }
}

Эта функция создает следующее дерево пользовательского интерфейса.

SearchResult
  Row
    Image
    Column
      Text
      Text

В примере SearchResult структура дерева пользовательского интерфейса имеет следующий порядок:

  1. Корневой узел Row просят измерить.
  2. Корневой узел Row запрашивает у своего первого дочернего узла Image измерение.
  3. Image является конечным узлом (то есть не имеет дочерних элементов), поэтому оно сообщает размер и возвращает инструкции по размещению.
  4. Корневой узел Row запрашивает у своего второго дочернего узла Column измерение.
  5. Узел Column запрашивает у своего первого дочернего элемента Text измерение.
  6. Первый узел Text является конечным узлом, поэтому он сообщает размер и возвращает инструкции по размещению.
  7. Узел Column запрашивает у своего второго дочернего элемента Text измерение.
  8. Второй узел Text является конечным узлом, поэтому он сообщает размер и возвращает инструкции по размещению.
  9. Теперь, когда узел Column измерил, определил размер и разместил свои дочерние элементы, он может определить свой собственный размер и размещение.
  10. Теперь, когда корневой узел Row измерил, определил размер и разместил свои дочерние элементы, он может определить свой собственный размер и размещение.

Порядок измерения, определения размера и размещения в дереве пользовательского интерфейса результатов поиска

Производительность

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

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

Поскольку измерение и размещение являются отдельными подэтапами этапа компоновки, любые изменения, которые влияют только на размещение элементов, а не на измерения, могут быть выполнены отдельно.

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

Как обсуждалось в разделе Модификаторы Compose , вы можете использовать модификаторы для украшения или дополнения ваших компонуемых объектов. Модификаторы необходимы для настройки вашего макета. Например, здесь мы объединяем несколько модификаторов для настройки ArtistCard :

@Composable
fun ArtistCardModifiers(
    artist: Artist,
    onClick: () -> Unit
) {
    val padding = 16.dp
    Column(
        Modifier
            .clickable(onClick = onClick)
            .padding(padding)
            .fillMaxWidth()
    ) {
        Row(verticalAlignment = Alignment.CenterVertically) { /*...*/ }
        Spacer(Modifier.size(padding))
        Card(
            elevation = CardDefaults.cardElevation(defaultElevation = 4.dp),
        ) { /*...*/ }
    }
}

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

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

  • clickable заставляет компонуемый объект реагировать на ввод пользователя и отображать рябь.
  • padding создает пространство вокруг элемента.
  • fillMaxWidth задает для компонуемой заливки максимальную ширину, предоставленную ей ее родителем.
  • size() указывает предпочтительную ширину и высоту элемента.

Прокручиваемые макеты

Подробнее о прокручиваемых макетах читайте в документации по жестам создания сообщений .

Информацию о списках и ленивых списках см. в документации по составлению списков .

Адаптивные макеты

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

Ограничения

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

@Composable
fun WithConstraintsComposable() {
    BoxWithConstraints {
        Text("My minHeight is $minHeight while my maxWidth is $maxWidth")
    }
}

Макеты на основе слотов

Compose предоставляет большое разнообразие компонуемых элементов на основе Material Design с зависимостью androidx.compose.material:material (включается при создании проекта Compose в Android Studio) для упрощения разработки пользовательского интерфейса. Предоставляются такие элементы, как Drawer , FloatingActionButton и TopAppBar .

Компоненты материала активно используют API слотов , шаблон Compose, который вводит для добавления слоя настройки поверх компонуемых. Такой подход делает компоненты более гибкими, поскольку они принимают дочерний элемент, который может настраиваться сам, а не раскрывать каждый параметр конфигурации дочернего элемента. Слоты оставляют пустое место в пользовательском интерфейсе, которое разработчик может заполнить по своему усмотрению. Например, вот слоты, которые можно настроить в TopAppBar :

Диаграмма, показывающая доступные слоты на панели приложений Material Components

Компонуемые элементы обычно принимают компонуемую лямбду content ( content: @Composable () -> Unit ). API слотов предоставляют несколько параметров content для конкретных целей. Например, TopAppBar позволяет вам предоставлять контент для title , navigationIcon и actions .

Например, Scaffold позволяет реализовать пользовательский интерфейс с базовой структурой макета Material Design. Scaffold предоставляет слоты для наиболее распространенных компонентов Material верхнего уровня, таких как TopAppBar , BottomAppBar , FloatingActionButton и Drawer . Используя Scaffold , легко убедиться, что эти компоненты правильно размещены и работают вместе правильно.

Пример приложения JetNews, которое использует Scaffold для позиционирования нескольких элементов

@Composable
fun HomeScreen(/*...*/) {
    ModalNavigationDrawer(drawerContent = { /* ... */ }) {
        Scaffold(
            topBar = { /*...*/ }
        ) { contentPadding ->
            // ...
        }
    }
}

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