1. Введение
В этом практическом занятии вы узнаете о создании тем оформления для ваших приложений в Jetpack Compose с использованием Material Design 3. Вы также познакомитесь с ключевыми элементами Material Design 3: цветовыми схемами, типографикой и формами, которые помогут вам персонализировать и сделать ваше приложение доступным для всех.
Кроме того, вы изучите поддержку динамического оформления, а также различные уровни выделения.
Что вы узнаете
В этом практическом занятии вы узнаете:
- Ключевые аспекты оформления в стиле Material 3
- Цветовые схемы Material 3 и как создавать темы для вашего приложения.
- Как обеспечить поддержку динамического и светлого/темного оформления для вашего приложения
- Типографика и формы для персонализации вашего приложения.
- Компоненты Material 3 и возможность настройки стиля вашего приложения.
Что вы построите
В этом практическом занятии вы оформите почтовое приложение под названием Reply. Вы начнете с приложения без оформления, используя базовую тему, и примените полученные знания для оформления приложения и поддержки темных тем.

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

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

Конечная точка практического занятия по темам оформления с использованием темной цветовой гаммы и темной динамической темы.
Что вам понадобится
- Последняя версия Android Studio
- Базовые знания языка Kotlin.
- Базовое понимание Jetpack Compose
- Базовое знание элементов интерфейса Compose, таких как «Строка» , «Столбец» и «Модификатор».
2. Настройка
На этом этапе вы загружаете полный код приложения Reply, который будете стилизовать в этом практическом занятии.
Получите код
Код для этого практического занятия можно найти в репозитории android-compose-codelabs на GitHub. Чтобы клонировать его, выполните команду:
$ git clone https://github.com/googlecodelabs/android-compose-codelabs
В качестве альтернативы вы можете скачать два ZIP-файла:
Ознакомьтесь с примером приложения.
Загруженный вами код содержит код для всех доступных практических заданий Compose. Чтобы выполнить это практическое задание, откройте проект ThemingCodelab в Android Studio.
Мы рекомендуем начать с кода в основной ветке и шаг за шагом следовать инструкциям в руководстве в удобном для вас темпе. В любой момент вы можете запустить любую из версий в Android Studio, изменив ветку проекта в Git.
Изучение стартового кода
Основной код содержит пакет пользовательского интерфейса, который включает в себя следующие основные пакеты и файлы, с которыми вы будете взаимодействовать:
-
MainActivity.kt– точка входа в приложение Reply. -
com.example.reply.ui.theme– Этот пакет содержит темы оформления, типографику и цветовые схемы. В этом пакете вы будете добавлять темы Material Design. -
com.example.reply.ui.components– Содержит пользовательские компоненты приложения, такие как элементы списка, панели приложений и т. д. К этим компонентам будут применяться темы оформления. -
ReplyApp.kt– Это наша основная функция Composable, с которой будет начинаться дерево пользовательского интерфейса. В этом файле вы будете применять темы верхнего уровня.
В этом практическом занятии основное внимание будет уделено файлам пакетов ui .
3. Материал 3. Тематическое оформление
Jetpack Compose предлагает реализацию Material Design — комплексной системы дизайна для создания цифровых интерфейсов. Компоненты Material Design (кнопки, карточки, переключатели и т. д.) построены на основе Material Theming , которая представляет собой систематический способ настройки Material Design для лучшего отражения фирменного стиля вашего продукта.
Тема Material 3 включает в себя следующие подсистемы для добавления тем оформления в ваше приложение: цветовая схема , типографика и фигуры . При настройке этих параметров ваши изменения автоматически отражаются в компонентах M3, которые вы используете для создания приложения. Давайте рассмотрим каждую подсистему и реализуем её в примере приложения.

Материал 3 — подсистема цветов, типографики и форм.
4. Цветовые схемы
Основой цветовой схемы является набор из пяти ключевых цветов, каждый из которых соответствует тональной палитре из 13 оттенков, используемых компонентами Material 3.

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

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

Четыре тональных цвета базовой нейтральной цветовой гаммы.
Подробнее о цветовой гамме и роли цветов можно прочитать здесь.
Создание цветовых схем
Хотя вы можете создать собственную ColorScheme вручную, зачастую проще сгенерировать её, используя исходные цвета вашего бренда. Инструмент Material Theme Builder позволяет это сделать, а также, при желании, экспортировать код темы Compose.
Вы можете выбрать любой цвет, но для нашего случая мы будем использовать основной цвет ответа по умолчанию #825500 . Щелкните «Основной цвет» в разделе «Основные цвета» слева и добавьте код в палитру цветов.

Добавление кода основного цвета в Material Theme Builder.
После добавления основного цвета в Material Theme Builder вы увидите следующую тему и возможность экспорта в правом верхнем углу. В этом практическом задании вы экспортируете тему в Jetpack Compose.

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

Экспортирована цветовая схема светлого и темного цветов на основе основного цвета.
Сгенерированный файл The Color.kt содержит цвета вашей темы со всеми ролями, определенными как для светлых, так и для темных цветов темы.
Color.kt
package com.example.reply.ui.theme
import androidx.compose.ui.graphics.Color
val md_theme_light_primary = Color(0xFF825500)
val md_theme_light_onPrimary = Color(0xFFFFFFFF)
val md_theme_light_primaryContainer = Color(0xFFFFDDB3)
val md_theme_light_onPrimaryContainer = Color(0xFF291800)
val md_theme_light_secondary = Color(0xFF6F5B40)
val md_theme_light_onSecondary = Color(0xFFFFFFFF)
val md_theme_light_secondaryContainer = Color(0xFFFBDEBC)
val md_theme_light_onSecondaryContainer = Color(0xFF271904)
val md_theme_light_tertiary = Color(0xFF51643F)
val md_theme_light_onTertiary = Color(0xFFFFFFFF)
val md_theme_light_tertiaryContainer = Color(0xFFD4EABB)
val md_theme_light_onTertiaryContainer = Color(0xFF102004)
val md_theme_light_error = Color(0xFFBA1A1A)
val md_theme_light_errorContainer = Color(0xFFFFDAD6)
val md_theme_light_onError = Color(0xFFFFFFFF)
val md_theme_light_onErrorContainer = Color(0xFF410002)
val md_theme_light_background = Color(0xFFFFFBFF)
val md_theme_light_onBackground = Color(0xFF1F1B16)
val md_theme_light_surface = Color(0xFFFFFBFF)
val md_theme_light_onSurface = Color(0xFF1F1B16)
val md_theme_light_surfaceVariant = Color(0xFFF0E0CF)
val md_theme_light_onSurfaceVariant = Color(0xFF4F4539)
val md_theme_light_outline = Color(0xFF817567)
val md_theme_light_inverseOnSurface = Color(0xFFF9EFE7)
val md_theme_light_inverseSurface = Color(0xFF34302A)
val md_theme_light_inversePrimary = Color(0xFFFFB951)
val md_theme_light_shadow = Color(0xFF000000)
val md_theme_light_surfaceTint = Color(0xFF825500)
val md_theme_light_outlineVariant = Color(0xFFD3C4B4)
val md_theme_light_scrim = Color(0xFF000000)
val md_theme_dark_primary = Color(0xFFFFB951)
val md_theme_dark_onPrimary = Color(0xFF452B00)
val md_theme_dark_primaryContainer = Color(0xFF633F00)
val md_theme_dark_onPrimaryContainer = Color(0xFFFFDDB3)
val md_theme_dark_secondary = Color(0xFFDDC2A1)
val md_theme_dark_onSecondary = Color(0xFF3E2D16)
val md_theme_dark_secondaryContainer = Color(0xFF56442A)
val md_theme_dark_onSecondaryContainer = Color(0xFFFBDEBC)
val md_theme_dark_tertiary = Color(0xFFB8CEA1)
val md_theme_dark_onTertiary = Color(0xFF243515)
val md_theme_dark_tertiaryContainer = Color(0xFF3A4C2A)
val md_theme_dark_onTertiaryContainer = Color(0xFFD4EABB)
val md_theme_dark_error = Color(0xFFFFB4AB)
val md_theme_dark_errorContainer = Color(0xFF93000A)
val md_theme_dark_onError = Color(0xFF690005)
val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
val md_theme_dark_background = Color(0xFF1F1B16)
val md_theme_dark_onBackground = Color(0xFFEAE1D9)
val md_theme_dark_surface = Color(0xFF1F1B16)
val md_theme_dark_onSurface = Color(0xFFEAE1D9)
val md_theme_dark_surfaceVariant = Color(0xFF4F4539)
val md_theme_dark_onSurfaceVariant = Color(0xFFD3C4B4)
val md_theme_dark_outline = Color(0xFF9C8F80)
val md_theme_dark_inverseOnSurface = Color(0xFF1F1B16)
val md_theme_dark_inverseSurface = Color(0xFFEAE1D9)
val md_theme_dark_inversePrimary = Color(0xFF825500)
val md_theme_dark_shadow = Color(0xFF000000)
val md_theme_dark_surfaceTint = Color(0xFFFFB951)
val md_theme_dark_outlineVariant = Color(0xFF4F4539)
val md_theme_dark_scrim = Color(0xFF000000)
val seed = Color(0xFF825500)
Сгенерированный файл The Theme.kt содержит настройки для светлой и темной цветовых схем, а также для темы приложения. Он также содержит основную функцию для настройки темы, AppTheme() .
Theme.kt
package com.example.reply.ui.theme
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.runtime.Composable
private val LightColors = lightColorScheme(
primary = md_theme_light_primary,
onPrimary = md_theme_light_onPrimary,
primaryContainer = md_theme_light_primaryContainer,
onPrimaryContainer = md_theme_light_onPrimaryContainer,
secondary = md_theme_light_secondary,
onSecondary = md_theme_light_onSecondary,
secondaryContainer = md_theme_light_secondaryContainer,
onSecondaryContainer = md_theme_light_onSecondaryContainer,
tertiary = md_theme_light_tertiary,
onTertiary = md_theme_light_onTertiary,
tertiaryContainer = md_theme_light_tertiaryContainer,
onTertiaryContainer = md_theme_light_onTertiaryContainer,
error = md_theme_light_error,
errorContainer = md_theme_light_errorContainer,
onError = md_theme_light_onError,
onErrorContainer = md_theme_light_onErrorContainer,
background = md_theme_light_background,
onBackground = md_theme_light_onBackground,
surface = md_theme_light_surface,
onSurface = md_theme_light_onSurface,
surfaceVariant = md_theme_light_surfaceVariant,
onSurfaceVariant = md_theme_light_onSurfaceVariant,
outline = md_theme_light_outline,
inverseOnSurface = md_theme_light_inverseOnSurface,
inverseSurface = md_theme_light_inverseSurface,
inversePrimary = md_theme_light_inversePrimary,
surfaceTint = md_theme_light_surfaceTint,
outlineVariant = md_theme_light_outlineVariant,
scrim = md_theme_light_scrim,
)
private val DarkColors = darkColorScheme(
primary = md_theme_dark_primary,
onPrimary = md_theme_dark_onPrimary,
primaryContainer = md_theme_dark_primaryContainer,
onPrimaryContainer = md_theme_dark_onPrimaryContainer,
secondary = md_theme_dark_secondary,
onSecondary = md_theme_dark_onSecondary,
secondaryContainer = md_theme_dark_secondaryContainer,
onSecondaryContainer = md_theme_dark_onSecondaryContainer,
tertiary = md_theme_dark_tertiary,
onTertiary = md_theme_dark_onTertiary,
tertiaryContainer = md_theme_dark_tertiaryContainer,
onTertiaryContainer = md_theme_dark_onTertiaryContainer,
error = md_theme_dark_error,
errorContainer = md_theme_dark_errorContainer,
onError = md_theme_dark_onError,
onErrorContainer = md_theme_dark_onErrorContainer,
background = md_theme_dark_background,
onBackground = md_theme_dark_onBackground,
surface = md_theme_dark_surface,
onSurface = md_theme_dark_onSurface,
surfaceVariant = md_theme_dark_surfaceVariant,
onSurfaceVariant = md_theme_dark_onSurfaceVariant,
outline = md_theme_dark_outline,
inverseOnSurface = md_theme_dark_inverseOnSurface,
inverseSurface = md_theme_dark_inverseSurface,
inversePrimary = md_theme_dark_inversePrimary,
surfaceTint = md_theme_dark_surfaceTint,
outlineVariant = md_theme_dark_outlineVariant,
scrim = md_theme_dark_scrim,
)
@Composable
fun AppTheme(
useDarkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable() () -> Unit
) {
val colors = if (!useDarkTheme) {
LightColors
} else {
DarkColors
}
MaterialTheme(
colorScheme = colors,
content = content
)
}
Ключевым элементом для реализации тем оформления в Jetpack Compose является компонент MaterialTheme .
Вы оборачиваете компонент MaterialTheme() в функцию AppTheme() , которая принимает два параметра:
- Параметр
useDarkThemeсвязан с функциейisSystemInDarkTheme()и отслеживает системные настройки тем оформления, применяя светлую или темную тему. Если вы хотите вручную использовать светлую или темную тему для своего приложения, вы можете передать логическое значение параметруuseDarkTheme. -
content— это контент, к которому будет применена данная тема.
Theme.kt
@Composable
fun AppTheme(
useDarkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable() () -> Unit
) {
val colors = if (!useDarkTheme) {
LightColors
} else {
DarkColors
}
MaterialTheme(
colorScheme = colors,
content = content
)
}
Если вы сейчас запустите приложение, вы увидите, что оно выглядит так же. Даже несмотря на то, что вы импортировали нашу новую цветовую схему с новыми цветами темы, вы по-прежнему видите базовую тему, потому что вы не применили тему к приложению Compose.

Приложение с базовой темой оформления, если тема не применена.
Чтобы применить новую тему, в файле MainActivity.kt оберните основной компонуемый ReplyApp в основную функцию настройки темы, AppTheme() .
MainActivity.kt
setContent {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
AppTheme {
ReplyApp(/*..*/)
}
}
Вам также потребуется обновить функции предварительного просмотра, чтобы увидеть примененную тему к предварительным просмотрам приложения. Оберните составной объект ReplyApp в функцию ReplyAppPreview() с помощью AppTheme , чтобы применить тему к предварительным просмотрам.
В параметрах предварительного просмотра заданы как светлая, так и темная системные темы, поэтому вы увидите оба варианта предварительного просмотра.
MainActivity.kt
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
name = "DefaultPreviewDark"
)
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_NO,
name = "DefaultPreviewLight"
)
@Composable
fun ReplyAppPreview() {
AppTheme {
ReplyApp(
replyHomeUIState = ReplyHomeUIState(
emails = LocalEmailsDataProvider.allEmails
)
)
}
}
Если вы запустите приложение сейчас, вы должны увидеть предварительный просмотр приложения с цветами импортированной темы вместо базовой темы.


Приложение с базовой темой (слева).
Приложение с импортированной цветовой темой (справа).

Предварительный просмотр приложения в светлой и темной версиях с импортированными цветовыми темами.
Material 3 поддерживает как светлую, так и темную цветовые схемы. Вы просто обернули приложение импортированной темой; компоненты Material 3 используют цветовые роли по умолчанию.
Прежде чем добавлять цвета в приложение, давайте разберемся с их ролями и применением.
Цветовые роли и доступность
Каждый цветовой параметр может использоваться в различных местах в зависимости от состояния, значимости и акцента компонента.

Роль основных, вторичных и третичных цветов.
Основной цвет — это базовый цвет, используемый для главных компонентов, таких как заметные кнопки и активные состояния.
Дополнительный ключевой цвет используется для менее заметных элементов пользовательского интерфейса, таких как фильтрующие элементы.
Третий ключевой цвет используется для создания контрастных акцентов, а нейтральные цвета применяются для фона и поверхностей в приложении.
Цветовая система Material Design предоставляет стандартные значения тонов и измерения, которые можно использовать для достижения доступных коэффициентов контрастности. Используйте основной цвет поверх основного, основной цвет-контейнер поверх основного контейнера, и то же самое для других акцентных и нейтральных цветов, чтобы обеспечить доступный для пользователя контраст.
Для получения более подробной информации см. раздел «Цветовые роли и доступность» .
Тональные и теневые возвышения
В материале 3 высота создается преимущественно с помощью наложений тональных цветов. Это новый способ различения контейнеров и поверхностей друг от друга — усиление тонального эффекта достигается за счет использования более выраженного тона — в дополнение к теням.
Повышение тональности на уровне 2, при котором цвет заимствуется из основного цветового диапазона.
В темных темах интерфейса наложения с учетом высоты также были заменены на наложения с тональными цветами в Material Design 3. Цвет наложения берется из слота основного цвета.
Поверхность M3 Surface — подложка, используемая в большинстве компонентов M3, — поддерживает как тональное, так и теневое усиление:
Surface(
modifier = modifier,
tonalElevation = {..}
shadowElevation = {..}
) {
Column(content = content)
}
Добавление цветов в приложение
Если вы запустите приложение, вы увидите, что экспортированные цвета отображаются в приложении там, где компоненты используют цвета по умолчанию. Теперь, когда мы знаем о ролях цветов и их использовании, давайте оформим приложение в соответствии с правильными ролями цветов.

Приложение с цветовой темой и компонентами, использующими цветовые роли по умолчанию.
Цвета поверхности
На главном экране начните с того, что оберните основной компонент приложения в Surface() чтобы создать основу для размещения контента приложения поверх него. Откройте MainActivity.kt и оберните компонент ReplyApp() в Surface .
Также необходимо задать тональное повышение на 5 dp, чтобы придать поверхности тональный цвет основного слота, что поможет создать контраст с элементом списка и строкой поиска над ним. По умолчанию тональное и теневое повышение для поверхности составляет 0 dp.
MainActivity.kt
AppTheme {
Surface(tonalElevation = 5.dp) {
ReplyApp(
replyHomeUIState = uiState,
// other parameters
)
}
}
Если вы запустите приложение сейчас и увидите страницы «Список» и «Подробности», вы должны заметить, что тоновая текстура применена ко всему приложению.


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

Пользовательская панель поиска без фона (слева).
Настраиваемая панель поиска с фоном (справа).
Теперь отредактируйте файл ui/components/ReplyAppBars.kt , содержащий панель приложения. Добавьте MaterialTheme.colorScheme.background в Modifier Row Composable.
ReplyAppBars.kt
@Composable
fun ReplySearchBar(modifier: Modifier = Modifier) {
Row(
modifier = modifier
.fillMaxWidth()
.padding(16.dp)
.background(MaterialTheme.colorScheme.background),
verticalAlignment = Alignment.CenterVertically
) {
// Search bar content
}
}
Теперь вы должны увидеть четкое разделение между тональной поверхностью и панелью приложения с фоновым цветом.

Поисковая строка с фоновым цветом поверх тональной поверхности.
Цвета плавающих кнопок действий

Большая кнопка FAB без каких-либо примененных тем оформления (слева).
Тематический большой FAB с третичным цветом (справа).
На главном экране можно улучшить внешний вид плавающей кнопки действия (FAB), чтобы она выделялась как кнопка призыва к действию. Для этого нужно добавить к ней дополнительный акцентный цвет.
В файле ReplyListContent.kt обновите параметр containerColor для кнопки FAB на цвет tertiaryContainer , а цвет содержимого — на onTertiaryContainer , чтобы сохранить доступность и цветовой контраст.
ReplyListContent.kt
ReplyInboxScreen(/*..*/) {
// Email list content
LargeFloatingActionButton(
containerColor = MaterialTheme.colorScheme.tertiaryContainer,
contentColor = MaterialTheme.colorScheme.onTertiaryContainer
){
/*..*/
}
}
Запустите приложение, чтобы увидеть выбранную вами тему оформления FAB. Для этого практического задания вы используете LargeFloatingActionButton .
Цвета карт
Список рассылок на главном экране использует компонент карточки. По умолчанию это заполненная карточка , в которой в качестве цвета контейнера используется вариант цвета поверхности, что обеспечивает четкое разделение между цветом поверхности и цветом карточки. Compose также предоставляет реализации ElevatedCard и OutlinedCard .
Вы можете дополнительно выделить важные элементы, добавив дополнительные цветовые тона. Для этого вам нужно будет изменить файл ui/components/ReplyEmailListItem.kt , обновив цвет контейнера карточки с помощью CardDefaults.cardColors() для важных писем:
ReplyEmailListItem.kt
Card(
modifier = modifier
.padding(horizontal = 16.dp, vertical = 4.dp)
.semantics { selected = isSelected }
.clickable { navigateToDetail(email.id) },
colors = CardDefaults.cardColors(
containerColor = if (email.isImportant)
MaterialTheme.colorScheme.secondaryContainer
else MaterialTheme.colorScheme.surfaceVariant
)
){
/*..*/
}


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


Страница с подробной информацией по умолчанию без тематического элемента списка (слева).
Подробный элемент списка с примененным фоновым оформлением (справа).
К элементу списка не применено никакого цвета, поэтому используется стандартный тональный цвет поверхности. Вы примените цвет фона к элементу списка, чтобы создать разделение, и добавите отступы, чтобы обеспечить пространство вокруг фона.
ReplyEmailThreadItem.kt
@Composable
fun ReplyEmailThreadItem(
email: Email,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier
.fillMaxWidth()
.padding(16.dp)
.background(MaterialTheme.colorScheme.background)
.padding(20.dp)
) {
// List item content
}
}
Как видите, просто добавив фон, вы получаете четкое разделение между тональной поверхностью и элементом списка.
Теперь у вас есть и главная страница, и страница с подробной информацией, с правильным распределением и использованием цветовых ролей. Давайте посмотрим, как ваше приложение может использовать динамические цвета для обеспечения еще более персонализированного и целостного пользовательского опыта.
5. Добавление динамических цветов в приложение.
Динамическая цветокоррекция — ключевая особенность Material 3, в которой алгоритм подбирает пользовательские цвета на основе обоев пользователя для применения в его приложениях и системном интерфейсе.
Динамическое оформление делает ваши приложения более персонализированными. Оно также обеспечивает пользователям целостный и бесшовный опыт взаимодействия с системной темой.
Динамическая настройка цвета доступна в Android 12 и выше. Если динамическая настройка цвета доступна, вы можете настроить динамическую цветовую схему, используя dynamicDarkColorScheme() или dynamicLightColorScheme() . В противном случае следует использовать стандартную светлую или темную ColorScheme .
Замените код функции AppTheme в файле Theme.kt на приведенный ниже:
Theme.kt
@Composable
fun AppTheme(
useDarkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val context = LocalContext.current
val colors = when {
(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) -> {
if (useDarkTheme) dynamicDarkColorScheme(context)
else dynamicLightColorScheme(context)
}
useDarkTheme -> DarkColors
else -> LightColors
}
MaterialTheme(
colorScheme = colors,
content = content
)
}

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

Приложение без цвета строки состояния (слева).
Приложение с примененным цветом строки состояния (справа).
Чтобы изменить цвет строки состояния в зависимости от основного цвета вашей темы, добавьте цвет строки состояния после выбора цветовой схемы в составном элементе AppTheme :
Theme.kt
@Composable
fun AppTheme(
useDarkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
// color scheme selection code
// Add primary status bar color from chosen color scheme.
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as Activity).window
window.statusBarColor = colors.primary.toArgb()
WindowCompat
.getInsetsController(window, view)
.isAppearanceLightStatusBars = useDarkTheme
}
}
MaterialTheme(
colorScheme = colors,
content = content
)
}
При запуске приложения строка состояния должна принять вашу основную цветовую тему. Вы также можете попробовать динамическую светлую и темную темы, изменив системную темную тему.

Динамическая светлая (слева) и темная (справа) темы, примененные к стандартным обоям Android 13.
До сих пор вы применяли цвета к своему приложению, что улучшало его внешний вид. Однако вы видите, что весь текст в приложении имеет одинаковый размер, поэтому теперь вы можете добавить типографику.
6. Типография
В Material Design 3 определена шкала шрифтов . Названия и группировка упрощены до: display, headline, title, body и label, с большим, средним и малым размерами для каждого.

Материал 3 типа шкалы.
Определение типографики
Compose предоставляет класс M3 Typography — наряду с существующими классами TextStyle и классами font-related , — для моделирования шкалы шрифтов Material 3.
Конструктор Typography предлагает значения по умолчанию для каждого стиля, поэтому вы можете опустить любые параметры, которые не хотите настраивать. Для получения дополнительной информации см. стили типографики и их значения по умолчанию .
В вашем приложении будут использоваться пять стилей типографики: headlineSmall , titleLarge , bodyLarge , bodyMedium и labelMedium . Эти стили будут применяться как на главном экране, так и на экране с подробной информацией.

На экране демонстрируется использование типографики в заголовках, метках и основном тексте.
Далее перейдите в пакет ui/theme и откройте Type.kt Добавьте следующий код, чтобы использовать собственные реализации для некоторых стилей текста вместо значений по умолчанию:
Type.kt
val typography = Typography(
headlineSmall = TextStyle(
fontWeight = FontWeight.SemiBold,
fontSize = 24.sp,
lineHeight = 32.sp,
letterSpacing = 0.sp
),
titleLarge = TextStyle(
fontWeight = FontWeight.Normal,
fontSize = 18.sp,
lineHeight = 28.sp,
letterSpacing = 0.sp
),
bodyLarge = TextStyle(
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.15.sp
),
bodyMedium = TextStyle(
fontWeight = FontWeight.Medium,
fontSize = 14.sp,
lineHeight = 20.sp,
letterSpacing = 0.25.sp
),
labelMedium = TextStyle(
fontWeight = FontWeight.SemiBold,
fontSize = 12.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
)
Теперь ваша типографика определена. Чтобы добавить её в вашу тему, передайте её в составной объект MaterialTheme() внутри AppTheme :
Theme.kt
@Composable
fun AppTheme(
useDarkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable() () -> Unit
) {
// dynamic theming content
MaterialTheme(
colorScheme = colors,
typography = typography,
content = content
)
}
Работа с типографикой
Как и в случае с цветами, доступ к стилю типографики для текущей темы осуществляется с помощью MaterialTheme.typography . Это позволяет использовать экземпляр типографики, определенный в файле Type.k
Text(
text = "Hello M3 theming",
style = MaterialTheme.typography.titleLarge
)
Text(
text = "you are learning typography",
style = MaterialTheme.typography.bodyMedium
)
Вашему продукту, скорее всего, не понадобятся все 15 стандартных стилей из шкалы типов Material Design. В этом практическом задании выбраны пять размеров, а остальные опущены.
Поскольку вы не применили типографику к составным элементам Text( ), весь текст по умолчанию будет иметь тип Typography.bodyLarge .
Типографика списка главной страницы
Далее, примените типографику к функции ReplyEmailListItem в файле ui/components/ReplyEmailListItem.kt , чтобы создать различие между заголовками и метками:
ReplyEmailListItem.kt
Text(
text = email.sender.firstName,
style = MaterialTheme.typography.labelMedium
)
Text(
text = email.createdAt,
style = MaterialTheme.typography.labelMedium
)
Text(
text = email.subject,
style = MaterialTheme.typography.titleLarge,
modifier = Modifier.padding(top = 12.dp, bottom = 8.dp),
)
Text(
text = email.body,
maxLines = 2,
style = MaterialTheme.typography.bodyLarge,
overflow = TextOverflow.Ellipsis
)


Главный экран без типографики (слева).
Главный экран с примененным типографическим оформлением (справа).
Подробный список типографики
Аналогичным образом, вы добавите типографику на экране с подробной информацией, обновив все текстовые элементы ReplyEmailThreadItem в файле ui i/components/ReplyEmailThreadItem.kt :
ReplyEmailThreadItem.kt
Text(
text = email.sender.firstName,
style = MaterialTheme.typography.labelMedium
)
Text(
text = stringResource(id = R.string.twenty_mins_ago),
style = MaterialTheme.typography.labelMedium
)
Text(
text = email.subject,
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(top = 12.dp, bottom = 8.dp),
)
Text(
text = email.body,
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant
)


Подробный экран без типографики (слева).
Подробный экран с примененным типографическим оформлением (справа).
Настройка типографики
С помощью Compose очень легко настроить стиль текста или указать собственный шрифт. Вы можете изменить TextStyle , чтобы настроить тип шрифта, семейство шрифтов, межбуквенный интервал и т.д.
Вы измените стиль текста в файле theme/Type.kt , и эти изменения будут применены ко всем компонентам, использующим этот стиль.
Измените значение fontWeight на SemiBold и lineHeight на 32.sp для titleLarge , который используется для заголовка в элементе списка. Это позволит выделить заголовок и обеспечить четкое разделение.
Type.kt
...
titleLarge = TextStyle(
fontWeight = FontWeight.SemiBold,
fontSize = 18.sp,
lineHeight = 32.sp,
letterSpacing = 0.0.sp
),
...

Применение пользовательской типографики к тематическому тексту.
7. Формы
Материальные поверхности могут иметь различные формы. Формы привлекают внимание, идентифицируют компоненты, передают информацию о состоянии и выражают имидж бренда.
Определение форм
Класс Compose предоставляет классу Shapes расширенные параметры для реализации новых фигур M3. Масштаб фигуры M3, аналогично типу scale , позволяет создавать выразительные фигуры в пользовательском интерфейсе.
В шкале форм представлены фигуры разных размеров:
- Очень маленький
- Маленький
- Середина
- Большой
- Очень большой
По умолчанию каждая фигура имеет значение по умолчанию, которое можно переопределить. В вашем приложении вы будете использовать фигуру среднего размера для изменения элемента списка, но вы также можете объявить и другие фигуры. Создайте новый файл с именем Shape.kt в пакете ui/theme и добавьте код для фигур:
Shape.kt
package com.example.reply.ui.theme
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Shapes
import androidx.compose.ui.unit.dp
val shapes = Shapes(
extraSmall = RoundedCornerShape(4.dp),
small = RoundedCornerShape(8.dp),
medium = RoundedCornerShape(16.dp),
large = RoundedCornerShape(24.dp),
extraLarge = RoundedCornerShape(32.dp)
)
Теперь, когда вы определили свои shapes , передайте их в MaterialTheme M3 так же, как вы это делали для цветов и типографики:
Theme.kt
@Composable
fun AppTheme(
useDarkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable() () -> Unit
) {
// dynamic theming content
MaterialTheme(
colorScheme = colors,
typography = typography,
shapes = shapes
content = content
)
}
Работа с геометрическими фигурами
Подобно цвету и типографике, вы можете применять фигуры к компонентам Material с помощью MaterialTheme.shape , который предоставляет вам экземпляр Shape для доступа к фигурам Material.
Многие компоненты Material Design уже имеют заданные по умолчанию формы, но вы можете задавать и применять собственные формы к компонентам через доступные слоты.
Card(shape = MaterialTheme.shapes.medium) { /* card content */ }
FloatingActionButton(shape = MaterialTheme.shapes.large) { /* fab content */}
Отображение материальных компонентов с использованием различных типов форм.
Сопоставление фигур для всех компонентов можно посмотреть в документации по элементам Shape.
В Compose доступны еще две фигуры — RectangleShape и CircleShape . У фигуры RectangleShape нет радиуса скругления углов, а у фигуры CircleShape — полностью обведенные края.
Вы также можете придавать форму своим компонентам, используя Modifiers , которые принимают форму, например, Modifier.clip , Modifier.background и Modifier.border .
форма панели приложения
Мы хотим, чтобы панель приложения имела фон со скругленными углами:

В TopAppBar используется Row с фоновым цветом. Чтобы добиться закругленных углов фона, задайте форму фона, передав параметр CircleShape в модификатор фона:
ReplyAppBars.kt
@Composable
fun ReplySearchBar(modifier: Modifier = Modifier) {
Row(
modifier = modifier
.fillMaxWidth()
.padding(16.dp)
.background(
MaterialTheme.colorScheme.background,
CircleShape
),
verticalAlignment = Alignment.CenterVertically
) {
// Search bar content
}
}

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


Подробный список элементов с пустым полем (слева) и полем средней формы (справа).
ReplyEmailThreadItem.kt
@Composable
fun ReplyEmailThreadItem(
email: Email,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier
.fillMaxWidth()
.padding(8.dp)
.background(
MaterialTheme.colorScheme.background,
MaterialTheme.shapes.medium
)
.padding(16.dp)
) {
// List item content
}
}
Теперь при запуске приложения на экране отображается подробный список элементов, имеющий medium .
8. Акцент
Функция выделения в пользовательском интерфейсе помогает подчеркнуть один контент по сравнению с другим, например, когда нужно отличить заголовок от подзаголовков. В M3 функция выделения использует различные цветовые вариации и их цветовые сочетания. Есть два способа добавить выделение:
- Использование цветов поверхности, вариантов поверхности и фона, а также цветов поверхности и вариантов поверхности из расширенной цветовой системы M3.
Например, surface можно использовать с on-surface-variant, а surface-variant можно использовать с on-surface для обеспечения разных уровней акцента.
Варианты отделки поверхности также можно использовать с акцентными цветами, чтобы сделать их менее заметными, чем основные акцентные цвета, но при этом сохранить доступность и соответствие коэффициенту контрастности.

Роли цвета поверхности, фона и варианта цвета поверхности.
- Использование разной толщины шрифта для текста. Как вы видели в разделе типографики, вы можете задать пользовательскую толщину шрифта для создания различного акцента.
Далее обновите ReplyEmailListItem.kt , чтобы задать разницу в выделении с использованием варианта поверхности. По умолчанию содержимое карточки принимает цвет содержимого по умолчанию в зависимости от фона.
Вы измените цвет составного текста времени и основного текста на значение из onSurfaceVariant . Это уменьшит их акцент по сравнению со onContainerColors , которое по умолчанию применяется к составным текстам темы и заголовка.


Время и основной текст выделены с тем же акцентом, что и тема и заголовок (слева).
Время и основная часть текста с меньшим акцентом по сравнению с темой и заголовком (справа).
ReplyEmailListItem.kt
Text(
text = email.createdAt,
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Text(
text = email.body,
maxLines = 2,
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant
overflow = TextOverflow.Ellipsis
)
Для важной карточки электронного письма с фоном secondaryContainer весь цвет текста по умолчанию соответствует цвету onSecondaryContainer . Для остальных писем фон имеет значение surfaceVariant, поэтому весь текст по умолчанию имеет цвет onSurfaceVariant .
9. Поздравляем!
Поздравляем! Вы успешно завершили этот практический урок! Вы реализовали тему оформления Material Design с помощью Compose, используя цвета, типографику и фигуры, а также динамические цвета, чтобы оформить ваше приложение и обеспечить персонализированный пользовательский опыт.



В итоге получился дизайн с динамическими цветами и примененной цветовой темой.
Что дальше?
Ознакомьтесь с другими нашими практическими занятиями по Compose :
- Основы написания музыки
- Создание макетов
- Состояние в составе
- Создание приложений для существующих приложений
Дополнительная информация
- Руководство по созданию тем оформления
- Материальное оформление для композиции
Примеры приложений
- Пример приложения с полным оформлением в стиле Material 3.
- JetChat демонстрирует динамическое оформление тем.