Что можно и чего нельзя делать со стилями

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

Что нужно делать

Следуйте этим рекомендациям:

Рекомендация: Используйте стили для визуального оформления и модификаторы для поведения.

Используйте API стилей для визуальной настройки (фон, отступы, границы), а модификаторы оставьте для таких функций, как логика кликов, распознавание жестов или доступность.

Что нужно сделать: Предоставить доступ к параметрам стиля в системах проектирования.

Для компонентов вашей собственной системы проектирования следует добавить объект Style после параметра modifier.

@Composable
fun GradientButton(
    modifier: Modifier = Modifier,
    // ✅ DO: for design system components, expose a style modifier to consumers to be able to customize the components
    style: Style = Style
) {
    // Consume the style
}

Рекомендация: Замените параметры, основанные на визуальном представлении, на стиль.

Рассмотрите возможность замены параметров в ваших составных элементах одним параметром Style . Например:

// Before
@Composable
fun OldButton(background: Color, fontColor: Color) {
}

// After
// ✅ DO: Replace visual-based parameters with a style that includes same properties
@Composable
fun NewButton(style: Style = Style) {
}

Рекомендация: Расставьте приоритеты для стилей анимации.

Используйте встроенный блок animate для стилизации на основе состояния с помощью анимации, что обеспечивает повышение производительности по сравнению с модификаторами.

Рекомендация: Воспользуйтесь преимуществом правила «Последний написавший выигрывает».

Воспользуйтесь тем фактом, что свойства style перезаписывают, а не накладываются друг на друга. Это позволит переопределить границы или фон компонентов по умолчанию без необходимости использования нескольких параметров.

Не следует

Следующие варианты не рекомендуются:

Не следует: использовать стили для логики взаимодействия.

Не пытайтесь обрабатывать onClick или распознавание жестов внутри стиля. Стили ограничены визуальными настройками, основанными на состоянии, поэтому они не должны обрабатывать бизнес-логику; вместо этого они должны иметь разный визуальный вид в зависимости от состояния.

Не следует: указывать стиль по умолчанию в качестве параметра по умолчанию.

Параметры стиля всегда следует объявлять с помощью style: Style = Style :`

@Composable
fun BadButton(
    modifier: Modifier = Modifier,
    // ❌ DON'T set a default style here as a parameter
    style: Style = Style { background(Color.Red) }
) {
}

Чтобы включить параметр по умолчанию, объедините стиль входящего параметра с определенным по умолчанию:

@Composable
fun GoodButton(
    modifier: Modifier = Modifier,
    // ✅ Do: always pass it as a Style, do not pass other defaults
    style: Style = Style
) {
    // ...
    val defaultStyle = Style { background(Color.Red) }
    // ✅ Do Combine defaults inside with incoming parameter
    Box(modifier = modifier.styleable(styleState, defaultStyle, style)) {
      // your logic
    }
}

Не следует: Задавать параметры стиля для элементов, создаваемых на основе макета.

Хотя стиль можно задать для любого компонента, не ожидается, что компоненты, созданные на основе макета или на уровне экрана, будут принимать стили — с точки зрения пользователя неясно, что стиль будет делать на этом уровне. Стили предназначены для компонентов, а не обязательно для макетов.

Не следует: создавать стили в разделе «Композиция».

CompositionLocals считываются в момент определения стиля, а не в момент его использования. Когда стиль фактически используется, состояние CompositionLocal может измениться, что приведет к неточному отображению стиля.

// DON'T - Create styles in Composition that access composition locals in this way - this will likely lead to issues when style is used / accessed, as it would not get updated when the value changes.
@Composable
fun containerStyle(): Style {
    val background = MaterialTheme.colorScheme.background
    val onBackground = MaterialTheme.colorScheme.onBackground
    return Style {
        background(background)
        contentColor(onBackground)
    }
}

// Do: Instead, Create StyleScope extension functions for your subsystems to access themed composition Locals
val StyleScope.colors: JetsnackColors
    get() = JetsnackTheme.LocalJetsnackTheme.currentValue.colors

val StyleScope.typography: androidx.compose.material3.Typography
    get() = JetsnackTheme.LocalJetsnackTheme.currentValue.typography
val StyleScope.shapes: Shapes
    get() = JetsnackTheme.LocalJetsnackTheme.currentValue.shapes
// Access CompositionLocals
val button = Style {
    background(colors.brandSecondary)
    shape(shapes.small)
}

Рекомендация: Создайте единый стиль для изменений значений подсистем.

Например, при переключении между темным и светлым режимами запросите существующие значения темы (через CompositionLocal ), чтобы динамически изменить Style :

// Do: Use CompositionLocals or themed values to create a single style
val buttonStyle = Style {
    background(colors.brandSecondary)
    shape(shapes.small)
}

Рекомендация: Заменяйте стили целиком, если компонент принципиально отличается в разных определениях темы.

Вы можете заменить целые объекты стиля на уровне темы, если это принципиально разные темы.

Например, если вы создаете приложение, в котором для каждого продукта/страницы или предложения используются разные темы оформления, и многие свойства стиля различаются, то допустимо менять целые наборы стилей на уровне темы.

// DO Switch out whole styles when many properties differ - if Product A and Product B are two white labelled apps that provide different Themes.
val productBThemedButton = Style {
    shape(shapes.small)
    background(colors.brandSecondary)
    // other properties are fundamentally different
}

val productAThemedButton = Style {
    shape(shapes.large)
    background(colors.brand)
    // other properties are fundamentally different
}