Zalecamy Material Design, a Jetpack Compose zawiera wdrożenie Material, ale nie musisz z niego korzystać. Materiał jest w całości oparty na publicznych interfejsach API, więc można w ten sam sposób stworzyć własny system do projektowania.
Oto kilka rzeczy, które możesz zastosować:
- Rozszerzamy element
MaterialTheme
o dodatkowe wartości tematyczne - Zastępowanie co najmniej jednego systemu Material Design –
Colors
,Typography
lubShapes
– własnymi implementacjami przy zachowaniu pozostałych - Wdrożenie w pełni niestandardowego systemu projektowania zastępującego
MaterialTheme
Możesz też nadal używać komponentów Material Design z niestandardowym systemem projektowania. Jest to możliwe, ale trzeba pamiętać o kilku kwestiach.
Więcej informacji o konstrukcjach niższego poziomu i interfejsach API używanych przez MaterialTheme
oraz o niestandardowych systemach projektowania znajdziesz w przewodniku Anatomia motywu w tworzeniu.
Rozszerzanie tematyki materiałów
Interfejs Compose Material ściśle modeluje motywowanie Material Design, aby ułatwić i umożliwić przestrzeganie wytycznych dotyczących materiałów. Możesz jednak rozszerzyć zestawy kolorów, typografii i kształtów o dodatkowe wartości.
Najprostszym sposobem jest dodanie właściwości rozszerzenia:
// Use with MaterialTheme.colors.snackbarAction val Colors.snackbarAction: Color get() = if (isLight) 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)
Zapewnia to spójność z interfejsami API MaterialTheme
. Przykładem zdefiniowanego przez samą funkcję tworzenia wiadomości jest primarySurface
, która w zależności od Colors.isLight
działa jako serwer proxy między primary
a surface
.
Innym sposobem jest zdefiniowanie rozszerzonego motywu, który „zawija” element MaterialTheme
i jego wartości.
Załóżmy, że chcesz dodać dwa dodatkowe kolory – tertiary
i onTertiary
– zachowując przy tym dotychczasowe kolory materiału:
@Immutable data class ExtendedColors( val tertiary: Color, val onTertiary: Color ) val LocalExtendedColors = staticCompositionLocalOf { ExtendedColors( tertiary = Color.Unspecified, onTertiary = Color.Unspecified ) } @Composable fun ExtendedTheme( /* ... */ content: @Composable () -> Unit ) { val extendedColors = ExtendedColors( tertiary = Color(0xFFA8EFF0), onTertiary = Color(0xFF002021) ) CompositionLocalProvider(LocalExtendedColors provides extendedColors) { MaterialTheme( /* colors = ..., typography = ..., shapes = ... */ content = content ) } } // Use with eg. ExtendedTheme.colors.tertiary object ExtendedTheme { val colors: ExtendedColors @Composable get() = LocalExtendedColors.current }
Przypomina to interfejs API MaterialTheme
dotyczący wykorzystania. Obsługuje też wiele motywów, ponieważ obiekty ExtendedTheme
możesz zagnieżdżać w taki sam sposób jak obiekt MaterialTheme
.
Korzystanie z komponentów Material
W przypadku rozszerzania zakresu tematycznego materiału istniejące wartości MaterialTheme
są zachowywane, a komponenty Material Design mają nadal uzasadnione wartości domyślne.
Jeśli chcesz używać rozszerzonych wartości w komponentach, umieść je we własnych funkcjach kompozycyjnych, bezpośrednio ustawiając wartości, które chcesz zmienić, i ujawnij innym jako parametry funkcji kompozycyjnej:
@Composable fun ExtendedButton( onClick: () -> Unit, modifier: Modifier = Modifier, content: @Composable RowScope.() -> Unit ) { Button( colors = ButtonDefaults.buttonColors( containerColor = ExtendedTheme.colors.tertiary, contentColor = ExtendedTheme.colors.onTertiary /* Other colors use values from MaterialTheme */ ), onClick = onClick, modifier = modifier, content = content ) }
W razie potrzeby zastąpisz użycie słowa Button
wartością ExtendedButton
.
@Composable fun ExtendedApp() { ExtendedTheme { /*...*/ ExtendedButton(onClick = { /* ... */ }) { /* ... */ } } }
Zastępowanie systemów Material Design
Zamiast rozszerzać uporządkowanie tematyczne, możesz zastąpić jeden lub kilka systemów – Colors
, Typography
lub Shapes
– wdrożeniem niestandardowej implementacji, zachowując pozostałe.
Załóżmy, że chcesz zmienić układ typu i kształtów przy zachowaniu systemu kolorów:
@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 }
Korzystanie z komponentów Material
W przypadku zastąpienia co najmniej 1 systemu elementu MaterialTheme
użycie komponentów Materiał w takiej postaci może spowodować przypisanie do niego niepożądanych wartości koloru, typu lub kształtu.
Jeśli chcesz użyć wartości zastępczej w komponentach, umieść je we własnych funkcjach kompozycyjnych, bezpośrednio ustawiając wartości dla odpowiedniego systemu, a innym ujawniając je jako parametry funkcji kompozycyjnej.
@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() } } ) }
W razie potrzeby zastąpisz użycie słowa Button
wartością ReplacementButton
.
@Composable fun ReplacementApp() { ReplacementTheme { /*...*/ ReplacementButton(onClick = { /* ... */ }) { /* ... */ } } }
Wdrożenie systemu projektowania w pełni niestandardowego
Możesz zastąpić motyw Material Design systemem całkowicie niestandardowym.
Pamiętaj, że MaterialTheme
udostępnia te systemy:
Colors
,Typography
iShapes
: systemy doboru tematycznego materiałówContentAlpha
: poziomy przezroczystości, które pozwalają podkreślić wyróżnienie w językachText
iIcon
TextSelectionColors
: kolory używane do zaznaczania tekstu według właściwościText
iTextField
Ripple
iRippleTheme
: materialna implementacjaIndication
Jeśli nadal chcesz korzystać z komponentów Material Design, musisz zastąpić niektóre z nich w swoich motywach niestandardowych lub motywach niestandardowych albo obsługiwać systemy w komponentach, aby uniknąć niepożądanych zachowań.
Systemy projektowania nie są jednak ograniczone do koncepcji, na których opiera się aplikacja Material. Możesz modyfikować istniejące systemy i wprowadzać zupełnie nowe (z nowymi klasami i typami), aby inne koncepcje były zgodne z motywami.
W poniższym kodzie modelujemy niestandardowy system kolorów, który zawiera gradienty (List<Color>
), dodamy system typów, wprowadzimy nowy system wysokości i wykluczymy inne systemy dostarczane przez 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 }
Korzystanie z komponentów Material
Jeśli nie ma elementu MaterialTheme
, użycie komponentów Materiał w takiej postaci spowoduje niepożądane ustawienia koloru, typu i kształtu materiału oraz zachowania innych wskaźników.
Jeśli chcesz używać w komponentach wartości niestandardowych, umieść je we własnych funkcjach kompozycyjnych, ustaw bezpośrednio wartości dla odpowiedniego systemu, a inne (udostępnij) jako parametry funkcji kompozycyjnej.
Zalecamy dostęp do wartości ustawionych z poziomu motywu niestandardowego. Jeśli Twój motyw nie obsługuje Color
, TextStyle
, Shape
ani innych systemów, możesz je zakodować na stałe.
@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 = ContentAlpha.disabled) ), 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)
Po wprowadzeniu nowych typów klas, np. List<Color>
do reprezentowania gradientów, lepiej zaimplementować komponenty od zera zamiast je pakować. Spójrzmy na przykład na wartość JetsnackButton
z próbki Jetsnack.
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy JavaScript jest wyłączony
- Material Design 2 w obszarze Utwórz
- Migracja z Material 2 do Material 3 w sekcji Utwórz
- Anatomia motywu w sekcji Utwórz