Создавайте адаптивные приложения с помощью Jetpack Compose.

1. Введение

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

Прежде чем мы начнем, важно понять, что мы подразумеваем под адаптивностью.

Адаптируемость

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

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

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

Что вы узнаете

  • Как спроектировать приложение таким образом, чтобы оно отображалось во всех размерах окна с помощью Jetpack Compose.
  • Как адаптировать ваше приложение под разные типы складных устройств.
  • Как использовать различные типы навигации для повышения доступности и удобства использования.
  • Как использовать компоненты Material 3 для обеспечения наилучшего взаимодействия с пользователем при любом размере окна.

Что вам понадобится

В этом практическом занятии вы будете использовать эмулятор Resizable , который позволяет переключаться между различными типами устройств и размерами окон.

Эмулятор с изменяемым размером и возможностью выбора режима работы: телефон, развернутый экран, планшет и настольный компьютер.

Если вы не знакомы с Compose, перед выполнением этого задания рекомендуется пройти базовый курс по Jetpack Compose .

Что вы построите

  • Интерактивное почтовое приложение Reply, использующее лучшие практики адаптивного дизайна, различные варианты навигации в стиле Material Design и оптимальное использование экранного пространства.

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

2. Настройка

Чтобы получить код для этого практического занятия, клонируйте репозиторий GitHub из командной строки:

git clone https://github.com/android/codelab-android-compose.git
cd codelab-android-compose/AdaptiveUiCodelab

В качестве альтернативы вы можете загрузить репозиторий в виде ZIP-файла:

Мы рекомендуем начать с кода в основной ветке и шаг за шагом выполнять задания в рамках Codelab в удобном для вас темпе.

Откройте проект в Android Studio.

  1. В окне «Добро пожаловать в Android Studio» выберите c01826594f360d94.png Откройте существующий проект.
  2. Выберите папку <Download Location>/AdaptiveUiCodelab (убедитесь, что вы выбрали каталог AdaptiveUiCodelab , содержащий build.gradle ).
  3. После импорта проекта в Android Studio проверьте, можете ли вы запустить main ветку.

Изучите стартовый код

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

  • MainActivity.kt — точка входа в приложение.
  • ReplyApp.kt — содержит компоненты пользовательского интерфейса главного экрана.
  • ReplyHomeViewModel.kt — предоставляет данные и состояние пользовательского интерфейса для содержимого приложения.
  • ReplyListContent.kt — содержит компоненты для создания списков и подробных экранов.

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

Начальный экран телефона

Первоначальное растянутое изображение на планшете

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

3. Сделайте приложения адаптируемыми.

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

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

Размеры окон

Устройства Android бывают самых разных форм и размеров: от телефонов и складных устройств до планшетов и устройств ChromeOS. Для поддержки как можно большего количества размеров окон ваш пользовательский интерфейс должен быть адаптивным и отзывчивым. Чтобы помочь вам определить оптимальный порог для изменения пользовательского интерфейса вашего приложения, мы определили значения контрольных точек, которые помогают классифицировать устройства по предопределенным классам размеров (компактный, средний и расширенный), называемым классами размеров окон . Это набор заданных контрольных точек области просмотра, которые помогут вам проектировать, разрабатывать и тестировать адаптивные и отзывчивые макеты приложений.

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

Класс WindowWidthSizeClass для компактной, средней и расширенной ширины.

Класс WindowHeightSizeClass для компактной, средней и расширенной высоты.

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

состояния складки

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

Складные положения: в плоском и полуоткрытом виде.

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

Подробнее о положениях при складывании и шарнирах можно прочитать здесь.

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

Получайте адаптивную информацию

adaptive библиотека Material3 обеспечивает удобный доступ к информации об окне, в котором работает ваше приложение.

  1. Добавьте записи об этом артефакте и его версии в файл каталога версий:

gradle/libs.versions.toml

[versions]
material3Adaptive = "1.0.0"

[libraries]
androidx-material3-adaptive = { module = "androidx.compose.material3.adaptive:adaptive", version.ref = "material3Adaptive" }
  1. В файле сборки модуля приложения добавьте новую зависимость библиотеки, а затем выполните синхронизацию Gradle:

app/build.gradle.kts

dependencies {

    implementation(libs.androidx.material3.adaptive)
}

Теперь в любой компонуемой области видимости вы можете использовать currentWindowAdaptiveInfo() для получения объекта WindowAdaptiveInfo содержащего информацию, такую ​​как текущий класс размера окна и находится ли устройство в сложенном положении, например, в положении стола.

Вы можете попробовать это прямо сейчас в MainActivity .

  1. В onCreate() внутри блока ReplyTheme получите информацию об адаптивности окна и отобразите классы размеров в составном Text элементе. Это можно добавить после элемента ReplyApp() :

MainActivity.kt

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    setContent {
        ReplyTheme {
            val uiState by viewModel.uiState.collectAsStateWithLifecycle()
            ReplyApp(
                replyHomeUIState = uiState,
                onEmailClick = viewModel::setSelectedEmail
            )

            val adaptiveInfo = currentWindowAdaptiveInfo()
            val sizeClassText =
                "${adaptiveInfo.windowSizeClass.windowWidthSizeClass}\n" +
                "${adaptiveInfo.windowSizeClass.windowHeightSizeClass}"
            Text(
                text = sizeClassText,
                color = Color.Magenta,
                modifier = Modifier.padding(
                    WindowInsets.safeDrawing.asPaddingValues()
                )
            )
        }
    }
}

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

4. Динамическая навигация

Теперь вы сможете адаптировать навигацию приложения в зависимости от состояния и размера устройства, чтобы сделать его более удобным в использовании.

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

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

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

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

Нижняя навигация

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

Нижняя панель навигации с элементами

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

Навигационная панель с элементами

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

Модальная навигационная панель

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

Модальная навигационная панель с элементами

Постоянное навигационное меню

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

Постоянное навигационное меню с элементами

Реализовать динамическую навигацию

Теперь вы будете переключаться между различными типами навигации в зависимости от состояния и размера устройства.

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

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

gradle/libs.versions.toml

[versions]
material3AdaptiveNavSuite = "1.3.0"

[libraries]
androidx-material3-adaptive-navigation-suite = { module = "androidx.compose.material3:material3-adaptive-navigation-suite", version.ref = "material3AdaptiveNavSuite" }

app/build.gradle.kts

dependencies {

    implementation(libs.androidx.material3.adaptive.navigation.suite)
}
  1. Найдите составную функцию ReplyNavigationWrapper() в ReplyApp.kt и замените Column и его содержимое на NavigationSuiteScaffold :

ReplyApp.kt

@Composable
private fun ReplyNavigationWrapperUI(
    content: @Composable () -> Unit = {}
) {
    var selectedDestination: ReplyDestination by remember {
        mutableStateOf(ReplyDestination.Inbox)
    }

    NavigationSuiteScaffold(
        navigationSuiteItems = {
            ReplyDestination.entries.forEach {
                item(
                    selected = it == selectedDestination,
                    onClick = { /*TODO update selection*/ },
                    icon = {
                        Icon(
                            imageVector = it.icon,
                            contentDescription = stringResource(it.labelRes)
                        )
                    },
                    label = {
                        Text(text = stringResource(it.labelRes))
                    },
                )
            }
        }
    ) {
        content()
    }
}

Аргумент navigationSuiteItems представляет собой блок, позволяющий добавлять элементы с помощью функции item() , аналогично добавлению элементов в LazyColumn . Внутри лямбда-функции в конце блока этот код вызывает функцию content() переданную в качестве аргумента функции ReplyNavigationWrapperUI() .

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

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

  1. Добавьте следующий код непосредственно перед вызовом NavigationSuiteScaffold :

ReplyApp.kt

@Composable
private fun ReplyNavigationWrapperUI(
    content: @Composable () -> Unit = {}
) {
    var selectedDestination: ReplyDestination by remember {
        mutableStateOf(ReplyDestination.Inbox)
    }

    val windowSize = with(LocalDensity.current) {
        currentWindowSize().toSize().toDpSize()
    }
    val layoutType = if (windowSize.width >= 1200.dp) {
        NavigationSuiteType.NavigationDrawer
    } else {
        NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(
            currentWindowAdaptiveInfo()
        )
    }

    NavigationSuiteScaffold(
        layoutType = layoutType,
        ...
    ) {
        content()
    }
}

Этот код сначала получает размер окна и преобразует его в единицы DP с помощью currentWindowSize() и LocalDensity.current , а затем сравнивает ширину окна, чтобы определить тип компоновки навигационного интерфейса. Если ширина окна составляет не менее 1200.dp , используется NavigationSuiteType.NavigationDrawer . В противном случае используется расчет по умолчанию.

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

Демонстрация изменений адаптивности для устройств разных размеров.

Поздравляем, вы узнали о различных типах навигации, поддерживающих разные размеры и состояния окон!

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

5. Использование экранного пространства

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

В Material 3 определены три канонических макета, каждый из которых имеет конфигурации для компактного, среднего и расширенного размеров окна. Канонический макет List Detail идеально подходит для этого варианта использования и доступен в Compose как ListDetailPaneScaffold .

  1. Чтобы получить этот компонент, добавьте следующие зависимости и выполните синхронизацию Gradle:

gradle/libs.versions.toml

[libraries]
androidx-material3-adaptive-layout = { module = "androidx.compose.material3.adaptive:adaptive-layout", version.ref = "material3Adaptive" }
androidx-material3-adaptive-navigation = { module = "androidx.compose.material3.adaptive:adaptive-navigation", version.ref = "material3Adaptive" }

app/build.gradle.kts

dependencies {

    implementation(libs.androidx.material3.adaptive.layout)
    implementation(libs.androidx.material3.adaptive.navigation)
}
  1. Найдите компонуемую функцию ReplyAppContent() в ReplyApp.kt , которая в настоящее время отображает список только при вызове ReplyListPane() . Замените эту реализацию на ListDetailPaneScaffold , вставив следующий код. Поскольку это экспериментальный API, вам также потребуется добавить аннотацию @OptIn к функции ReplyAppContent() :

ReplyApp.kt

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun ReplyAppContent(
    replyHomeUIState: ReplyHomeUIState,
    onEmailClick: (Email) -> Unit,
) {
    val navigator = rememberListDetailPaneScaffoldNavigator<Long>()

    ListDetailPaneScaffold(
        directive = navigator.scaffoldDirective,
        value = navigator.scaffoldValue,
        listPane = {
            ReplyListPane(replyHomeUIState, onEmailClick)
        },
        detailPane = {
            ReplyDetailPane(replyHomeUIState.emails.first())
        }
    )
}

Этот код сначала создает навигатор, используя rememberListDetailPaneNavigator () . Навигатор предоставляет некоторые возможности управления отображением панели и содержимым, которое должно быть в ней представлено, что будет продемонстрировано позже.

ListDetailPaneScaffold отобразит две панели, когда будет развернут класс ширины окна. В противном случае она отобразит либо одну, либо другую панель в зависимости от значений двух параметров: директивы scaffold и значения scaffold. Для получения поведения по умолчанию этот код использует директиву scaffold и значение scaffold, предоставленные навигатором.

Остальные необходимые параметры представляют собой компонуемые лямбда-функции для панелей. ReplyListPane() и ReplyDetailPane() (найденные в ReplyListContent.kt ) используются для заполнения ролей панели списка и панели с подробной информацией соответственно. ReplyDetailPane() ожидает аргумент в виде адреса электронной почты, поэтому на данный момент этот код использует первый адрес электронной почты из списка адресов электронной почты в ReplyHomeUIState .

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

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

  1. Откройте ReplyHomeViewModel.kt и найдите класс данных ReplyHomeUIState . Добавьте свойство для выбранного адреса электронной почты со значением по умолчанию null :

ReplyHomeViewModel.kt

data class ReplyHomeUIState(
    val emails : List<Email> = emptyList(),
    val selectedEmail: Email? = null,
    val loading: Boolean = false,
    val error: String? = null
)
  1. В том же файле, ReplyHomeViewModel , есть функция setSelectedEmail() , которая вызывается при нажатии пользователем на элемент списка. Измените эту функцию, чтобы она копировала состояние пользовательского интерфейса и записывала выбранный адрес электронной почты:

ReplyHomeViewModel.kt

fun setSelectedEmail(email: Email) {
    _uiState.update {
        it.copy(selectedEmail = email)
    }
}

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

  1. В том же файле измените функцию observeEmails() . Когда список писем будет загружен, если в предыдущем состоянии пользовательского интерфейса не было выбранного письма, установите его значение равным первому элементу:

ReplyHomeViewModel.kt

private fun observeEmails() {
    viewModelScope.launch {
        emailsRepository.getAllEmails()
            .catch { ex ->
                _uiState.value = ReplyHomeUIState(error = ex.message)
            }
            .collect { emails ->
                val currentSelection = _uiState.value.selectedEmail
                _uiState.value = ReplyHomeUIState(
                    emails = emails,
                    selectedEmail = currentSelection ?: emails.first()
                )
            }
    }
}
  1. Вернитесь на ReplyApp.kt и, если выбранный адрес электронной почты доступен, заполните содержимое панели с подробной информацией:

ReplyApp.kt

ListDetailPaneScaffold(
    // ...
    detailPane = {
        if (replyHomeUIState.selectedEmail != null) {
            ReplyDetailPane(replyHomeUIState.selectedEmail)
        }
    }
)

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

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

  1. Чтобы это исправить, вставьте следующий код в качестве лямбда-функции, передаваемой в ReplyListPane :

ReplyApp.kt

ListDetailPaneScaffold(
    // ...
    listPane = {
        ReplyListPane(
            replyHomeUIState = replyHomeUIState,
            onEmailClick = { email ->
                onEmailClick(email)
                navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, email.id)
            }
        )
    },
    // ...
)

Эта лямбда-функция использует созданный ранее навигатор для добавления дополнительного поведения при щелчке по элементу. Она вызовет исходную лямбда-функцию, переданную в эту функцию, а затем также вызовет navigator.navigateTo() , указав, какая панель должна быть показана. Каждая панель в генеративном коде имеет связанную с ней роль, и для панели с подробной информацией это ListDetailPaneScaffoldRole.Detail . На небольших окнах это создаст впечатление, что приложение перешло на следующую страницу.

Приложение также должно обрабатывать действия пользователя при нажатии кнопки «Назад» в панели сведений, и это поведение будет различаться в зависимости от того, отображается одна или две панели.

  1. Для поддержки функции возврата назад добавьте следующий код.

ReplyApp.kt

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun ReplyAppContent(
    replyHomeUIState: ReplyHomeUIState,
    onEmailClick: (Email) -> Unit,
) {
    val navigator = rememberListDetailPaneScaffoldNavigator<Long>()

    BackHandler(navigator.canNavigateBack()) {
        navigator.navigateBack()
    }

    ListDetailPaneScaffold(
        directive = navigator.scaffoldDirective,
        value = navigator.scaffoldValue,
        listPane = {
            AnimatedPane {
                ReplyListPane(
                    replyHomeUIState = replyHomeUIState,
                    onEmailClick = { email ->
                        onEmailClick(email)
                        navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, email.id)
                    }
                )
            }
        },
        detailPane = {
            AnimatedPane {
                if (replyHomeUIState.selectedEmail != null) {
                    ReplyDetailPane(replyHomeUIState.selectedEmail)
                }
            }
        }
    )
}

Навигатор знает полное состояние ListDetailPaneScaffold , возможность возврата назад и что делать во всех этих сценариях. Этот код создает BackHandler , который активируется всякий раз, когда навигатор может вернуться назад, и внутри лямбда-функции вызывает navigateBack() . Кроме того, для более плавного перехода между панелями каждая панель обернута в AnimatedPane() .

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

Демонстрация изменений адаптивности для устройств разных размеров.

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

6. Поздравляем!

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

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

Что дальше?

Ознакомьтесь с другими практическими заданиями по Compose .

Примеры приложений

  • Примеры Compose представляют собой набор множества приложений, в которых воплощены лучшие практики, описанные в Codelabs.

Справочная документация