Системы индивидуального дизайна в Compose

Хотя Material является нашей рекомендуемой системой проектирования, а Jetpack Compose предоставляет реализацию Material, вы не обязаны ее использовать. Материал полностью построен на общедоступных API, поэтому таким же образом можно создать собственную систему дизайна.

Вы можете использовать несколько подходов:

Вы также можете продолжить использовать компоненты 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.

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

Пока рекомендаций нет.

Попытайтесь в свой аккаунт Google.

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