Motywy w Compose with Material 3

1. Wprowadzenie

Z tego ćwiczenia w Codelabs dowiesz się, jak tworzyć tematyczne aplikacje w Jetpack Compose z wykorzystaniem Material Design 3. Poznasz też kluczowe elementy składowe schematów kolorów, typografii i kształtów Material Design 3, które pozwolą Ci spersonalizować aplikację w bardziej przystępny sposób.

Dodatkowo poznasz obsługę dynamicznego motywu wraz z różnymi poziomami nacisku.

Czego się nauczysz

Z tego ćwiczenia w Codelabs dowiesz się:

  • Najważniejsze aspekty motywów Material 3
  • Schematy kolorów Material 3 i generowanie motywów do aplikacji
  • Jak obsługiwać dynamiczne i jasne/ciemne motywy w aplikacji
  • Typografia i kształty pozwalające spersonalizować aplikację
  • Komponenty Material 3 i dostosowywanie do stylu aplikacji

Co utworzysz

W ramach tego ćwiczenia w Codelabs stworzysz motyw dla klienta poczty e-mail o nazwie Reply. Zaczynasz od aplikacji bez stylu i korzystasz z motywu podstawowego, a następnie wykorzystujesz zdobyte informacje do tworzenia motywu oraz obsługi ciemnego motywu.

d15db3dc75a9d00f.png

Domyślny punkt początkowy aplikacji z motywem podstawowym.

Utwórz motyw ze schematem kolorów, typografią i kształtami, a potem zastosuj go do listy e-mailowej i strony z informacjami o aplikacji. Do aplikacji dodasz także obsługę motywów dynamicznych. Po ukończeniu ćwiczeń w programie aplikacja będzie obsługiwać zarówno motywy kolorystyczne, jak i dynamiczne.

1357cdbfaaa67721.png

Punkt końcowy ćwiczenia z programowania dotyczącego tematów z jasnym motywem i jasną, dynamiczną motywacją.

1357cdbfaaa67721.png

Punkt końcowy ćwiczenia z programowania dotyczącego tematów z ciemnymi motywami i ciemną, dynamiczną motywacją.

Co będzie potrzebne

2. Przygotowanie

W tym kroku pobierzesz pełny kod aplikacji Reply, którego styl będziesz używać w tym ćwiczeniu w Codelabs.

Pobierz kod

Kod do tego ćwiczenia w Codelabs znajdziesz w repozytorium android-compose-codelabs w repozytorium GitHub. Aby go sklonować, uruchom polecenie:

$ git clone https://github.com/googlecodelabs/android-compose-codelabs

Możesz też pobrać 2 pliki ZIP:

Zobacz przykładową aplikację

Pobrany przed chwilą kod zawiera wszystkie dostępne ćwiczenia z programowania w narzędziu Compose. Aby wykonać to ćwiczenie w programowaniu, otwórz projekt ThemingCodelab w Android Studio.

Warto zacząć od kodu w gałęzi głównej i postępować zgodnie z instrukcjami w Codelabs we własnym tempie. W każdej chwili możesz uruchomić dowolną wersję w Android Studio, zmieniając gałąź git projektu.

Kod startowy

Główny kod zawiera pakiet UI zawierający następujące główne pakiety i pliki, z którymi będziesz korzystać:

  • MainActivity.kt – aktywność w punkcie wejścia, w której uruchamiasz aplikację Odpowiedz.
  • com.example.reply.ui.theme – ten pakiet zawiera motywy, typografię i schematy kolorów. Do tego pakietu dodasz motywy Material Design.
  • com.example.reply.ui.components – zawiera niestandardowe komponenty aplikacji, takie jak elementy listy czy paski aplikacji. Do tych komponentów dodasz motywy.
  • ReplyApp.kt – to nasza główna funkcja kompozycyjna, na której będzie zaczynać się drzewo interfejsu. W tym pliku zostaną zastosowane motywy najwyższego poziomu.

To ćwiczenie w Codelabs koncentruje się na ui plikach pakietów.

3. Motyw Material 3

W Jetpack Compose dostępna jest implementacja Material Design – kompleksowego systemu do tworzenia interfejsów cyfrowych. Komponenty Material Design (przyciski, karty, przełączniki itp.) są oparte na motywie Material Design, który umożliwia systematyczne dostosowywanie interfejsu Material Design, aby lepiej odzwierciedlał markę produktu.

Motyw Material 3 obejmuje te podsystemy, które umożliwiają dodanie ich do aplikacji: schemat kolorów, typografia i kształty. Gdy dostosujesz te wartości, zostaną one automatycznie odzwierciedlone w komponentach M3, których używasz do tworzenia aplikacji. Przyjrzyjmy się poszczególnym podsystemom i zaimplementujmy je w przykładowej aplikacji.

Podsystemy stylu Material Design: kolor, typografia i kształty.

Podsystem kolorów Material 3, typografii i kształtów.

4. Schematy kolorów

Podstawą schematu kolorów jest zestaw 5 głównych kolorów, z których każdy odnosi się do palety tonalnej 13 tonów używanych przez komponenty Material 3.

5 podstawowych kolorów do tworzenia motywów M3.

5 podstawowych kolorów do tworzenia motywów M3.

Każdy kolor uzupełniający (podstawowy, drugorzędny i trzeciorzędny) jest następnie dostępny w 4 zgodnych kolorach w różnych odcieniach, co umożliwia ich powiązanie, określenie uwydatnienia i ekspresję wizualną.

Cztery kolory tonalne koloru podstawowego, dodatkowego i trzeciorzędnego koloru bazowego.

4 kolory tonalne koloru podstawowego, dodatkowego i trzeciorzędnego koloru bazowego.

Analogicznie kolory neutralne są podzielone na 4 odcienie pasujące do powierzchni i tła. Są one również ważne, aby wyróżnić ikony tekstowe umieszczone na dowolnej powierzchni.

Cztery kolory tonalne dla neutralnych kolorów podstawowych.

Cztery kolory tonalne podstawowych kolorów neutralnych.

Dowiedz się więcej o schematach kolorów i rolach kolorów.

Generuję schematy kolorów

Niestandardowy ColorScheme możesz utworzyć ręcznie, ale często łatwiej jest wygenerować go za pomocą kolorów źródłowych Twojej marki. Możesz to zrobić za pomocą narzędzia Material Theme Builder. Opcjonalnie możesz też wyeksportować kod motywów Compose.

Możesz wybrać dowolny kolor, ale w naszym przypadku użycia zostanie użyty domyślny kolor odpowiedzi Odpowiedz #825500. W sekcji Kolory podstawowe po lewej stronie kliknij Kolor podstawowy i dodaj kod w selektorze kolorów.

294f73fc9d2a570e.png

Dodaję podstawowy kod koloru w Material Theme Builder.

Po dodaniu koloru podstawowego w narzędziu Material Theme Builder powinien pojawić się poniższy motyw i opcja eksportu w prawym górnym rogu. Na potrzeby tego ćwiczenia w Codelabs musisz wyeksportować motyw w Jetpack Compose.

Material Theme Builder z opcją eksportu w prawym górnym rogu.

Material Theme Builder z opcją eksportu w prawym górnym rogu.

Kolor podstawowy #825500 generuje ten motyw, który możesz dodać do aplikacji. Material 3 zapewnia szeroki wybór kolorów, które pozwalają elastycznie przedstawiać stan, widoczność i uwydatnienie komponentu.

Schemat kolorów Jasny i Ciemny został wyeksportowany z koloru podstawowego.

Schemat kolorów jasnego i ciemnego został wyeksportowany z koloru podstawowego.

Wygenerowany plik The Color.kt zawiera kolory Twojego motywu ze wszystkimi rolami zdefiniowanymi zarówno w przypadku jasnych, jak i ciemnych kolorów motywu.

Color.kt

package com.example.reply.ui.theme
import androidx.compose.ui.graphics.Color

val md_theme_light_primary = Color(0xFF825500)
val md_theme_light_onPrimary = Color(0xFFFFFFFF)
val md_theme_light_primaryContainer = Color(0xFFFFDDB3)
val md_theme_light_onPrimaryContainer = Color(0xFF291800)
val md_theme_light_secondary = Color(0xFF6F5B40)
val md_theme_light_onSecondary = Color(0xFFFFFFFF)
val md_theme_light_secondaryContainer = Color(0xFFFBDEBC)
val md_theme_light_onSecondaryContainer = Color(0xFF271904)
val md_theme_light_tertiary = Color(0xFF51643F)
val md_theme_light_onTertiary = Color(0xFFFFFFFF)
val md_theme_light_tertiaryContainer = Color(0xFFD4EABB)
val md_theme_light_onTertiaryContainer = Color(0xFF102004)
val md_theme_light_error = Color(0xFFBA1A1A)
val md_theme_light_errorContainer = Color(0xFFFFDAD6)
val md_theme_light_onError = Color(0xFFFFFFFF)
val md_theme_light_onErrorContainer = Color(0xFF410002)
val md_theme_light_background = Color(0xFFFFFBFF)
val md_theme_light_onBackground = Color(0xFF1F1B16)
val md_theme_light_surface = Color(0xFFFFFBFF)
val md_theme_light_onSurface = Color(0xFF1F1B16)
val md_theme_light_surfaceVariant = Color(0xFFF0E0CF)
val md_theme_light_onSurfaceVariant = Color(0xFF4F4539)
val md_theme_light_outline = Color(0xFF817567)
val md_theme_light_inverseOnSurface = Color(0xFFF9EFE7)
val md_theme_light_inverseSurface = Color(0xFF34302A)
val md_theme_light_inversePrimary = Color(0xFFFFB951)
val md_theme_light_shadow = Color(0xFF000000)
val md_theme_light_surfaceTint = Color(0xFF825500)
val md_theme_light_outlineVariant = Color(0xFFD3C4B4)
val md_theme_light_scrim = Color(0xFF000000)

val md_theme_dark_primary = Color(0xFFFFB951)
val md_theme_dark_onPrimary = Color(0xFF452B00)
val md_theme_dark_primaryContainer = Color(0xFF633F00)
val md_theme_dark_onPrimaryContainer = Color(0xFFFFDDB3)
val md_theme_dark_secondary = Color(0xFFDDC2A1)
val md_theme_dark_onSecondary = Color(0xFF3E2D16)
val md_theme_dark_secondaryContainer = Color(0xFF56442A)
val md_theme_dark_onSecondaryContainer = Color(0xFFFBDEBC)
val md_theme_dark_tertiary = Color(0xFFB8CEA1)
val md_theme_dark_onTertiary = Color(0xFF243515)
val md_theme_dark_tertiaryContainer = Color(0xFF3A4C2A)
val md_theme_dark_onTertiaryContainer = Color(0xFFD4EABB)
val md_theme_dark_error = Color(0xFFFFB4AB)
val md_theme_dark_errorContainer = Color(0xFF93000A)
val md_theme_dark_onError = Color(0xFF690005)
val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
val md_theme_dark_background = Color(0xFF1F1B16)
val md_theme_dark_onBackground = Color(0xFFEAE1D9)
val md_theme_dark_surface = Color(0xFF1F1B16)
val md_theme_dark_onSurface = Color(0xFFEAE1D9)
val md_theme_dark_surfaceVariant = Color(0xFF4F4539)
val md_theme_dark_onSurfaceVariant = Color(0xFFD3C4B4)
val md_theme_dark_outline = Color(0xFF9C8F80)
val md_theme_dark_inverseOnSurface = Color(0xFF1F1B16)
val md_theme_dark_inverseSurface = Color(0xFFEAE1D9)
val md_theme_dark_inversePrimary = Color(0xFF825500)
val md_theme_dark_shadow = Color(0xFF000000)
val md_theme_dark_surfaceTint = Color(0xFFFFB951)
val md_theme_dark_outlineVariant = Color(0xFF4F4539)
val md_theme_dark_scrim = Color(0xFF000000)


val seed = Color(0xFF825500)

Wygenerowany The Theme.kt plik zawiera konfigurację jasnych i ciemnych schematów kolorów oraz motyw aplikacji. Zawiera też główną funkcję tworzenia motywów – AppTheme().

Theme.kt

package com.example.reply.ui.theme

import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.runtime.Composable


private val LightColors = lightColorScheme(
   primary = md_theme_light_primary,
   onPrimary = md_theme_light_onPrimary,
   primaryContainer = md_theme_light_primaryContainer,
   onPrimaryContainer = md_theme_light_onPrimaryContainer,
   secondary = md_theme_light_secondary,
   onSecondary = md_theme_light_onSecondary,
   secondaryContainer = md_theme_light_secondaryContainer,
   onSecondaryContainer = md_theme_light_onSecondaryContainer,
   tertiary = md_theme_light_tertiary,
   onTertiary = md_theme_light_onTertiary,
   tertiaryContainer = md_theme_light_tertiaryContainer,
   onTertiaryContainer = md_theme_light_onTertiaryContainer,
   error = md_theme_light_error,
   errorContainer = md_theme_light_errorContainer,
   onError = md_theme_light_onError,
   onErrorContainer = md_theme_light_onErrorContainer,
   background = md_theme_light_background,
   onBackground = md_theme_light_onBackground,
   surface = md_theme_light_surface,
   onSurface = md_theme_light_onSurface,
   surfaceVariant = md_theme_light_surfaceVariant,
   onSurfaceVariant = md_theme_light_onSurfaceVariant,
   outline = md_theme_light_outline,
   inverseOnSurface = md_theme_light_inverseOnSurface,
   inverseSurface = md_theme_light_inverseSurface,
   inversePrimary = md_theme_light_inversePrimary,
   surfaceTint = md_theme_light_surfaceTint,
   outlineVariant = md_theme_light_outlineVariant,
   scrim = md_theme_light_scrim,
)


private val DarkColors = darkColorScheme(
   primary = md_theme_dark_primary,
   onPrimary = md_theme_dark_onPrimary,
   primaryContainer = md_theme_dark_primaryContainer,
   onPrimaryContainer = md_theme_dark_onPrimaryContainer,
   secondary = md_theme_dark_secondary,
   onSecondary = md_theme_dark_onSecondary,
   secondaryContainer = md_theme_dark_secondaryContainer,
   onSecondaryContainer = md_theme_dark_onSecondaryContainer,
   tertiary = md_theme_dark_tertiary,
   onTertiary = md_theme_dark_onTertiary,
   tertiaryContainer = md_theme_dark_tertiaryContainer,
   onTertiaryContainer = md_theme_dark_onTertiaryContainer,
   error = md_theme_dark_error,
   errorContainer = md_theme_dark_errorContainer,
   onError = md_theme_dark_onError,
   onErrorContainer = md_theme_dark_onErrorContainer,
   background = md_theme_dark_background,
   onBackground = md_theme_dark_onBackground,
   surface = md_theme_dark_surface,
   onSurface = md_theme_dark_onSurface,
   surfaceVariant = md_theme_dark_surfaceVariant,
   onSurfaceVariant = md_theme_dark_onSurfaceVariant,
   outline = md_theme_dark_outline,
   inverseOnSurface = md_theme_dark_inverseOnSurface,
   inverseSurface = md_theme_dark_inverseSurface,
   inversePrimary = md_theme_dark_inversePrimary,
   surfaceTint = md_theme_dark_surfaceTint,
   outlineVariant = md_theme_dark_outlineVariant,
   scrim = md_theme_dark_scrim,
)

@Composable
fun AppTheme(
   useDarkTheme: Boolean = isSystemInDarkTheme(),
   content: @Composable() () -> Unit
) {
   val colors = if (!useDarkTheme) {
       LightColors
   } else {
       DarkColors
   }

   MaterialTheme(
       colorScheme = colors,
       content = content
   )
}

Podstawowym elementem wdrażania motywów w Jetpack Compose jest funkcja kompozycyjna MaterialTheme.

Funkcję kompozycyjną MaterialTheme() opakowujesz w funkcję AppTheme(), która przyjmuje 2 parametry:

  • useDarkTheme – ten parametr jest powiązany z funkcją isSystemInDarkTheme(), co umożliwia obserwowanie ustawień motywów systemu i zastosowanie jasnego lub ciemnego motywu. Jeśli chcesz ręcznie pozostawić aplikację w jasnym lub ciemnym motywie, do funkcji useDarkTheme możesz przekazać wartość logiczną.
  • content – zawartość, do której motyw zostanie zastosowany.

Theme.kt

@Composable
fun AppTheme(
   useDarkTheme: Boolean = isSystemInDarkTheme(),
   content: @Composable() () -> Unit
) {
   val colors = if (!useDarkTheme) {
       LightColors
   } else {
       DarkColors
   }

   MaterialTheme(
       colorScheme = colors,
       content = content
   )
}

Jeśli spróbujesz uruchomić aplikację teraz, zobaczysz, że wygląda tak samo. Po zaimportowaniu nowego schematu kolorów z nowymi kolorami motywów nadal będzie widoczny motyw bazowy, ponieważ motyw nie został zastosowany w aplikacji Utwórz.

Aplikacja z motywem podstawowym, gdy nie zastosowano żadnego motywu.

Aplikacja z motywem podstawowym, gdy nie zastosowano żadnego motywu.

Aby zastosować nowy motyw, w MainActivity.kt dodaj główny element kompozycyjny ReplyApp za pomocą głównej funkcji tworzenia motywów AppTheme().

MainActivity.kt

setContent {
   val uiState by viewModel.uiState.collectAsStateWithLifecycle()

   AppTheme {
       ReplyApp(/*..*/)
   }
}

Zaktualizowane zostaną też funkcje podglądu, aby można było zobaczyć motyw zastosowany do podglądów aplikacji. Aby zastosować motywy w podglądach, zawiń funkcję ReplyApp kompozycyjną wewnątrz elementu ReplyAppPreview() za pomocą elementu AppTheme.

W parametrach podglądu są zdefiniowane zarówno jasny, jak i ciemny motyw systemowy, więc zobaczysz oba podglądy.

MainActivity.kt

@Preview(
   uiMode = Configuration.UI_MODE_NIGHT_YES,
   name = "DefaultPreviewDark"
)
@Preview(
   uiMode = Configuration.UI_MODE_NIGHT_NO,
   name = "DefaultPreviewLight"
)
@Composable
fun ReplyAppPreview() {
   AppTheme {
       ReplyApp(
           replyHomeUIState = ReplyHomeUIState(
               emails = LocalEmailsDataProvider.allEmails
           )
       )
   }
}

Jeśli uruchomisz aplikację teraz, zobaczysz jej podgląd z zaimportowanymi kolorami motywu zamiast motywu bazowego.

fddf7b9cc99b1fe3.png be7a661b4553167b.png

Aplikacja z motywem podstawowym (po lewej).

Aplikacja z zaimportowanym motywem kolorystycznym (po prawej).

674cec6cc12db6a0.png

Jasny i ciemny podgląd aplikacji z zaimportowanymi motywami kolorystycznymi.

Material 3 obsługuje zarówno jasne, jak i ciemne schematy kolorów. aplikacja została opakowana tylko z zaimportowanym motywem; Komponenty Material 3 używają domyślnych ról kolorów.

Zanim zaczniesz dodawać kolory do aplikacji, dowiedz się więcej o rolach kolorów i ich wykorzystywaniu.

Role oznaczone kolorami i ułatwienia dostępu

Każdej roli koloru można użyć w różnych miejscach w zależności od stanu i widoczności komponentu.

1f184a05ea57aa84.png

Role kolorów podstawowych, dodatkowych i trzeciorzędnych.

Podstawowy to kolor podstawowy, który jest używany w przypadku głównych komponentów, takich jak widoczne przyciski i stany aktywności.

Dodatkowy kolor klucza jest używany w przypadku mniej widocznych komponentów interfejsu, np. elementów filtra.

Trzeci kolor klucza pozwala uzyskać kontrastowe akcenty, a kolory neutralne stanowią tło i powierzchnie aplikacji.

System kolorów Material Design przedstawia standardowe wartości odcieni i miary, które pozwalają uzyskać dostępne współczynniki kontrastu. Użyj tego samego ustawienia jako głównego nad kontenerem głównym, nad kontenerem głównym oraz nad innymi kolorami uzupełniającymi i neutralnymi, by zapewnić użytkownikom odpowiedni kontrast.

Więcej informacji znajdziesz w artykule o rolach kolorów i ułatwieniach dostępu.

Podwyższenia tonacji i cieni

Materiał 3 reprezentuje wysokość głównie za pomocą nakładek w tonach. To nowy sposób odróżniania kontenerów i powierzchni od siebie – zwiększenie tonacji polega na uwydatnieniu tonacji oraz cieni.

Wysokość tonalna z uniesieniem cienia Wysokość tonalna na poziomie 2, która pobiera kolor z głównego przedziału kolorów.

Nakładki wysokości w ciemnych motywach zmieniły się też w kolorowe nakładki w Material Design 3. Kolor nakładki pochodzi z głównego boksu koloru.

Powierzchnia M3 – kompozycyjna baza za większością komponentów M3 – umożliwia podniesienie zarówno tonacji, jak i cieni:

Surface(
   modifier = modifier,
   tonalElevation = {..}
   shadowElevation = {..}
) {
   Column(content = content)
}

Dodaję kolory do aplikacji

Po uruchomieniu aplikacji zobaczysz, że wyeksportowane kolory są widoczne w aplikacji, w której komponenty mają kolory domyślne. Skoro znamy już role kolorów i ich użycie, nadajmy aplikacji odpowiednie role związane z kolorami.

be7a661b4553167b.png

Aplikacja z motywem kolorystycznym i komponentami przyjmującymi domyślne role dotyczące kolorów.

Kolory powierzchni

Na ekranie głównym zaczniesz od opakowania głównej aplikacji kompozycyjnej w komponencie Surface(), aby zapewnić podstawę do umieszczania na niej zawartości aplikacji. Otwórz plik MainActivity.kt i zapakuj kompozycję ReplyApp() za pomocą funkcji Surface.

Należy również określić wysokość tonalną wynoszącą 5 dp, aby nadać powierzchnię kolor tonalny boksu głównego, co zwiększy kontrast względem elementu listy i znajdującego się nad nim paska wyszukiwania. Domyślnie wartość tonalnego i cienia powierzchni wynosi 0, dp.

MainActivity.kt

AppTheme {
   Surface(tonalElevation = 5.dp) {
       ReplyApp(
           replyHomeUIState = uiState,
          // other parameters
         )
   }
}

Jeśli po uruchomieniu aplikacji widzisz strony Lista i Szczegóły, powinna być widoczna powierzchnia tonalna zastosowana do całej aplikacji.

be7a661b4553167b.png e70d762495173610.png

Tło aplikacji bez powierzchni i koloru tonu (po lewej).

Tło aplikacji z zastosowanymi kolorami powierzchni i tonacji (po prawej).

Kolory paska aplikacji

Nasz niestandardowy pasek wyszukiwania u góry nie ma jasnego tła zgodnie z wymaganiami projektu. Domyślnie powróci do domyślnej powierzchni podstawowej. Możesz podać tło, aby wyraźnie oddzielać od siebie treści.

5779fc399d8a8187.png

Niestandardowy pasek wyszukiwania bez tła (po lewej).

Niestandardowy pasek wyszukiwania na tle (po prawej).

Edytujesz aplikację ui/components/ReplyAppBars.kt, która zawiera pasek aplikacji. Dodasz MaterialTheme.colorScheme.background do Row elementu Modifier kompozycyjnego.

ReplyAppBars.kt

@Composable
fun ReplySearchBar(modifier: Modifier = Modifier) {
   Row(
       modifier = modifier
           .fillMaxWidth()
           .padding(16.dp)
           .background(MaterialTheme.colorScheme.background),
       verticalAlignment = Alignment.CenterVertically
   ) {
       // Search bar content
   }
}

Powierzchnia tonalna od paska aplikacji powinna być teraz wyraźnie oddzielona kolorem tła.

b1b374b801dadc06.png

Pasek wyszukiwania z kolorem tła na powierzchni tonalnej.

Kolory pływającego przycisku polecenia

70ceac87233fe466.png

Duży przycisk typu FAB bez zastosowanego motywu (po lewej).

Duży przycisk PPP z motywem zawierający trzeciorzędny kolor (po prawej).

Możesz poprawić wygląd pływającego przycisku polecenia na ekranie głównym, by się wyróżniał jako przycisk z wezwaniem do działania. Aby to zastosować, trzeba zastosować trzeciorzędny kolor uzupełniający.

W pliku ReplyListContent.kt zmień kolor pola containerColor dla przycisku PPP na tertiaryContainer, a kolor treści na onTertiaryContainer, by zachować dostępność i kontrast kolorów.

ReplyListContent.kt

ReplyInboxScreen(/*..*/) {
// Email list content
  LargeFloatingActionButton(
    containerColor = MaterialTheme.colorScheme.tertiaryContainer,
    contentColor = MaterialTheme.colorScheme.onTertiaryContainer
  ){
   /*..*/   
  }
}

Uruchom aplikację, aby zobaczyć swój ulubiony przycisk typu FAB. W tym ćwiczeniu z programowania używasz: LargeFloatingActionButton.

Kolory kart

Lista e-mailowa na ekranie głównym korzysta z komponentu karty. Domyślnie jest to Wypełniona karta, która w kolorze kontenera korzysta z koloru wersji powierzchni, by wyraźnie oddzielać tę powierzchnię od koloru karty. Narzędzie Compose zawiera też implementacje funkcji ElevatedCard i OutlinedCard.

Możesz dodatkowo wyróżnić niektóre ważne elementy, podając dodatkowe odcienie kolorów. Zmodyfikujesz plik ui/components/ReplyEmailListItem.kt, zmieniając kolor kontenera karty za pomocą pola CardDefaults.cardColors() w przypadku ważnych e-maili:

ReplyEmailListItem.kt

Card(
   modifier =  modifier
       .padding(horizontal = 16.dp, vertical = 4.dp)
       .semantics { selected = isSelected }
       .clickable { navigateToDetail(email.id) },
   colors = CardDefaults.cardColors(
       containerColor = if (email.isImportant)
           MaterialTheme.colorScheme.secondaryContainer
       else MaterialTheme.colorScheme.surfaceVariant
   )
){
  /*..*/   
}

5818200be0b01583.png 9367d40023db371d.png

Wyróżnij element listy za pomocą dodatkowego koloru kontenera na powierzchni tonalnej.

Kolor elementu listy szczegółów

To Twój główny motyw ekranu głównego. Kliknij dowolny element listy e-mailowej, aby otworzyć stronę z informacjami.

7a9ea7cf3e91e9c7.png 79b3874aeca4cd1.png

Domyślna strona z informacjami bez tematycznego elementu listy (po lewej).

Element listy szczegółów z zastosowanym motywem tła (po prawej).

Do elementu listy nie zastosowano żadnego koloru, więc przywracany jest domyślny kolor powierzchni tonalnej. Zastosowanie koloru tła do elementu listy spowoduje utworzenie separacji i dodanie dopełnienia, aby zapewnić odstępy wokół tła.

ReplyEmailThreadItem.kt

@Composable
fun ReplyEmailThreadItem(
   email: Email,
   modifier: Modifier = Modifier
) {
   Column(
       modifier = modifier
           .fillMaxWidth()
           .padding(16.dp)
           .background(MaterialTheme.colorScheme.background)
           .padding(20.dp)
    ) {
      // List item content
    }
}

Widać, że wystarczy określić tło, aby wyraźnie oddzielić powierzchnię tonalną od elementu listy.

Masz teraz zarówno stronę główną, jak i stronę szczegółów z prawidłowymi rolami kolorów i użytkowaniem . Zobaczmy, jak możesz wykorzystać dynamiczne kolory w swojej aplikacji, aby jeszcze bardziej spersonalizować i zapewnić spójność aplikacji.

5. Dodawanie dynamicznych kolorów w aplikacji

Dynamiczny kolor to kluczowy element Material 3. W tym przypadku algorytm pobiera z tapety użytkownika niestandardowe kolory do zastosowania w aplikacjach i interfejsie systemu.

Dynamiczne określanie motywów sprawia, że aplikacje są bardziej spersonalizowane. Zapewnia też użytkownikom spójne i płynne korzystanie z motywu systemowego.

Dynamiczne kolory są dostępne na Androidzie 12 i nowszych. Jeśli kolor dynamiczny jest dostępny, możesz skonfigurować dynamiczny schemat kolorów za pomocą atrybutu dynamicDarkColorScheme() lub dynamicLightColorScheme(). Jeśli nie, wybierz domyślny jasny lub ciemny kolor ColorScheme.

Zastąp kod funkcji AppTheme w pliku Theme.kt tym kodem:

Theme.kt

@Composable
fun AppTheme(
   useDarkTheme: Boolean =  isSystemInDarkTheme(),
   content: @Composable () -> Unit
) {
   val context = LocalContext.current
   val colors = when {
       (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) -> {
           if (useDarkTheme) dynamicDarkColorScheme(context)
           else dynamicLightColorScheme(context)
       }
       useDarkTheme -> DarkColors
       else -> LightColors
   }
   
      MaterialTheme(
       colorScheme = colors,
       content = content
     )
}

fecc63b4c6034236.png

Dynamiczny motyw wykonany z tapety na Androida 13.

Po uruchomieniu aplikacji powinno zostać zastosowane dynamiczne motywy z domyślną tapetą na Androida 13.

Pasek stanu może też zmieniać się dynamicznie w zależności od schematu kolorów używanego do motywu aplikacji.

1095e2b2c1ffdc14.png

Aplikacja bez zastosowanego koloru paska stanu (po lewej).

Aplikacja z zastosowanym kolorem paska stanu (po prawej).

Aby zaktualizować kolor paska stanu zgodnie z głównym kolorem motywu, dodaj ten kolor po wybraniu schematu kolorów w komponencie AppTheme:

Theme.kt

@Composable
fun AppTheme(
   useDarkTheme: Boolean =  isSystemInDarkTheme(),
   content: @Composable () -> Unit
) {
 
 // color scheme selection code

 // Add primary status bar color from chosen color scheme.
 val view = LocalView.current
 if (!view.isInEditMode) {
    SideEffect {
        val window = (view.context as Activity).window
        window.statusBarColor = colors.primary.toArgb()
        WindowCompat
            .getInsetsController(window, view)
            .isAppearanceLightStatusBars = useDarkTheme
    }
 }
   
  MaterialTheme(
    colorScheme = colors,
     content = content
   )
}

Po uruchomieniu aplikacji pasek stanu powinien przyjąć kolor podstawowy. Możesz też wypróbować jasne i ciemne motywy dynamiczne, zmieniając motyw ciemny systemu.

69093b5bce31fd43.png

Dynamiczny jasny (po lewej) i ciemny (po prawej) motyw z domyślną tapetą Androida 13.

Do tej pory udało Ci się zastosować w aplikacji kolory, które poprawiły jej wygląd. Możesz jednak zauważyć, że cały tekst w aplikacji ma ten sam rozmiar, więc możesz teraz dodać do aplikacji typografię.

6. Typografia

Material Design 3 definiuje skalę typów. Uprościliśmy nazewnictwo i grupowanie w taki sposób, aby wyświetlać, nagłówek, tytuł, treść i etykietę. Można wybrać duży, średni i mały rozmiar.

999a161dcd9b0ec4.png

Skala typu Material 3.

Definiowanie typografii

Compose udostępnia klasę M3 Typography wraz z istniejącymi klasami TextStyle i font-related do modelowania skali Material 3.

Konstruktor typografii oferuje domyślne wartości dla każdego stylu, dzięki czemu możesz pominąć parametry, których nie chcesz dostosowywać. Więcej informacji znajdziesz w artykule o stylach typograficznych i ich wartościach domyślnych.

W aplikacji będziesz używać 5 stylów typograficznych: headlineSmall, titleLarge, bodyLarge, bodyMedium i labelMedium. Obejmują one zarówno ekran główny, jak i ekran szczegółów.

Ekran pokazujący użycie typografii tytułu, etykiety i stylu treści.

Ekran pokazujący typografię tytułu, etykiety i stylu treści.

Następnie przejdź do pakietu aplikacji ui/theme i otwórz aplikację Type.kt. Dodaj ten kod, aby zamiast wartości domyślnych zastosować własną implementację niektórych stylów tekstu:

Type.kt

val typography = Typography(
   headlineSmall = TextStyle(
       fontWeight = FontWeight.SemiBold,
       fontSize = 24.sp,
       lineHeight = 32.sp,
       letterSpacing = 0.sp
   ),
   titleLarge = TextStyle(
       fontWeight = FontWeight.Normal,
       fontSize = 18.sp,
       lineHeight = 28.sp,
       letterSpacing = 0.sp
   ),
   bodyLarge = TextStyle(
       fontWeight = FontWeight.Normal,
       fontSize = 16.sp,
       lineHeight = 24.sp,
       letterSpacing = 0.15.sp
   ),
   bodyMedium = TextStyle(
       fontWeight = FontWeight.Medium,
       fontSize = 14.sp,
       lineHeight = 20.sp,
       letterSpacing = 0.25.sp
   ),
   labelMedium = TextStyle(
       fontWeight = FontWeight.SemiBold,
       fontSize = 12.sp,
       lineHeight = 16.sp,
       letterSpacing = 0.5.sp
   )
)

Typografia jest już zdefiniowana. Aby dodać go do motywu, przekaż go do funkcji kompozycyjnej MaterialTheme() w obiekcie AppTheme:

Theme.kt

@Composable
fun AppTheme(
   useDarkTheme: Boolean = isSystemInDarkTheme(),
   content: @Composable() () -> Unit
) {
  // dynamic theming content

   MaterialTheme(
       colorScheme = colors,
       typography = typography,
       content = content
   )
}

Praca z typografią

Tak samo jak w przypadku kolorów, uzyskasz dostęp do stylu typograficznego bieżącego motywu, używając opcji MaterialTheme.typography. Dzięki temu uzyskasz instancję typograficzną, która będzie używać całej zdefiniowanej typografii w języku Type.kt.

Text(
   text = "Hello M3 theming",
   style = MaterialTheme.typography.titleLarge
)

Text(
   text = "you are learning typography",
   style = MaterialTheme.typography.bodyMedium
)

Twój produkt prawdopodobnie nie będzie potrzebować wszystkich 15 domyślnych stylów ze skali typu Material Design. W tym ćwiczeniu w programowaniu wybieranych jest 5 rozmiarów. Pozostałe zostaną pominięte.

Ponieważ do funkcji kompozycyjnych Text( nie zastosowano typografii, cały tekst domyślnie przyjmuje wartość Typography.bodyLarge.

Typografia listy domowej

Następnie zastosuj typografię do funkcji ReplyEmailListItem w ui/components/ReplyEmailListItem.kt, aby wprowadzić rozróżnienie między tytułami a etykietami:

ReplyEmailListItem.kt

Text(
   text = email.sender.firstName,
   style = MaterialTheme.typography.labelMedium
)

Text(
   text = email.createdAt,
   style = MaterialTheme.typography.labelMedium
)

Text(
   text = email.subject,
   style = MaterialTheme.typography.titleLarge,
   modifier = Modifier.padding(top = 12.dp, bottom = 8.dp),
)

Text(
   text = email.body,
   maxLines = 2,
   style = MaterialTheme.typography.bodyLarge,
   overflow = TextOverflow.Ellipsis
)

90645c0765167bb7.png 6c4af2f412c18bfb.png

Ekran główny bez typografii (po lewej).

Ekran główny z zastosowaną typografią (po prawej).

Typografia listy szczegółów

W podobny sposób dodajesz typografię na ekranie szczegółów, aktualizując wszystkie elementy składowe tekstu ReplyEmailThreadItem w ui/components/ReplyEmailThreadItem.kt:

ReplyEmailThreadItem.kt

Text(
   text = email.sender.firstName,
   style = MaterialTheme.typography.labelMedium
)

Text(
   text = stringResource(id = R.string.twenty_mins_ago),
   style = MaterialTheme.typography.labelMedium
)

Text(
   text = email.subject,
   style = MaterialTheme.typography.bodyMedium,
   modifier = Modifier.padding(top = 12.dp, bottom = 8.dp),
)

Text(
   text = email.body,
   style = MaterialTheme.typography.bodyLarge,
   color = MaterialTheme.colorScheme.onSurfaceVariant
)

543ac09e43d8761.png 3412771e95a45f36.png

Ekran ze szczegółami bez zastosowanej typografii (po lewej).

Ekran ze szczegółami z zastosowaną typografią (po prawej).

Dostosowywanie typografii

W funkcji tworzenia wiadomości bardzo łatwo jest dostosować styl tekstu i dostosować czcionkę. Możesz zmodyfikować TextStyle, aby dostosować m.in. typ i rodzinę czcionek oraz odstępy między literami.

Zmienisz styl tekstu w pliku theme/Type.kt. Zostanie on też odzwierciedlony we wszystkich komponentach, które go używają.

Zmień fontWeight na SemiBold i lineHeight na 32.sp dla elementu titleLarge, który będzie używany w temacie elementu na liście. Skupienie się na temacie materiału i wyraźne rozdzielenie go.

Type.kt

...
titleLarge = TextStyle(
   fontWeight = FontWeight.SemiBold,
   fontSize = 18.sp,
   lineHeight = 32.sp,
   letterSpacing = 0.0.sp
),
...

f8d2212819eb0b61.png

Stosowanie niestandardowej typografii do tekstu tematu.

7. Kształty

Powierzchnie materiałowe mogą być wyświetlane w różnych kształtach. Kształty kierują uwagę, identyfikują komponenty, przekazują stan i wyrażają markę.

Definiowanie kształtów

Narzędzie Compose udostępnia klasę Shapes z rozwiniętymi parametrami umożliwiającymi implementację nowych kształtów M3. Skala kształtów M3, podobnie jak skala typów, umożliwia szeroki zakres kształtów w interfejsie.

Skala kształtów ma różne rozmiary:

  • Bardzo mały
  • Mały
  • Średnia
  • Duży
  • Bardzo duży

Domyślnie każdy kształt ma domyślną wartość, którą można zastąpić. W przypadku aplikacji do modyfikowania elementu listy używasz średniego kształtu, ale możesz też zadeklarować inne kształty. Utwórz w pakiecie ui/theme nowy plik o nazwie Shape.kt i dodaj kod dla kształtów:

Shape.kt

package com.example.reply.ui.theme

import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Shapes
import androidx.compose.ui.unit.dp

val shapes = Shapes(
   extraSmall = RoundedCornerShape(4.dp),
   small = RoundedCornerShape(8.dp),
   medium = RoundedCornerShape(16.dp),
   large = RoundedCornerShape(24.dp),
   extraLarge = RoundedCornerShape(32.dp)
)

Gdy masz już zdefiniowany szablon shapes, przekaż go do narzędzia MaterialTheme M3, tak jak w przypadku kolorów i typografii:

Theme.kt

@Composable
fun AppTheme(
   useDarkTheme: Boolean = isSystemInDarkTheme(),
   content: @Composable() () -> Unit
) {
  // dynamic theming content

   MaterialTheme(
       colorScheme = colors,
       typography = typography,
       shapes = shapes
       content = content
   )
}

Praca z kształtami

Podobnie jak w przypadku kolorów i typografii, możesz stosować kształty do komponentów Material, korzystając z elementu MaterialTheme.shape. Zapewnia to wystąpienie Shape umożliwiające dostęp do kształtów Material.

Do wielu komponentów Materiał zastosowano już domyślne kształty, ale możesz udostępniać komponenty i stosować do nich własne kształty, korzystając z dostępnych przedziałów.

Card(shape = MaterialTheme.shapes.medium) { /* card content */ }
FloatingActionButton(shape = MaterialTheme.shapes.large) { /* fab content */}

Domyślne wartości kształtów dla wszystkich komponentów Material 3.Przyporządkowywanie komponentów Material Design przy użyciu różnych rodzajów kształtów.

Mapowanie kształtów dla wszystkich komponentów znajdziesz w dokumentacji kształtu.

Do tworzenia wiadomości są też dostępne 2 inne kształty – RectangleShape i CircleShape. Prostokąt nie ma promienia obramowania, a obramowanie okrągłe jest pełne.

Możesz też zastosować kształt do komponentów, używając komponentu Modifiers, które mają kształty, takie jak Modifier.clip, Modifier.background czy Modifier.border.

Kształt paska aplikacji

Tło paska aplikacji powinno mieć zaokrąglony róg:

f873392abe535494.png

W elemencie TopAppBar jest używany kolor Row z kolorem tła. Aby uzyskać zaokrąglone rogi tła, określ jego kształt, przekazując parametr CircleShape do modyfikatora tła:

ReplyAppBars.kt

@Composable
fun ReplySearchBar(modifier: Modifier = Modifier) {
   Row(
       modifier = modifier
           .fillMaxWidth()
           .padding(16.dp)
           .background(
               MaterialTheme.colorScheme.background,
               CircleShape
           ),
       verticalAlignment = Alignment.CenterVertically
   ) {
       // Search bar content
   }
}

f873392abe535494.png

Kształt elementu listy szczegółów

Na ekranie głównym używasz karty, która domyślnie korzysta z Shape.Medium. Na stronie z informacjami została jednak użyta kolumna z kolorem tła. Aby uzyskać jednolity wygląd listy, nałóż na nią średni kształt.

3412771e95a45f36.png 80ee881c41a98c2a.png

Kolumna elementu listy szczegółów bez kształtu w elemencie listy (po lewej) i bez kształtu na liście (po prawej).

ReplyEmailThreadItem.kt

@Composable
fun ReplyEmailThreadItem(
   email: Email,
   modifier: Modifier = Modifier
) {
   Column(
       modifier = modifier
           .fillMaxWidth()
           .padding(8.dp)
           .background(
               MaterialTheme.colorScheme.background,
               MaterialTheme.shapes.medium
           )
           .padding(16.dp)

   ) {
      // List item content
      
   }
}

Teraz po uruchomieniu aplikacji zobaczysz szczegółową listę ekranów w kształcie kształtu medium.

8. Wyróżnienie

Wyróżnienie w interfejsie pozwala wyróżnić niektóre treści od innych, na przykład gdy chcesz odróżnić tytuł od napisów. W M3 najważniejsze są różne kolory i ich kombinacje. Możesz wzmocnić reklamę na 2 sposoby:

  1. Użycie powierzchni, wariantu powierzchni i tła oraz kolorów na powierzchni i na powierzchni z rozszerzonego systemu kolorów M3.

Na przykład opcję powierzchni można używać z wariantem powierzchni i elementu, a wariant powierzchni – z elementem na powierzchni, aby uzyskać różne poziomy podkreślenia.

Wariantów powierzchni możesz też używać z kolorami uzupełniającymi, aby nie uwydatniać kolorów będących akcentami, ale nadal były dostępne i utrzymują odpowiedni współczynnik kontrastu.

Role kolorów: Powierzchnia, Tło i Powierzchnia.

Role kolorów powierzchni, tła i powierzchni.

  1. używanie różnych grubości czcionki w tekście. Tak jak w sekcji typograficznej, możesz dodać do skali typu niestandardowe wagi, aby podkreślić znaczenie.

Następnie zaktualizuj atrybut ReplyEmailListItem.kt, aby podkreślić różnicę przy użyciu wariantu powierzchni. Domyślnie zawartość karty ma domyślny kolor zależny od tła.

Zmienisz kolor tekstu godziny i treści elementu kompozycyjnego na onSurfaceVariant. Zmniejsza to wyróżnienie w porównaniu z funkcją onContainerColors, która jest domyślnie stosowana do elementów kompozycyjnych tekstu tematu i tytułu.

2c9b7f2bd016edb8.png 6850ff391f21e4ba.png

Czas i tekst główny z takim samym naciskiem w porównaniu z tematem i tytułem (po lewej).

Czas i treść z mniejszym naciskiem w porównaniu z tematem i tytułem (po prawej).

ReplyEmailListItem.kt

Text(
   text = email.createdAt,
   style = MaterialTheme.typography.labelMedium,
   color = MaterialTheme.colorScheme.onSurfaceVariant
)

Text(
   text = email.body,
   maxLines = 2,
   style = MaterialTheme.typography.bodyLarge,
   color = MaterialTheme.colorScheme.onSurfaceVariant
   overflow = TextOverflow.Ellipsis
)

W przypadku ważnej karty e-mail z tłem secondaryContainer cały tekst jest domyślnie w kolorze onSecondaryContainer. W przypadku pozostałych e-maili tło jest ustawione na surfaceVariant,, więc domyślnie cały tekst ma kolor onSurfaceVariant.

9. Gratulacje

Gratulacje! Udało Ci się ukończyć to szkolenie z programowania. Zaimplementowałeś w Compose motywy Material Design, używając kolorów, typografii i kształtów, a także dynamicznych kolorów, aby utworzyć motywację dla aplikacji i zapewnić jej spersonalizowaną obsługę.

2d8fcabf15ac5202.png 5a4d31db0185dca6.png ce009e4ce560834d.png

Na końcu wyników do tworzenia motywów zostały zastosowane dynamiczne kolory i motywy kolorystyczne.

Co dalej?

Zapoznaj się z innymi ćwiczeniami z programowania dotyczącymi ścieżki tworzenia wiadomości:

Więcej informacji

Przykładowe aplikacje

Dokumenty referencyjne