Хотя Material — наша рекомендуемая дизайн-система, а Jetpack Compose включает в себя её реализацию, вы не обязаны её использовать. Material полностью построен на общедоступных API, поэтому вы можете создать свою собственную дизайн-систему таким же образом.
Есть несколько подходов, которые вы можете использовать:
- Расширение
MaterialTheme
дополнительными значениями тем - Замена одной или нескольких систем материалов —
Colors
,Typography
илиShapes
— пользовательскими реализациями, сохраняя при этом остальные - Реализация полностью настраиваемой системы дизайна для замены
MaterialTheme
Вы также можете продолжить использовать компоненты Material в системе дизайна, разработанной специально для вас. Это возможно, но следует учитывать некоторые моменты, соответствующие выбранному вами подходу.
Чтобы узнать больше о низкоуровневых конструкциях и API, используемых MaterialTheme
и системами пользовательского дизайна, ознакомьтесь с руководством «Анатомия темы в Compose» .
Расширение материальной темы
Compose Material во многом соответствует принципам Material Theming , что упрощает и типобезопасно позволяет следовать рекомендациям Material. Однако можно расширить наборы цветов, типографики и форм, добавив дополнительные значения.
Самый простой подход — добавить свойства расширения:
// Use with MaterialTheme.colorScheme.snackbarAction val ColorScheme.snackbarAction: Color @Composable get() = if (isSystemInDarkTheme()) Red300 else Red700 // Use with MaterialTheme.typography.textFieldInput val Typography.textFieldInput: TextStyle get() = TextStyle(/* ... */) // Use with MaterialTheme.shapes.card val Shapes.card: Shape get() = RoundedCornerShape(size = 20.dp)
Это обеспечивает согласованность с API использования MaterialTheme
. Примером такого подхода, определяемым самим Compose, является surfaceColorAtElevation
, который определяет цвет поверхности, используемый в зависимости от высоты.
Другой подход заключается в определении расширенной темы, которая «обертывает» MaterialTheme
и ее значения.
Предположим, вы хотите добавить два дополнительных цвета — caution
и onCaution
(желтый цвет, используемый для действий, которые являются полуопасными), — сохранив при этом существующие цвета Material:
@Immutable data class ExtendedColors( val caution: Color, val onCaution: Color ) val LocalExtendedColors = staticCompositionLocalOf { ExtendedColors( caution = Color.Unspecified, onCaution = Color.Unspecified ) } @Composable fun ExtendedTheme( /* ... */ content: @Composable () -> Unit ) { val extendedColors = ExtendedColors( caution = Color(0xFFFFCC02), onCaution = Color(0xFF2C2D30) ) CompositionLocalProvider(LocalExtendedColors provides extendedColors) { MaterialTheme( /* colors = ..., typography = ..., shapes = ... */ content = content ) } } // Use with eg. ExtendedTheme.colors.caution object ExtendedTheme { val colors: ExtendedColors @Composable get() = LocalExtendedColors.current }
Это похоже на API использования MaterialTheme
. Также поддерживается несколько тем, поскольку ExtendedTheme
можно вкладывать друг в друга, как и MaterialTheme
.
Использовать компоненты материала
При расширении Material Theme существующие значения MaterialTheme
сохраняются, а компоненты Material по-прежнему имеют разумные значения по умолчанию.
Если вы хотите использовать расширенные значения в компонентах, оберните их в собственные компонуемые функции, напрямую задавая значения, которые вы хотите изменить, и предоставляя другие в качестве параметров содержащему компонуемому компоненту:
@Composable fun ExtendedButton( onClick: () -> Unit, modifier: Modifier = Modifier, content: @Composable RowScope.() -> Unit ) { Button( colors = ButtonDefaults.buttonColors( containerColor = ExtendedTheme.colors.caution, contentColor = ExtendedTheme.colors.onCaution /* Other colors use values from MaterialTheme */ ), onClick = onClick, modifier = modifier, content = content ) }
Затем, где это уместно, замените Button
на ExtendedButton
.
@Composable fun ExtendedApp() { ExtendedTheme { /*...*/ ExtendedButton(onClick = { /* ... */ }) { /* ... */ } } }
Заменить подсистемы материалов
Вместо расширения Material Theming вы можете захотеть заменить одну или несколько систем — Colors
, Typography
или Shapes
— собственной реализацией, сохранив при этом остальные.
Предположим, вы хотите заменить систему шрифтов и форм, сохранив при этом цветовую систему:
@Immutable data class ReplacementTypography( val body: TextStyle, val title: TextStyle ) @Immutable data class ReplacementShapes( val component: Shape, val surface: Shape ) val LocalReplacementTypography = staticCompositionLocalOf { ReplacementTypography( body = TextStyle.Default, title = TextStyle.Default ) } val LocalReplacementShapes = staticCompositionLocalOf { ReplacementShapes( component = RoundedCornerShape(ZeroCornerSize), surface = RoundedCornerShape(ZeroCornerSize) ) } @Composable fun ReplacementTheme( /* ... */ content: @Composable () -> Unit ) { val replacementTypography = ReplacementTypography( body = TextStyle(fontSize = 16.sp), title = TextStyle(fontSize = 32.sp) ) val replacementShapes = ReplacementShapes( component = RoundedCornerShape(percent = 50), surface = RoundedCornerShape(size = 40.dp) ) CompositionLocalProvider( LocalReplacementTypography provides replacementTypography, LocalReplacementShapes provides replacementShapes ) { MaterialTheme( /* colors = ... */ content = content ) } } // Use with eg. ReplacementTheme.typography.body object ReplacementTheme { val typography: ReplacementTypography @Composable get() = LocalReplacementTypography.current val shapes: ReplacementShapes @Composable get() = LocalReplacementShapes.current }
Использовать компоненты материала
Если была заменена одна или несколько систем MaterialTheme
, использование компонентов Material «как есть» может привести к нежелательным значениям цвета, типа или формы Material.
Если вы хотите использовать заменяющие значения в компонентах, оберните их в собственные компонуемые функции, напрямую задавая значения для соответствующей системы и предоставляя другие в качестве параметров содержащему компонуемому элементу.
@Composable fun ReplacementButton( onClick: () -> Unit, modifier: Modifier = Modifier, content: @Composable RowScope.() -> Unit ) { Button( shape = ReplacementTheme.shapes.component, onClick = onClick, modifier = modifier, content = { ProvideTextStyle( value = ReplacementTheme.typography.body ) { content() } } ) }
Затем, где это уместно, замените Button
на ReplacementButton
.
@Composable fun ReplacementApp() { ReplacementTheme { /*...*/ ReplacementButton(onClick = { /* ... */ }) { /* ... */ } } }
Внедрить полностью индивидуальную систему проектирования
Возможно, вы захотите заменить Material Theming на полностью настраиваемую систему дизайна. Обратите внимание, что MaterialTheme
предоставляет следующие возможности:
-
Colors
,Typography
иShapes
: системы тематизации материалов -
TextSelectionColors
: Цвета, используемые для выделения текста с помощьюText
иTextField
-
Ripple
иRippleTheme
: Материальная реализацияIndication
Если вы хотите продолжить использование компонентов Material, вам придется заменить некоторые из этих систем в вашей пользовательской теме или темах или обрабатывать системы в ваших компонентах, чтобы избежать нежелательного поведения.
Однако дизайн-системы не ограничиваются концепциями, на которых основан Material. Вы можете модифицировать существующие системы и создавать совершенно новые — с новыми классами и типами — чтобы сделать другие концепции совместимыми с темами.
В следующем коде мы моделируем пользовательскую цветовую систему, которая включает градиенты ( List<Color>
), включаем систему типов, вводим новую систему высот и исключаем другие системы, предоставляемые MaterialTheme
:
@Immutable data class CustomColors( val content: Color, val component: Color, val background: List<Color> ) @Immutable data class CustomTypography( val body: TextStyle, val title: TextStyle ) @Immutable data class CustomElevation( val default: Dp, val pressed: Dp ) val LocalCustomColors = staticCompositionLocalOf { CustomColors( content = Color.Unspecified, component = Color.Unspecified, background = emptyList() ) } val LocalCustomTypography = staticCompositionLocalOf { CustomTypography( body = TextStyle.Default, title = TextStyle.Default ) } val LocalCustomElevation = staticCompositionLocalOf { CustomElevation( default = Dp.Unspecified, pressed = Dp.Unspecified ) } @Composable fun CustomTheme( /* ... */ content: @Composable () -> Unit ) { val customColors = CustomColors( content = Color(0xFFDD0D3C), component = Color(0xFFC20029), background = listOf(Color.White, Color(0xFFF8BBD0)) ) val customTypography = CustomTypography( body = TextStyle(fontSize = 16.sp), title = TextStyle(fontSize = 32.sp) ) val customElevation = CustomElevation( default = 4.dp, pressed = 8.dp ) CompositionLocalProvider( LocalCustomColors provides customColors, LocalCustomTypography provides customTypography, LocalCustomElevation provides customElevation, content = content ) } // Use with eg. CustomTheme.elevation.small object CustomTheme { val colors: CustomColors @Composable get() = LocalCustomColors.current val typography: CustomTypography @Composable get() = LocalCustomTypography.current val elevation: CustomElevation @Composable get() = LocalCustomElevation.current }
Использовать компоненты материала
Если MaterialTheme
отсутствует, использование компонентов Material «как есть» приведет к нежелательным значениям цвета, типа и формы Material, а также поведению индикации.
Если вы хотите использовать пользовательские значения в компонентах, оберните их в собственные компонуемые функции, напрямую задавая значения для соответствующей системы и предоставляя другие в качестве параметров содержащему компонуемому компоненту.
Мы рекомендуем вам использовать значения, заданные в вашей пользовательской теме. Если же ваша тема не поддерживает Color
, TextStyle
, Shape
или другие системы, вы можете задать их жёстко.
@Composable fun CustomButton( onClick: () -> Unit, modifier: Modifier = Modifier, content: @Composable RowScope.() -> Unit ) { Button( colors = ButtonDefaults.buttonColors( containerColor = CustomTheme.colors.component, contentColor = CustomTheme.colors.content, disabledContainerColor = CustomTheme.colors.content .copy(alpha = 0.12f) .compositeOver(CustomTheme.colors.component), disabledContentColor = CustomTheme.colors.content .copy(alpha = 0.38f) ), shape = ButtonShape, elevation = ButtonDefaults.elevatedButtonElevation( defaultElevation = CustomTheme.elevation.default, pressedElevation = CustomTheme.elevation.pressed /* disabledElevation = 0.dp */ ), onClick = onClick, modifier = modifier, content = { ProvideTextStyle( value = CustomTheme.typography.body ) { content() } } ) } val ButtonShape = RoundedCornerShape(percent = 50)
Если вы добавили новые типы классов, например, List<Color>
для представления градиентов, то, возможно, будет лучше реализовать компоненты с нуля, а не создавать для них обёртки. Например, взгляните на JetsnackButton
из примера Jetsnack.
Рекомендовано для вас
- Примечание: текст ссылки отображается, когда JavaScript отключен.
- Material Design 3 в Compose
- Переход из Material 2 в Material 3 в Compose
- Анатомия темы в Compose