Material Design 2 в Compose

Jetpack Compose предлагает реализацию Material Design — комплексной системы проектирования для создания цифровых интерфейсов. Компоненты Material Design (кнопки, карточки, переключатели и т. д.) создаются на основе Material Theming , который представляет собой систематический способ настройки Material Design для лучшего отражения бренда вашего продукта. Тема материала содержит атрибуты цвета , типографики и формы . Когда вы настраиваете эти атрибуты, ваши изменения автоматически отражаются в компонентах, которые вы используете для создания своего приложения.

Jetpack Compose реализует эти концепции с помощью компонуемого MaterialTheme :

MaterialTheme(
    colors = // ...
    typography = // ...
    shapes = // ...
) {
    // app content
}

Настройте параметры, которые вы передаете в MaterialTheme для оформления вашего приложения.

Два контрастных скриншота. В первом используется стиль MaterialTheme по умолчанию, на втором снимке экрана — измененный стиль.

Рисунок 1. На первом снимке экрана показано приложение, которое не настраивает MaterialTheme и поэтому использует стиль по умолчанию. На втором снимке экрана показано приложение, которое передает параметры в MaterialTheme для настройки стиля.

Цвет

Цвета моделируются в Compose с помощью класса Color , простого класса хранения данных.

val Red = Color(0xffff0000)
val Blue = Color(red = 0f, green = 0f, blue = 1f)

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

Пример цветовой палитры темы

Рисунок 2. Цветовая система материала.

Compose предоставляет класс Colors для моделирования цветовой системы Material . Colors предоставляет функции конструктора для создания наборов светлых или темных цветов:

private val Yellow200 = Color(0xffffeb46)
private val Blue200 = Color(0xff91a4fc)
// ...

private val DarkColors = darkColors(
    primary = Yellow200,
    secondary = Blue200,
    // ...
)
private val LightColors = lightColors(
    primary = Yellow500,
    primaryVariant = Yellow400,
    secondary = Blue700,
    // ...
)

После того, как вы определили свои Colors вы можете передать их в MaterialTheme :

MaterialTheme(
    colors = if (darkTheme) DarkColors else LightColors
) {
    // app content
}

Использование цветов темы

Вы можете получить Colors , предоставленные для составного объекта MaterialTheme , с помощью MaterialTheme.colors .

Text(
    text = "Hello theming",
    color = MaterialTheme.colors.primary
)

Цвет поверхности и содержимого

Многие компоненты принимают пару цвета и цвета содержимого:

Surface(
    color = MaterialTheme.colors.surface,
    contentColor = contentColorFor(color),
    // ...
) { /* ... */ }

TopAppBar(
    backgroundColor = MaterialTheme.colors.primarySurface,
    contentColor = contentColorFor(backgroundColor),
    // ...
) { /* ... */ }

Это позволяет вам не только установить цвет составного объекта, но также предоставить цвет по умолчанию для содержимого и составных элементов, содержащихся в нем. Многие составные элементы используют этот цвет содержимого по умолчанию. Например, цвет Text основывается на цвете содержимого родительского элемента, а Icon использует этот цвет для установки своего оттенка.

Два примера одного и того же баннера в разных цветах.

Рис. 3. Установка разных цветов фона приводит к разным цветам текста и значков.

Метод contentColorFor() извлекает соответствующий «включенный» цвет для любых цветов темы. Например, если вы установили primary цвет фона на Surface , он использует эту функцию, чтобы установить onPrimary в качестве цвета содержимого. Если вы установили цвет фона, не относящийся к теме, вам также следует указать соответствующий цвет содержимого. Используйте LocalContentColor чтобы получить предпочтительный цвет содержимого для текущего фона в заданной позиции в иерархии.

Альфа-контент

Часто вам нужно изменить степень подчеркивания контента, чтобы передать его важность и обеспечить визуальную иерархию. Рекомендации по разборчивости текста Material Design советуют использовать разные уровни непрозрачности для передачи разных уровней важности.

Jetpack Compose реализует это через LocalContentAlpha . Вы можете указать альфу контента для иерархии , указав значение для этого CompositionLocal . Вложенные составные элементы могут использовать это значение для применения альфа-обработки к их содержимому. Например, Text и Icon по умолчанию используют комбинацию LocalContentColor настроенную для использования LocalContentAlpha . Материал определяет некоторые стандартные значения альфа ( high , medium , disabled ), которые моделируются объектом ContentAlpha .

// By default, both Icon & Text use the combination of LocalContentColor &
// LocalContentAlpha. De-emphasize content by setting content alpha
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
    Text(
        // ...
    )
}
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.disabled) {
    Icon(
        // ...
    )
    Text(
        // ...
    )
}

Чтобы узнать больше о CompositionLocal , ознакомьтесь с руководством по локальным данным с помощью CompositionLocal .

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

Рисунок 4. Примените к тексту различные уровни выделения, чтобы визуально передать иерархию информации. Первая строка текста — это заголовок, она содержит наиболее важную информацию и поэтому использует ContentAlpha.high . Вторая строка содержит менее важные метаданные и поэтому использует ContentAlpha.medium .

Темная тема

В Compose вы реализуете светлые и темные темы, предоставляя различные наборы Colors для компонуемого MaterialTheme :

@Composable
fun MyTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit
) {
    MaterialTheme(
        colors = if (darkTheme) DarkColors else LightColors,
        /*...*/
        content = content
    )
}

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

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

val isLightTheme = MaterialTheme.colors.isLight
Icon(
    painterResource(
        id = if (isLightTheme) {
            R.drawable.ic_sun_24
        } else {
            R.drawable.ic_moon_24
        }
    ),
    contentDescription = "Theme"
)

Наложения высот

В «Материале» поверхности в темных темах с большей высотой получают наложения высот , которые осветляют их фон. Чем выше высота поверхности (поднимая ее ближе к предполагаемому источнику света), тем светлее становится эта поверхность.

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

Surface(
    elevation = 2.dp,
    color = MaterialTheme.colors.surface, // color will be adjusted for elevation
    /*...*/
) { /*...*/ }

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

Рисунок 5. Карточки и нижняя навигация используют цвет surface в качестве фона. Поскольку карточки и нижняя навигация находятся на разных уровнях над фоном, они имеют немного разные цвета: карточки светлее фона, а нижняя навигация светлее карточек.

Для пользовательских сценариев, не использующих Surface , используйте LocalElevationOverlay , CompositionLocal , содержащий ElevationOverlay , используемый компонентами Surface :

// Elevation overlays
// Implemented in Surface (and any components that use it)
val color = MaterialTheme.colors.surface
val elevation = 4.dp
val overlaidColor = LocalElevationOverlay.current?.apply(
    color, elevation
)

Чтобы отключить наложение высот, укажите null в нужной точке составной иерархии:

MyTheme {
    CompositionLocalProvider(LocalElevationOverlay provides null) {
        // Content without elevation overlays
    }
}

Ограниченные цветовые акценты

Material рекомендует применять ограниченные цветовые акценты для темных тем, предпочитая в большинстве случаев использование цвета surface primary цвету. Компонуемые материалы, такие как TopAppBar и BottomNavigation реализуют это поведение по умолчанию.

Рисунок 6. Темная тема материала с ограниченным количеством цветовых акцентов. Верхняя панель приложения использует основной цвет в светлой теме и цвет поверхности в темной теме.

Для пользовательских сценариев используйте свойство расширения primarySurface :

Surface(
    // Switches between primary in light theme and surface in dark theme
    color = MaterialTheme.colors.primarySurface,
    /*...*/
) { /*...*/ }

Типография

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

Пример нескольких разных шрифтов в разных стилях

Рисунок 7. Система типов материала.

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

val raleway = FontFamily(
    Font(R.font.raleway_regular),
    Font(R.font.raleway_medium, FontWeight.W500),
    Font(R.font.raleway_semibold, FontWeight.SemiBold)
)

val myTypography = Typography(
    h1 = TextStyle(
        fontFamily = raleway,
        fontWeight = FontWeight.W300,
        fontSize = 96.sp
    ),
    body1 = TextStyle(
        fontFamily = raleway,
        fontWeight = FontWeight.W600,
        fontSize = 16.sp
    )
    /*...*/
)
MaterialTheme(typography = myTypography, /*...*/) {
    /*...*/
}

Если вы хотите использовать один и тот же шрифт повсюду, укажите defaultFontFamily parameter и опустите fontFamily для любых элементов TextStyle :

val typography = Typography(defaultFontFamily = raleway)
MaterialTheme(typography = typography, /*...*/) {
    /*...*/
}

Использование стилей текста

Доступ к TextStyle осуществляется через MaterialTheme.typography . Получите TextStyle следующим образом:

Text(
    text = "Subtitle2 styled",
    style = MaterialTheme.typography.subtitle2
)

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

Рисунок 8. Используйте различные шрифты и стили, чтобы выразить свой бренд.

Форма

Материал определяет систему форм , позволяющую определять формы для больших, средних и малых компонентов.

Показывает различные формы Material Design.

Рисунок 9. Система форм Material.

Compose реализует систему фигур с помощью класса Shapes , который позволяет указать CornerBasedShape для каждой категории размера:

val shapes = Shapes(
    small = RoundedCornerShape(percent = 50),
    medium = RoundedCornerShape(0f),
    large = CutCornerShape(
        topStart = 16.dp,
        topEnd = 0.dp,
        bottomEnd = 0.dp,
        bottomStart = 16.dp
    )
)

MaterialTheme(shapes = shapes, /*...*/) {
    /*...*/
}

Многие компоненты используют эти формы по умолчанию. Например, Button , TextField и FloatingActionButton по умолчанию имеют малый размер, AlertDialog — средний, а ModalDrawer — большой — полное сопоставление см . в справочнике по схеме формы .

Использование фигур

Доступ Shape осуществляется через MaterialTheme.shapes . Получите Shape с помощью такого кода:

Surface(
    shape = MaterialTheme.shapes.medium, /*...*/
) {
    /*...*/
}

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

Рисунок 10. Используйте фигуры, чтобы выразить бренд или государство.

Стили по умолчанию

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

@Composable
fun MyButton(
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    content: @Composable RowScope.() -> Unit
) {
    Button(
        colors = ButtonDefaults.buttonColors(
            backgroundColor = MaterialTheme.colors.secondary
        ),
        onClick = onClick,
        modifier = modifier,
        content = content
    )
}

Наложения тем

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

Кроме того, при переносе экранов на основе просмотра в Compose следите за использованием атрибута android:theme . Вероятно, вам понадобится новая MaterialTheme в этой части дерева пользовательского интерфейса Compose.

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

Рисунок 11. Вложенные темы.

@Composable
fun DetailsScreen(/* ... */) {
    PinkTheme {
        // other content
        RelatedSection()
    }
}

@Composable
fun RelatedSection(/* ... */) {
    BlueTheme {
        // content
    }
}

Состояния компонентов

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

Компонуемые объекты часто имеют enabled параметр. Установка значения false предотвращает взаимодействие и изменяет такие свойства, как цвет и высота, чтобы визуально передать состояние компонента.

Рисунок 12. Кнопка с enabled = true (слева) и enabled = false (справа).

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

Button(
    onClick = { /* ... */ },
    enabled = true,
    // Custom colors for different states
    colors = ButtonDefaults.buttonColors(
        backgroundColor = MaterialTheme.colors.secondary,
        disabledBackgroundColor = MaterialTheme.colors.onBackground
            .copy(alpha = 0.2f)
            .compositeOver(MaterialTheme.colors.background)
        // Also contentColor and disabledContentColor
    ),
    // Custom elevation for different states
    elevation = ButtonDefaults.elevation(
        defaultElevation = 8.dp,
        disabledElevation = 2.dp,
        // Also pressedElevation
    )
) { /* ... */ }

Рисунок 13. Кнопка с enabled = true (слева) и enabled = false (справа) с настроенными значениями цвета и высоты.

Рябь

Компоненты материала используют рябь, указывающую на то, что с ними взаимодействуют. Если вы используете MaterialTheme в своей иерархии, Ripple будет использоваться в качестве Indication по умолчанию внутри модификаторов, таких как clickable и indication .

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

Вы можете расширить RippleTheme и использовать служебные функции defaultRippleColor и defaultRippleAlpha . Затем вы можете добавить свою собственную тему Ripple в свою иерархию, используя LocalRippleTheme :

@Composable
fun MyApp() {
    MaterialTheme {
        CompositionLocalProvider(
            LocalRippleTheme provides SecondaryRippleTheme
        ) {
            // App content
        }
    }
}

@Immutable
private object SecondaryRippleTheme : RippleTheme {
    @Composable
    override fun defaultColor() = RippleTheme.defaultRippleColor(
        contentColor = MaterialTheme.colors.secondary,
        lightTheme = MaterialTheme.colors.isLight
    )

    @Composable
    override fun rippleAlpha() = RippleTheme.defaultRippleAlpha(
        contentColor = MaterialTheme.colors.secondary,
        lightTheme = MaterialTheme.colors.isLight
    )
}

alt_text

Рисунок 14. Кнопки с разными значениями пульсации, предоставленные через RippleTheme .

Узнать больше

Чтобы узнать больше о темировании материалов в Compose, обратитесь к следующим дополнительным ресурсам.

Кодлабы

Видео

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