Material Design 2 w sekcji Utwórz

Jetpack Compose oferuje implementację Material Design, kompleksowego systemu projektowania do tworzenia interfejsów cyfrowych. Komponenty Material Design (przyciski, karty, przełączniki itp.) są oparte na motywach Material, czyli systematycznym sposobie dostosowywania Material Design do marki produktu. Motyw Material zawiera atrybuty kolor, typografia i kształt. Gdy dostosujesz te atrybuty, zmiany zostaną automatycznie odzwierciedlone w komponentach, których używasz do tworzenia aplikacji.

Jetpack Compose implementuje te koncepcje za pomocą funkcji kompozycyjnej MaterialTheme:

MaterialTheme(
    colors = // ...
    typography = // ...
    shapes = // ...
) {
    // app content
}

Skonfiguruj parametry przekazywane do MaterialTheme, aby dostosować wygląd aplikacji.

Dwa kontrastujące zrzuty ekranu. Pierwszy zrzut ekranu przedstawia domyślny styl MaterialTheme, a drugi – zmodyfikowany.
Rysunek 1. Pierwszy zrzut ekranu przedstawia aplikację, która nie konfiguruje `MaterialTheme`, więc używa domyślnego stylu. Drugi zrzut ekranu przedstawia aplikację, która przekazuje parametry do funkcji `MaterialTheme`, aby dostosować styl.

Kolor

Kolory są modelowane w Compose za pomocą klasy Color, która przechowuje dane.

val Red = Color(0xffff0000)
val Blue = Color(red = 0f, green = 0f, blue = 1f)

Możesz je uporządkować w dowolny sposób (jako stałe najwyższego poziomu, w ramach singletonu lub zdefiniowane w tekście), ale zdecydowanie zalecamy określanie kolorów w motywie i pobieranie ich stamtąd. Takie podejście umożliwia obsługę ciemnego motywu i zagnieżdżonych motywów.

Przykład palety kolorów motywu
Rysunek 2. System kolorów Material.

Biblioteka Compose udostępnia klasę Colors do modelowania systemu kolorów Material. Colors udostępnia funkcje do tworzenia zestawów jasnych lub ciemnych kolorów:

private val Yellow200 = Color(0xffffeb46)
private val Blue200 = Color(0xff91a4fc)
// ...

private val DarkColors = darkColors(
    primary = Yellow200,
    secondary = Blue200,
    // ...
)
private val LightColors = lightColors(
    primary = Yellow500,
    primaryVariant = Yellow400,
    secondary = Blue700,
    // ...
)

Po zdefiniowaniu Colors możesz przekazać je do MaterialTheme:

MaterialTheme(
    colors = if (darkTheme) DarkColors else LightColors
) {
    // app content
}

Używanie kolorów motywu

Wartość Colors przekazaną do elementu kompozycyjnego MaterialTheme możesz pobrać za pomocą funkcji MaterialTheme.colors.

Text(
    text = "Hello theming",
    color = MaterialTheme.colors.primary
)

Kolor powierzchni i treści

Wiele komponentów akceptuje parę kolorów i kolorów treści:

Surface(
    color = MaterialTheme.colors.surface,
    contentColor = contentColorFor(color),
    // ...
) { /* ... */ }

TopAppBar(
    backgroundColor = MaterialTheme.colors.primarySurface,
    contentColor = contentColorFor(backgroundColor),
    // ...
) { /* ... */ }

Dzięki temu możesz nie tylko ustawić kolor elementu kompozycyjnego, ale też podać domyślny kolor treści, czyli elementów kompozycyjnych, które zawiera. Wiele funkcji kompozycyjnych domyślnie używa tego koloru treści. Na przykład Text opiera swój kolor na kolorze treści elementu nadrzędnego, a Icon używa tego koloru do ustawienia odcienia.

Dwa przykłady tego samego banera w różnych kolorach
Rysunek 3. Ustawienie różnych kolorów tła powoduje zmianę kolorów tekstu i ikony.

Metoda contentColorFor() pobiera odpowiedni kolor „włączony” dla dowolnych kolorów motywu. Jeśli na przykład ustawisz kolor tła primary na Surface, ta funkcja ustawi kolor treści onPrimary. Jeśli ustawisz kolor tła inny niż kolor motywu, musisz też określić odpowiedni kolor treści. Użyj LocalContentColor, aby pobrać preferowany kolor treści dla bieżącego tła w danym miejscu w hierarchii.

Przezroczystość treści

Często chcesz zmieniać stopień podkreślenia treści, aby przekazać ich znaczenie i zapewnić hierarchię wizualną. Zalecenia dotyczące czytelności tekstu w Material Design sugerują stosowanie różnych poziomów krycia, aby przekazywać różne poziomy ważności.

Jetpack Compose implementuje to za pomocą LocalContentAlpha. Możesz określić wartość alfa treści dla hierarchii, podając wartość dla tego CompositionLocal. Zagnieżdżone funkcje kompozycyjne mogą używać tej wartości do zastosowania efektu alfa do swoich treści. Na przykład TextIcon domyślnie używają kombinacji LocalContentColor dostosowanej do używania LocalContentAlpha. Materiał określa niektóre standardowe wartości alfa (high, medium, disabled), które są modelowane przez obiekt ContentAlpha.

// By default, both Icon & Text use the combination of LocalContentColor &
// LocalContentAlpha. De-emphasize content by setting content alpha
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
    Text(
        // ...
    )
}
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.disabled) {
    Icon(
        // ...
    )
    Text(
        // ...
    )
}

Więcej informacji o CompositionLocal znajdziesz w artykule Dane o lokalnym zakresie z użyciem funkcji CompositionLocal.

Zrzut ekranu z tytułem artykułu przedstawiający różne poziomy wyróżnienia tekstu
Rysunek 4. Zastosuj różne poziomy wyróżnienia tekstu, aby wizualnie przekazać hierarchię informacji. Pierwsza linia tekstu to tytuł, który zawiera najważniejsze informacje, dlatego używa symbolu ContentAlpha.high. Drugi wiersz zawiera mniej istotne metadane, dlatego używa ContentAlpha.medium.

Ciemny motyw

W Compose motywy jasny i ciemny wdraża się, podając różne zestawy Colors do funkcji kompozycyjnej MaterialTheme:

@Composable
fun MyTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit
) {
    MaterialTheme(
        colors = if (darkTheme) DarkColors else LightColors,
        /*...*/
        content = content
    )
}

W tym przykładzie funkcja MaterialTheme jest opakowana w osobną funkcję kompozycyjną, która przyjmuje parametr określający, czy ma być używany ciemny motyw. W tym przypadku funkcja pobiera wartość domyślną dla darkTheme, wysyłając zapytanie do ustawienia motywu urządzenia.

Aby sprawdzić, czy bieżące Colors są jasne czy ciemne, możesz użyć takiego kodu:

val isLightTheme = MaterialTheme.colors.isLight
Icon(
    painterResource(
        id = if (isLightTheme) {
            R.drawable.ic_sun_24
        } else {
            R.drawable.ic_moon_24
        }
    ),
    contentDescription = "Theme"
)

Nakładki wysokości

W Materialu powierzchnie w ciemnych motywach o większej wysokości otrzymują nakładki wysokości, które rozjaśniają ich tło. Im wyższa jest powierzchnia (czyli im bliżej jest domniemanego źródła światła), tym jaśniejsza się staje.

Komponent Surface automatycznie stosuje te nakładki, gdy używasz ciemnych kolorów. Podobnie robi każdy inny komponent Material, który korzysta z powierzchni:

Surface(
    elevation = 2.dp,
    color = MaterialTheme.colors.surface, // color will be adjusted for elevation
    /*...*/
) { /*...*/ }

Zrzut ekranu aplikacji pokazujący subtelnie różne kolory użyte w przypadku elementów na różnych poziomach wysokości
Rysunek 5. Karty i dolny pasek nawigacyjny mają kolor surface jako tło. Karty i nawigacja u dołu znajdują się na różnych poziomach nad tłem, dlatego mają nieco inne kolory – karty są jaśniejsze niż tło, a nawigacja u dołu jest jaśniejsza niż karty.

W przypadku scenariuszy niestandardowych, które nie obejmują Surface, użyj LocalElevationOverlay, czyli CompositionLocal zawierającego ElevationOverlay używany przez komponenty Surface:

// Elevation overlays
// Implemented in Surface (and any components that use it)
val color = MaterialTheme.colors.surface
val elevation = 4.dp
val overlaidColor = LocalElevationOverlay.current?.apply(
    color, elevation
)

Aby wyłączyć nakładki z wysokością, podaj wartość null w wybranym punkcie hierarchii kompozycyjnej:

MyTheme {
    CompositionLocalProvider(LocalElevationOverlay provides null) {
        // Content without elevation overlays
    }
}

Ograniczone akcenty kolorystyczne

W przypadku ciemnych motywów Material zaleca stosowanie ograniczonych akcentów kolorystycznych, preferując w większości przypadków kolor surface zamiast koloru primary. Komponenty Material, takie jak TopAppBarBottomNavigation, domyślnie implementują to zachowanie.

Zrzut ekranu z ciemnym motywem Material, na którym widać górny pasek aplikacji z kolorem powierzchni zamiast koloru podstawowego, co ogranicza akcenty kolorystyczne.
Rysunek 6. Ciemny motyw Material Design z ograniczoną liczbą akcentów kolorystycznych. Górny pasek aplikacji używa koloru podstawowego w jasnym motywie i koloru powierzchni w ciemnym motywie.

W przypadku scenariuszy niestandardowych użyj właściwości rozszerzenia primarySurface:

Surface(
    // Switches between primary in light theme and surface in dark theme
    color = MaterialTheme.colors.primarySurface,
    /*...*/
) { /*...*/ }

Typografia

Material definiuje system typów, zachęcając do używania niewielkiej liczby stylów o nazwach semantycznych.

Przykład kilku różnych krojów pisma w różnych stylach
Rysunek 7. System typu materiału.

Compose implementuje system typów za pomocą klas Typography, TextStylezwiązanych z czcionkami. Konstruktor Typography oferuje wartości domyślne dla każdego stylu, więc możesz pominąć te, których nie chcesz dostosowywać:

val raleway = FontFamily(
    Font(R.font.raleway_regular),
    Font(R.font.raleway_medium, FontWeight.W500),
    Font(R.font.raleway_semibold, FontWeight.SemiBold)
)

val myTypography = Typography(
    h1 = TextStyle(
        fontFamily = raleway,
        fontWeight = FontWeight.W300,
        fontSize = 96.sp
    ),
    body1 = TextStyle(
        fontFamily = raleway,
        fontWeight = FontWeight.W600,
        fontSize = 16.sp
    )
    /*...*/
)
MaterialTheme(typography = myTypography, /*...*/) {
    /*...*/
}

Jeśli chcesz używać tego samego kroju pisma w całym dokumencie, określ parametr defaultFontFamily i pomijaj parametr fontFamily w elementach TextStyle:

val typography = Typography(defaultFontFamily = raleway)
MaterialTheme(typography = typography, /*...*/) {
    /*...*/
}

Używanie stylów tekstu

Dostęp do elementów TextStyle uzyskuje się za pomocą elementu MaterialTheme.typography. Pobierz elementy TextStyle w ten sposób:

Text(
    text = "Subtitle2 styled",
    style = MaterialTheme.typography.subtitle2
)

Zrzut ekranu przedstawiający różne kroje pisma do różnych celów
Rysunek 8. Używaj różnych krojów i stylów czcionek, aby wyrazić swoją markę.

Kształt

Materiał definiuje system kształtów, który umożliwia określanie kształtów dużych, średnich i małych komponentów.

Wyświetla różne kształty w stylu Material Design.
Rysunek 9. System kształtów Material.

Compose implementuje system kształtów za pomocą klasy Shapes, która umożliwia określenie CornerBasedShape dla każdej kategorii rozmiaru:

val shapes = Shapes(
    small = RoundedCornerShape(percent = 50),
    medium = RoundedCornerShape(0f),
    large = CutCornerShape(
        topStart = 16.dp,
        topEnd = 0.dp,
        bottomEnd = 0.dp,
        bottomStart = 16.dp
    )
)

MaterialTheme(shapes = shapes, /*...*/) {
    /*...*/
}

Wiele komponentów domyślnie używa tych kształtów. Na przykład Button, TextFieldFloatingActionButton domyślnie mają rozmiar mały, AlertDialog domyślnie ma rozmiar średni, a ModalDrawer domyślnie ma rozmiar duży. Pełne mapowanie znajdziesz w dokumentacji schematu kształtów.

Używanie kształtów

Dostęp do elementów Shape uzyskuje się za pomocą elementu MaterialTheme.shapes. Pobierz elementy Shape za pomocą kodu takiego jak ten:

Surface(
    shape = MaterialTheme.shapes.medium, /*...*/
) {
    /*...*/
}

Zrzut ekranu aplikacji, która używa kształtów Material do przekazywania informacji o stanie elementu
Rysunek 10. Używaj kształtów, aby wyrazić markę lub stan.

Style domyślne

W Compose nie ma odpowiednika domyślnych stylów z Androida Views. Podobną funkcjonalność możesz uzyskać, tworząc własne funkcje kompozycyjne overload, które opakowują komponenty Material. Aby na przykład utworzyć styl przycisku, umieść go we własnej funkcji kompozycyjnej, bezpośrednio ustawiając parametry, które chcesz lub musisz zmienić, i udostępniając inne jako parametry funkcji kompozycyjnej zawierającej przycisk.

@Composable
fun MyButton(
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    content: @Composable RowScope.() -> Unit
) {
    Button(
        colors = ButtonDefaults.buttonColors(
            backgroundColor = MaterialTheme.colors.secondary
        ),
        onClick = onClick,
        modifier = modifier,
        content = content
    )
}

Nakładki motywu

W Compose możesz uzyskać efekt podobny do nakładek motywu z widoków Androida, zagnieżdżając komponenty MaterialTheme. Ponieważ MaterialTheme domyślnie ustawia kolory, typografię i kształty na bieżącą wartość motywu, wszystkie inne parametry zachowują swoje wartości domyślne, gdy motyw ustawia tylko jeden z tych parametrów.

Podczas przenoszenia ekranów opartych na widokach do Compose zwróć uwagę na użycie atrybutu android:theme. Prawdopodobnie w tej części drzewa interfejsu Compose potrzebujesz nowego elementu MaterialTheme.

W tym przykładzie ekran szczegółów używa PinkTheme na większości ekranu, a następnie BlueTheme w sekcji powiązanych. Tę koncepcję ilustruje poniższy zrzut ekranu i kod:

Zrzut ekranu aplikacji z zagnieżdżonymi motywami: różowym na ekranie głównym i niebieskim w powiązanej sekcji
Rysunek 11. Zagnieżdżone tematy.

@Composable
fun DetailsScreen(/* ... */) {
    PinkTheme {
        // other content
        RelatedSection()
    }
}

@Composable
fun RelatedSection(/* ... */) {
    BlueTheme {
        // content
    }
}

Stany komponentu

Komponenty Material, z którymi można wchodzić w interakcje (klikać, przełączać itp.), mogą mieć różne stany wizualne. Możliwe stany to włączony, wyłączony, naciśnięty itp.

Funkcje kompozycyjne często mają parametr enabled. Ustawienie false uniemożliwia interakcję i zmienia właściwości, takie jak kolor i wysokość, aby wizualnie przekazać stan komponentu.

Zrzut ekranu z 2 przyciskami: jednym włączonym i jednym wyłączonym, pokazujący ich różne stany wizualne
Rysunek 12. Przycisk z ikonami enabled = true (po lewej) i enabled = false (po prawej).

W większości przypadków możesz polegać na wartościach domyślnych, takich jak kolor i wysokość. Jeśli musisz skonfigurować wartości używane w różnych stanach, możesz skorzystać z dostępnych klas i funkcji pomocniczych. Przyjrzyj się temu przykładowi przycisku:

Button(
    onClick = { /* ... */ },
    enabled = true,
    // Custom colors for different states
    colors = ButtonDefaults.buttonColors(
        backgroundColor = MaterialTheme.colors.secondary,
        disabledBackgroundColor = MaterialTheme.colors.onBackground
            .copy(alpha = 0.2f)
            .compositeOver(MaterialTheme.colors.background)
        // Also contentColor and disabledContentColor
    ),
    // Custom elevation for different states
    elevation = ButtonDefaults.elevation(
        defaultElevation = 8.dp,
        disabledElevation = 2.dp,
        // Also pressedElevation
    )
) { /* ... */ }

Zrzut ekranu przedstawiający 2 przyciski z dostosowanymi kolorami i wysokością w stanach włączonym i wyłączonym
Rysunek 13. Przycisk z ikonami enabled = true (po lewej) i enabled = false (po prawej) z dostosowanymi wartościami koloru i wysokości.

Kręgi na wodzie

Komponenty Material używają efektu fali, aby wskazać, że użytkownik wchodzi z nimi w interakcję. Jeśli w hierarchii używasz znaku MaterialTheme, znak Ripple jest używany jako domyślny Indication w modyfikatorach takich jak clickableindication.

W większości przypadków możesz użyć domyślnego ustawienia Ripple. Jeśli chcesz skonfigurować ich wygląd, możesz użyć RippleTheme, aby zmienić właściwości, takie jak kolor i wartość alfa.

Możesz rozszerzyć RippleTheme i korzystać z funkcji narzędziowych defaultRippleColordefaultRippleAlpha. Następnie możesz podać niestandardowy motyw efektu fali w hierarchii za pomocą elementu LocalRippleTheme:

@Composable
fun MyApp() {
    MaterialTheme {
        CompositionLocalProvider(
            LocalRippleTheme provides SecondaryRippleTheme
        ) {
            // App content
        }
    }
}

@Immutable
private object SecondaryRippleTheme : RippleTheme {
    @Composable
    override fun defaultColor() = RippleTheme.defaultRippleColor(
        contentColor = MaterialTheme.colors.secondary,
        lightTheme = MaterialTheme.colors.isLight
    )

    @Composable
    override fun rippleAlpha() = RippleTheme.defaultRippleAlpha(
        contentColor = MaterialTheme.colors.secondary,
        lightTheme = MaterialTheme.colors.isLight
    )
}

Animowany GIF przedstawiający przyciski z różnymi efektami fali po kliknięciu.
Rysunek 14. Przyciski z różnymi wartościami efektu fali, które są podane za pomocą RippleTheme.

Więcej informacji

Więcej informacji o motywach Material w Compose znajdziesz w tych materiałach.

Codelabs

Filmy