Мы рекомендуем использовать систему проектирования Material, и в Jetpack Compose есть ее реализация, но вы не обязаны это делать. Material полностью основан на общедоступных API, поэтому вы можете создать собственную систему дизайна аналогичным образом.
Вот несколько вариантов:
- Расширьте
MaterialTheme, добавив дополнительные значения тем. - Заменить одну или несколько систем Material (
Colors,TypographyилиShapes) собственными реализациями, сохранив остальные. - Реализуйте полностью настраиваемую систему проектирования, чтобы заменить
MaterialTheme.
Вы также можете использовать компоненты Material с собственной системой дизайна. Это возможно, но нужно учитывать некоторые нюансы.
Чтобы узнать больше о конструкциях более низкого уровня и API, используемых MaterialTheme и пользовательскими системами дизайна, ознакомьтесь с руководством Анатомия темы в Compose.
Как расширить возможности тематического оформления Material Design
Compose Material тесно связан с тематическим оформлением Material Design, поэтому следовать рекомендациям Material Design легко и типобезопасно. Однако наборы цветов, шрифтов и форм можно расширить, добавив в них дополнительные значения. Проще всего добавить свойства расширения:
// 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
При расширении тематического оформления Material Design сохраняются существующие значения MaterialTheme, а у компонентов Material Design остаются разумные значения по умолчанию.
Если вы хотите использовать расширенные значения в компонентах, оберните их в собственные composable-функции, непосредственно задав значения, которые нужно изменить, и предоставив другие значения в качестве параметров для содержащей composable-функции:
@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 Design, вы можете заменить одну или несколько систем (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 }
Как использовать компоненты Material
Если один или несколько системных компонентов MaterialTheme были заменены, использование компонентов Material в том виде, в котором они есть, может привести к нежелательным значениям цвета, типа или формы Material.
Если вы хотите использовать в компонентах значения для замены, оберните их в собственные composable-функции, задав значения для нужной системы и предоставив другие значения в качестве параметров для содержащей composable-функции.
@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 Design полностью настраиваемой системой дизайна.
Компания MaterialTheme предоставляет следующие системы:
Colors,TypographyиShapes– системы тематического оформления Material Design.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 }
Как использовать компоненты Material
Если MaterialTheme отсутствует, использование компонентов Material в том виде, в котором они есть, приведет к нежелательным значениям цвета, типа и формы, а также к нежелательному поведению индикаторов.
Если вы хотите использовать в компонентах собственные значения, оберните их в свои composable-функции, напрямую задав значения для нужной системы и предоставив другие значения в качестве параметров для содержащей composable-функции.
Мы рекомендуем получать доступ к заданным значениям из собственной темы.
Если в вашей теме нет 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
- Структура темы в режиме создания