Aplikacje możesz tworzyć na kilka sposobów, korzystając ze stylów. Wybór zależy od tego, na jakim etapie wdrażania Material Design jest Twoja aplikacja:
- W pełni niestandardowy system projektowania, który nie korzysta z Material Design.
- Zalecenie: zdefiniuj style komponentów, które korzystają z wartości z motywu, i udostępnij parametry stylu w komponentach systemu projektowania.
- Korzystanie z Material Design.
- Zalecenie: poczekaj na wdrożenie Material, aby zintegrować go ze stylami. W miarę możliwości używaj stylów w swoich komponentach.
Warstwa stylu
W tradycyjnym modelu Compose dostosowywanie często polega na zastępowaniu tokenów globalnych (kolorów i typografii) udostępnianych przez MaterialTheme lub na opakowywaniu i zastępowaniu właściwości komponentu systemu projektowania, jeśli jest to możliwe.
Czasami w warstwie Material występują właściwości, które nie są udostępniane przez podsystemy ani parametry, ale są zakodowane na stałe jako wartości domyślne w samym komponencie.
W interfejsie Styles API dostępna jest nowa warstwa abstrakcji, która stanowi pomost między podsystemami a komponentami – style.
| Warstwa | Odpowiedzialność | Przykład |
|---|---|---|
| Wartości podsystemu | Nazwane wartości | val Primary = Color(0xFF34A85E) |
| Style atomowe | Styl, który zmienia dokładnie 1 właściwość | val buttonStyle = paddingAtomic then roundedCornerShapeAtomic then primaryBackgroundAtomic then largeSize then interactiveShadowAtomic |
| Style komponentów | Konfiguracje specyficzne dla komponentu | Przycisk z niebieskim tłem i dopełnieniem 16 dp. val buttonStyle = Style { contentPadding(16.dp) shape(RoundedCornerShape(8.dp)) background(Color.Blue) } |
| Komponenty | Funkcjonalny element interfejsu, który korzysta ze stylu. | Button(style = buttonStyle) { ... } |
Style atomowe a style monolityczne
Dzięki interfejsowi Styles API możesz podzielić styl na osobne style atomowe.
Zamiast definiować złożone style specyficzne dla komponentu, takie jak baseButtonStyle, możesz też tworzyć małe style narzędziowe o jednym przeznaczeniu. Działają one jak „atomy”.
// 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, ) ) } } }
Kompozycja z użyciem „then”
Jedną z zaawansowanych funkcji nowego interfejsu Styles API jest operator then, który umożliwia łączenie wielu obiektów Style. Dzięki temu możesz tworzyć komponenty za pomocą atomowych klas narzędziowych.
Tradycyjne (nieatomowe):
// One large monolithic style val buttonStyle = Style { contentPadding(16.dp) shape(RoundedCornerShape(8.dp)) background(Color.Blue) }
Refaktoryzacja atomowa:
// Combine atoms to create the final appearance val buttonStyle = paddingAtomic then roundedCornerShapeAtomic then primaryBackgroundAtomic then interactiveShadowAtomic
Wdrażanie stylów w systemie projektowania
Podczas wdrażania stylów w systemie projektowania rozważ te opcje, w zależności od tego, na jakim etapie wdrażania jest Twój system projektowania.
Niestandardowy system projektowania ze stylami
Rozważ, gdy: masz obszerny przewodnik po marce, który nie jest oparty na Material Design, i nie planujesz korzystać z Material Design.
Strategia: wdrożenie w pełni niestandardowego systemu projektowania i udostępnienie stylów jako części motywu.
Ta opcja jest ścieżką niestandardową, jeśli nie używasz Material jako głównego języka systemu projektowania. Całkowicie pomijasz MaterialTheme w przypadku definicji wizualnych i masz już utworzony własny motyw niestandardowy. Tworzysz CompanyTheme, który działa jako kontener dla Twoich stylów.
- Jak to działa: utwórz obiekt
CompanyTheme, który zawiera obiektyStyledla każdego komponentu w systemie. Twoje komponenty (opakowania logiki Material lub niestandardowe implementacjeBoxlubLayout) bezpośrednio korzystają z tych stylów i udostępniają parametrStyledla użytkowników Twojego systemu projektowania. - Warstwa stylu: style są podstawową definicją Twojego systemu projektowania. Tokeny to nazwane zmienne, które są przekazywane do tych stylów. Umożliwia to głębokie dostosowywanie, np. definiowanie unikalnych animacji zmian stanu (np. animowanie skali i koloru po naciśnięciu).
Jeśli tworzysz własny motyw niestandardowy bez użycia Material i chcesz wdrożyć style, dodaj listę stylów do motywu. Dzięki temu możesz uzyskać dostęp do stylów podstawowych z dowolnego miejsca w projekcie.
Utwórz klasę
Styles, która będzie przechowywać różne style w aplikacji, i utwórz wartości domyślne. Na przykład w aplikacji Jetsnack klasa ma nazwę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) } }
Udostępnij
Stylesjako część ogólnego motywu i udostępnij pomocnicze funkcje rozszerzające wStyleScope, aby uzyskać dostęp do podsystemów:@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, ) } }
Uzyskaj dostęp do
JetsnackStylesw komponencie:@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) } }
Oprócz wdrożenia motywu globalnego istnieją alternatywne strategie włączania Styles do aplikacji. Możesz używać Styles w wierszu w przypadku konkretnych miejsc wywołań lub używać definicji statycznych, gdy pełne możliwości motywu są zbędne.
Styles nie należy zamieniać warunkowo, chyba że cały styl jest zasadniczo inny. Zamiast przełączać się między różnymi obiektami stylu, lepiej jest uzyskiwać dostęp do tokenów dynamicznych w definicji wizualnej.