Material Design 2 w sekcji Utwórz

Jetpack Compose udostępnia implementację Material Design, czyli kompleksowego systemu projektowania interfejsów cyfrowych. Komponenty Material Design (przyciski, karty, przełączniki itd.) są oparte na motywie Material Design, co umożliwia systematyczne dostosowywanie interfejsu Material Design, aby lepiej odzwierciedlał markę produktu. Motyw materiału zawiera atrybuty kolor, typografia i kształt. Gdy dostosujesz te atrybuty, zmiany zostaną automatycznie odzwierciedlone w komponentach, których używasz do utworzenia aplikacji.

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

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

Skonfiguruj parametry przesyłane do MaterialTheme, aby zastosować motyw aplikacji.

Dwa kontrastujące zrzuty ekranu. W pierwszym zastosowano domyślny styl MaterialTheme,
a drugi – zmodyfikowany styl.

Rysunek 1. Pierwszy zrzut ekranu przedstawia aplikację, która nie konfiguruje MaterialTheme, więc używa stylu domyślnego. Drugi zrzut ekranu pokazuje aplikację, która przekazuje parametry do MaterialTheme, aby dostosować styl.

Kolor

Kolory są modelowane w Compose za pomocą klasy Color, prostej klasy przechowywania danych.

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

Możesz uporządkować je w dowolny sposób (jako stałe najwyższego poziomu, w jednym tonie lub zdefiniowane w tekście), zdecydowanie zalecamy jednak określenie kolorów w motywie i pobranie ich stamtąd. Takie podejście pozwala na łatwą obsługę ciemnego motywu i motywów zagnieżdżonych.

Przykład palety kolorów motywu

Rysunek 2. System kolorów Material.

Tworzenie udostępnia klasę Colors do modelowania systemu kolorów Material Design. Colors udostępnia funkcje kreatora umożliwiające tworzenie zestawów kolorów jasnych lub ciemnych:

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 je przekazać do MaterialTheme:

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

Używanie kolorów motywu

Możesz pobrać Colors dostarczone do MaterialTheme funkcji kompozycyjnej, używając 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),
    // ...
) { /* ... */ }

Pozwala to nie tylko ustawić kolor funkcji kompozycyjnej, ale także domyślny kolor treści oraz zawartych w niej elementów kompozycyjnych. Wiele elementów kompozycyjnych domyślnie używa tego koloru treści. Na przykład Text określa swój kolor na podstawie koloru 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 umożliwia uzyskanie różnych kolorów tekstu i ikon.

Metoda contentColorFor() pobiera odpowiedni kolor „on” dla dowolnego koloru motywu. Jeśli na przykład ustawisz kolor tła primary w elemencie Surface, będzie ona używać tej funkcji do ustawienia onPrimary jako koloru treści. Jeśli ustawisz kolor tła inny niż motyw, musisz też określić odpowiedni kolor treści. Użyj funkcji LocalContentColor, aby pobrać preferowany kolor treści dla bieżącego tła w określonym miejscu w hierarchii.

Treść alfa

Często trzeba zróżnicować umiejscowienie treści, aby podkreślić ich znaczenie i zapewnić wizualną hierarchię. Rekomendacje dotyczące czytelności tekstu w stylu Material Design zalecają stosowanie różnych poziomów nieprzezroczystości, aby wskazywać różne poziomy znaczenia.

Jetpack Compose implementuje to za pomocą LocalContentAlpha. Możesz określić wersję alfa treści dla hierarchii, podając wartość dla obiektu CompositionLocal. Zagnieżdżone elementy kompozycyjne mogą używać tej wartości, aby stosować do swoich treści grupę alfa. Na przykład Text i Icon domyślnie używają kombinacji LocalContentColor dopasowanej do wartości LocalContentAlpha. Material 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(
        // ...
    )
}

Aby dowiedzieć się więcej na temat: CompositionLocal, zapoznaj się z danymi o zakresie lokalnym dzięki programowi CompositionLocal.

Zrzut ekranu z tytułem artykułu, na którym widać
różne poziomy wyróżnienia tekstu

Rysunek 4. Zastosuj różne poziomy nacisku na tekst, aby wizualnie przedstawić hierarchię informacji. Pierwszy wiersz tekstu to tytuł, który zawiera najważniejszą informację, dlatego zawiera element ContentAlpha.high. Drugi wiersz zawiera mniej ważne metadane, więc korzysta z elementu ContentAlpha.medium.

Ciemny motyw

Aby wdrożyć jasne i ciemne motywy w interfejsie tworzenia, musisz udostępnić różne zestawy elementów 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 zawarta we własnej funkcji kompozycyjnej, która akceptuje parametr określający, czy użyć ciemnego motywu. W tym przypadku funkcja pobiera wartość domyślną darkTheme, wysyłając zapytanie o ustawienie motywu urządzenia.

Aby sprawdzić, czy bieżące urządzenie Colors jest jasne, czy ciemne, możesz użyć kodu podobnego do tego:

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 przypadku interfejsu Material Design powierzchnie z ciemnym motywem o większych wysokościach otrzymują nakładki wysokości, które rozjaśniają tło. Im większa wysokość powierzchni, tym lżejsza, tym bardziej zbliżona do domniemanego źródła światła.

Te nakładki są stosowane automatycznie przez funkcję Surface kompozycyjną, gdy używasz ciemnych kolorów, oraz przez dowolny inny element kompozycyjny Material, który używa powierzchni:

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

Zrzut ekranu aplikacji pokazujący subtelnie różne kolory elementów na różnych poziomach wysokości

Rysunek 5. Tło kart i menu nawigacyjnego u dołu mają kolor surface. Karty i dolna część nawigacji znajdują się na różnych poziomach wysokości nad tłem, dlatego mają nieco inne kolory – karty są jaśniejsze niż tło, a dolna część nawigacji jest jaśniejsza.

W przypadku niestandardowych scenariuszy, które nie obejmują Surface, użyj LocalElevationOverlay, czyli CompositionLocal zawierającego ElevationOverlay używane 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 wysokości, podaj null w wybranym punkcie hierarchii kompozycyjnej:

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

Ograniczone kolorowe akcenty

Zespół Material zaleca stosowanie ograniczonych kolorów akcentów dla ciemnych motywów i w większości przypadków preferuje użycie koloru surface zamiast koloru primary. Obiekty kompozycyjne materiału, takie jak TopAppBar i BottomNavigation, implementują to zachowanie domyślnie.

Rysunek 6. Ciemny motyw Material Design z ograniczoną liczbą kolorów. Górny pasek aplikacji używa koloru podstawowego w jasnym motywie i koloru powierzchni w ciemnym motywie.

W niestandardowych scenariuszach używaj właściwości rozszerzenia primarySurface:

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

Typografia

Material określa system typów, co zachęca do używania niewielkiej liczby stylów nazywanych semantycznie.

Przykład kilku krojów pisma w różnych stylach

Rysunek 7. System rodzajów materiałów.

Funkcja tworzenia implementuje system typów z klasami Typography, TextStyle i powiązanymi z czcionkami. Konstruktor Typography udostępnia 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 czcionki w całym tekście, określ defaultFontFamily parameter i pomiń fontFamily we wszystkich elementach TextStyle:

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

Używanie stylów tekstu

Dostęp do obiektów TextStyle można uzyskać przez MaterialTheme.typography. Pobierz TextStyle w ten sposób:

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

Zrzut ekranu przedstawiający kombinację różnych krojów pisma do różnych celów

Rysunek 8. Wyraź swoją markę, korzystając z różnych krojów czcionek i stylów.

Kształt

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

Pokazuje różne kształty w stylu Material Design

Rysunek 9. System Kształt Material Design.

Tworzenie implementuje system kształtów z klasą Shapes, co pozwala określić właściwość CornerBasedShape dla każdej kategorii rozmiarów:

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 używa tych kształtów domyślnie. Na przykład wartości Button, TextField i FloatingActionButton są domyślnie ustawiane na małą wartość AlertDialog, wartość domyślna to „medium”, a ModalDrawer wartość domyślna to schemat kształtu.

Używanie kształtów

Dostęp do obiektów Shape można uzyskać przez MaterialTheme.shapes. Pobierz plik Shape za pomocą tego kodu:

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

Zrzut ekranu aplikacji, która pokazuje stan elementu za pomocą kształtów Material

Rysunek 10. Użyj kształtów do przedstawienia marki lub stanu.

Style domyślne

W ramach tworzenia stylów domyślnych z widoków Androida nie ma odpowiednika tego koncepcji. Podobne funkcje możesz uzyskać, tworząc własne „przeciążone” funkcje kompozycyjne, które opakowują komponenty Material Design. Aby na przykład utworzyć styl przycisku, umieść go we własnej funkcji kompozycyjnej, bezpośrednio ustaw parametry, które chcesz zmienić, i udostępnij innym parametry jako parametry funkcji kompozycyjnej.

@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 z motywów

Odpowiednik nakładek motywuw widokach Androida w widoku tworzenia wiadomości możesz uzyskać, zagnieżdżając MaterialTheme elementy kompozycyjne. MaterialTheme domyślnie ustawia kolory, typografię i kształty zgodnie z bieżącą wartością motywu, więc jeśli motyw ustawi tylko jeden z tych parametrów, pozostałe parametry zachowają wartości domyślne.

Podczas migracji ekranów opartych na widokach do funkcji tworzenia uważaj na przypadki użycia atrybutu android:theme. Prawdopodobnie w tej części drzewa interfejsu tworzenia wiadomości będzie Ci potrzebny nowy element MaterialTheme.

W przykładzie Sowa ekran szczegółów wyświetla się jako PinkTheme przez większość ekranu, a następnie BlueTheme dla powiązanej sekcji. Poniżej znajdziesz zrzut ekranu i kod.

Rysunek 11. Zagnieżdżone motywy w próbce Sowa.

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

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

Stany komponentów

Komponenty materiałów, z którymi można wchodzić w interakcje (kliknięte, przełączone itp.), mogą mieć różne stany wizualne. Dostępne stany to: włączone, wyłączone, naciśnięte itd.

Elementy kompozycyjne często mają parametr enabled. Ustawienie wartości false zapobiega interakcji i zmienia właściwości takie jak kolor i wysokości, aby wizualnie przedstawić stan komponentu.

Rysunek 12. Przycisk z klawiszami enabled = true (po lewej) i enabled = false (po prawej).

W większości przypadków można polegać na wartościach domyślnych, takich jak kolor i wysokość. Jeśli chcesz skonfigurować wartości używane w różnych stanach, dostępne są klasy i funkcje ułatwiające pracę. Zobacz przykład przycisku poniżej:

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
    )
) { /* ... */ }

Rysunek 13. Przycisk z kolorami enabled = true (po lewej) i enabled = false (po prawej) z dostosowanymi wartościami koloru i wysokości.

Plusk

Komponenty materiału wykorzystują echa, aby wskazać, z którymi elementami wchodzą w interakcję. Jeśli w hierarchii używasz parametru MaterialTheme, jako domyślnego modyfikatora wewnętrznegoIndication, np. clickable i indication, używany będzie element Ripple.

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

Możesz rozszerzyć zakres RippleTheme i korzystać z funkcji narzędziowych defaultRippleColor oraz defaultRippleAlpha. Następnie możesz udostępnić w hierarchii niestandardowy motyw echo za pomocą 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
    )
}

tekst_alternatywny

Rysunek 14. Przyciski o różnych wartościach echa dostarczane przez: RippleTheme.

Więcej informacji

Więcej informacji o podziale tematów na tematy w usłudze Compose znajdziesz w tych dodatkowych materiałach.

Ćwiczenia z programowania

Filmy