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 opiera się 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 domyślne w samym komponencie.
Dzięki interfejsowi Styles API masz nową warstwę abstrakcji, która jest pomostem 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 largeSizeAtomic = Style { size(100.dp, 40.dp) } |
| 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
Kiedy warto rozważyć: 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 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 style 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 funkcje pomocnicze 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 wywołań lub używać definicji statycznych, gdy pełne możliwości motywowania nie są potrzebne.
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.