Хотя Material — это рекомендуемая нами система дизайна, и Jetpack Compose включает в себя реализацию Material, вы не обязаны её использовать. Material полностью построена на основе общедоступных API, поэтому вы можете создать свою собственную систему дизайна аналогичным образом.
Вы можете использовать несколько подходов:
- Расширьте
MaterialTheme, добавив дополнительные параметры темы. - Замените одну или несколько систем Material Design —
Colors,TypographyилиShapes— на собственные реализации, сохранив при этом остальные. - Внедрить полностью настраиваемую систему дизайна взамен
MaterialTheme.
Вы также можете продолжить использовать компоненты Material Design с собственной системой дизайна. Это возможно, но следует учитывать некоторые моменты, соответствующие выбранному вами подходу.
Чтобы узнать больше о низкоуровневых конструкциях и 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 Design:
@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 в неизменном виде может привести к нежелательным значениям цвета, типа или формы, характерным для 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 Theme на полностью настраиваемую систему дизайна. Учтите, что MaterialTheme предоставляет следующие возможности:
-
Colors,TypographyиShapes: системы оформления Material Design -
TextSelectionColors: Цвета, используемые для выделения текста в поляхTextиTextField -
RippleиRippleTheme: материальная реализацияIndication
Если вы хотите продолжать использовать компоненты Material Design, вам необходимо заменить некоторые из этих систем в ваших пользовательских темах или управлять системами в ваших компонентах, чтобы избежать нежелательного поведения.
Однако системы проектирования не ограничиваются концепциями, на которых основан 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 отключен.
- Материальный дизайн 3 в Compose
- Перейдите с Материала 2 на Материал 3 в Compose.
- Анатомия темы в Compose