Хотя 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
, желтый цвет, используемый для полуопасных действий, — сохраняя при этом существующие цвета материала:
@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 Theming существующие значения 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 как есть может привести к нежелательным значениям цвета, типа или формы материала.
Если вы хотите использовать значения замены в компонентах, оберните их в свои собственные составные функции, напрямую устанавливая значения для соответствующей системы и предоставляя другие значения в качестве параметров для содержащего составного объекта.
@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 как есть приведет к нежелательным значениям цвета, типа и формы материала, а также к нежелательному поведению индикации.
Если вы хотите использовать пользовательские значения в компонентах, оберните их в свои собственные составные функции, напрямую устанавливая значения для соответствующей системы и предоставляя другие значения в качестве параметров для содержащего составного объекта.
Мы рекомендуем вам получить доступ к значениям, которые вы установили из своей пользовательской темы. В качестве альтернативы, если ваша тема не предоставляет 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
- Миграция с Материала 2 на Материал 3 в Compose
- Анатомия темы в Compose