Оформление интерьера в соответствии со стилями

There are several ways you can build out your apps using Styles. What you choose depends on where your app sits in relation to its adoption of Material Design:

  1. Полностью индивидуальная система дизайна, не использующая Material Design.
    • Рекомендация : Определите стили компонентов, которые используют значения из темы оформления, и предоставьте доступ к параметрам стиля для компонентов системы дизайна.
  2. Использование Material Design
    • Рекомендация : Дождитесь внедрения Material Design для интеграции со стилями. По возможности используйте стили в собственных компонентах.

Слой стиля

В традиционной модели Compose настройка часто в значительной степени опирается на переопределение глобальных токенов (цветов и типографики), предоставляемых MaterialTheme , или на обертывание и переопределение свойств системы дизайна, которую можно комбинировать, где это возможно. Иногда в слое Material есть свойства, которые не отображаются через подсистемы или параметры, а являются жестко заданными значениями по умолчанию для самого компонента.

API стилей (Styles API) представляет собой новый уровень абстракции, служащий мостом между подсистемами и компонентами: стили .

Слой Ответственность Пример
Значения подсистем Именованные значения val Primary = Color(0xFF34A85E)
Атомные стили Стиль, который вносит ровно одно изменение в свойство. val buttonStyle = paddingAtomic then roundedCornerShapeAtomic then primaryBackgroundAtomic then largeSize then interactiveShadowAtomic
Стили компонентов Конфигурации, специфичные для компонентов Кнопка с основным фоном и отступом 16dp. val buttonStyle = Style { contentPadding(16.dp) shape(RoundedCornerShape(8.dp)) background(Color.Blue) }
Компоненты Функциональный элемент пользовательского интерфейса, который использует стиль. Button(style = buttonStyle) { ... }
Диаграмма, демонстрирующая оформление с помощью стилей с использованием нового слоя.
Рисунок 1. Пример компонента и того, как он получает доступ к стилям из темы.

Атомарный против монолитного стиля

С помощью Styles API вы можете разбить стиль на отдельные атомарные стили. Вместо определения сложных, специфичных для компонентов стилей, таких как baseButtonStyle , вы также можете создавать небольшие, узкоспециализированные вспомогательные стили. Они выступают в роли ваших «атомов».

// Define single-purpose "atomic" styles
val paddingAtomic = Style {
    contentPadding(16.dp)
}
val roundedCornerShapeAtomic = Style {
    shape(RoundedCornerShape(8.dp))
}
val primaryBackgroundAtomic = Style {
    background(Color.Blue)
}
val largeSizeAtomic = Style {
    size(100.dp, 40.dp)
}
val interactiveShadowAtomic = Style {
    hovered {
        animate {
            dropShadow(
                Shadow(
                    offset = DpOffset(
                        0.dp,
                        0.dp
                    ),
                    radius = 2.dp,
                    spread = 0.dp,
                    color = Color.Blue,
                )
            )
        }
    }
}

Композиция с использованием слова «затем»

Одна из мощных функций нового API стилей — это оператор then , который позволяет объединять несколько объектов Style . Это позволяет создавать компоненты, используя атомарные вспомогательные классы.

Традиционный (неатомный) :

// One large monolithic style
val buttonStyle = Style {
    contentPadding(16.dp)
    shape(RoundedCornerShape(8.dp))
    background(Color.Blue)
}

Атомарный рефакторинг :

// Combine atoms to create the final appearance
val buttonStyle = paddingAtomic then roundedCornerShapeAtomic then primaryBackgroundAtomic then interactiveShadowAtomic

Внедрите стили в свою систему дизайна.

При внедрении стилей в вашу дизайн-систему следует учитывать следующие варианты, в зависимости от того, на каком этапе развития находится ваша дизайн-система.

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

Подумайте, когда : вам передали обширное руководство по фирменному стилю, которое не основано на Material Design, и вы не планируете использовать Material Design .

Стратегия : Внедрить полностью настраиваемую систему дизайна и сделать стили доступными в рамках темы оформления .

Этот вариант является пользовательским, если вы не используете Material в качестве основного языка дизайна. Вы полностью обходите MaterialTheme для определения визуальных параметров и уже создали свою собственную пользовательскую тему . Вы создаете CompanyTheme , которая выступает в качестве контейнера для ваших стилей.

  • Как это работает : Создайте объект CompanyTheme , который содержит объекты Style для каждого компонента в вашей системе. Ваши компоненты (будь то обертки над логикой Material или пользовательские реализации Box или Layout ) напрямую используют эти стили и предоставляют параметр Style для пользователей вашей системы дизайна.
  • Слой стилей : Стили — это основное определение вашей системы дизайна. Токены — это именованные переменные, передаваемые в эти стили. Это позволяет осуществлять глубокую настройку, например, определять уникальные анимации для изменений состояния (например, анимация масштабирования и цвета при нажатии).

Если вы создаёте собственную пользовательскую тему без использования Material Design и хотите применить стили, добавьте список стилей в свою тему. Это позволит вам получить доступ к базовым стилям из любой точки вашего проекта.

  1. Создайте класс Styles , который будет хранить различные стили в вашем приложении, и задайте для них значения по умолчанию. Например, в приложении Jetsnack этот класс называется JetsnackStyles :

    object JetsnackStyles{
        val buttonStyle: Style = Style {
            shape(shapes.medium)
            background(colors.brand)
            contentColor(colors.textPrimary)
            contentPaddingVertical(8.dp)
            contentPaddingHorizontal(24.dp)
            textStyle(typography.labelLarge)
            disabled {
                animate {
                    background(colors.brandSecondary)
                }
            }
        }
        val cardStyle: Style = Style {
            shape(shapes.medium)
            background(colors.uiBackground)
            contentColor(colors.textPrimary)
        }
    }

  2. Включите Styles в общую тему оформления и предоставьте вспомогательные функции расширения в StyleScope для доступа к подсистемам:

    @Immutable
    class JetsnackTheme(
        val colors: JetsnackColors = LightJetsnackColors,
        val typography: androidx.compose.material3.Typography = androidx.compose.material3.Typography(),
        val shapes: Shapes = Shapes()
    ) {
        companion object {
            val colors: JetsnackColors
                @Composable @ReadOnlyComposable
                get() = LocalJetsnackTheme.current.colors
    
            val typography: androidx.compose.material3.Typography
                @Composable @ReadOnlyComposable
                get() = LocalJetsnackTheme.current.typography
    
            val shapes: Shapes
                @Composable @ReadOnlyComposable
                get() = LocalJetsnackTheme.current.shapes
    
            val styles: JetsnackStyles = JetsnackStyles
    
            val LocalJetsnackTheme: ProvidableCompositionLocal<JetsnackTheme>
                get() = LocalJetsnackThemeInstance
        }
    }
    
    val StyleScope.colors: JetsnackColors
        get() = LocalJetsnackTheme.currentValue.colors
    
    val StyleScope.typography: androidx.compose.material3.Typography
        get() = LocalJetsnackTheme.currentValue.typography
    
    val StyleScope.shapes: Shapes
        get() = LocalJetsnackTheme.currentValue.shapes
    
    internal val LocalJetsnackThemeInstance = staticCompositionLocalOf { JetsnackTheme() }
    
    @Composable
    fun JetsnackTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) {
        val colors = if (darkTheme) DarkJetsnackColors else LightJetsnackColors
        val theme = JetsnackTheme(colors = colors)
    
        CompositionLocalProvider(
            LocalJetsnackTheme provides theme,
        ) {
            MaterialTheme(
                typography = LocalJetsnackTheme.current.typography,
                shapes = LocalJetsnackTheme.current.shapes,
                content = content,
            )
        }
    }

  3. Получите доступ к JetsnackStyles внутри вашего составного приложения:

    @Composable
    fun CustomButton(modifier: Modifier,
                     style: Style = Style,
                     text: String) {
        val interactionSource = remember { MutableInteractionSource() }
        val styleState = remember(interactionSource) { MutableStyleState(interactionSource) }
    
        // Apply style to top level container in combination with incoming style from parameter.
        Box(modifier = modifier
            .clickable(
                interactionSource = interactionSource,
                indication = null,
                enabled = true,
                role = Role.Button,
                onClick = {
    
                },
            )
            .styleable(styleState, JetsnackTheme.styles.buttonStyle, style)) {
            Text(text)
        }
    }

Помимо глобального внедрения тем оформления, существуют альтернативные стратегии интеграции Styles в ваши приложения. Вы можете использовать Styles непосредственно в коде для конкретных мест вызовов или использовать статические определения, когда полные возможности оформления не требуются. Styles не следует менять условно, если только весь стиль принципиально не отличается. Предпочтительнее использовать динамические токены внутри определения визуального элемента, а не переключаться между различными объектами стиля.