1. Zanim zaczniesz
Z tego ćwiczenia w Codelabs dowiesz się, jak zharmonizować spersonalizowane kolory z kolorami wygenerowanymi przez dynamiczny motyw.
Wymagania wstępne
Deweloperzy powinni
- znajomość podstawowych koncepcji motywów w Androidzie;
- Wygodna praca z widokami widżetów Androida i ich właściwościami
Czego się nauczysz
- Jak używać harmonizacji kolorów w aplikacji za pomocą różnych metod
- Jak działa harmonizacja i jak zmienia kolor
Czego potrzebujesz
- komputer z zainstalowanym systemem Android, jeśli chcesz śledzić instrukcje,
2. Aplikacje ogółem
Voyaĝi to aplikacja transportu publicznego, która korzysta już z motywu dynamicznego. W przypadku wielu systemów transportu publicznego kolor jest ważnym wskaźnikiem pociągów, autobusów lub tramwajów i nie można go zastąpić dynamicznymi kolorami podstawowymi, dodatkowymi ani trzeciorzędowymi. Skupimy się na widoku RecyclerView z kolorowymi kartami transportu publicznego.

3. Generowanie motywu
Aby utworzyć motyw Material 3, zacznij od skorzystania z naszego narzędzia Material Theme Builder. Na karcie niestandardowej możesz teraz dodać do motywu więcej kolorów. Po prawej stronie zobaczysz role kolorów i palety tonalne dla tych kolorów.
W sekcji rozszerzonych kolorów możesz usuwać kolory i zmieniać ich nazwy.

W menu eksportu pojawi się kilka możliwych opcji eksportu. W momencie pisania tego artykułu specjalne traktowanie ustawień harmonizacji w narzędziu Material Theme Builder jest dostępne tylko w przypadku widoków Androida.

Wyjaśnienie nowych wartości eksportu
Aby umożliwić Ci używanie tych kolorów i powiązanych z nimi ról kolorów w motywach niezależnie od tego, czy zdecydujesz się na harmonizację, eksportowany plik do pobrania zawiera teraz plik attrs.xml z nazwami ról kolorów dla każdego niestandardowego koloru.
<resources>
<attr name="colorCustom1" format="color" />
<attr name="colorOnCustom1" format="color" />
<attr name="colorCustom1Container" format="color" />
<attr name="colorOnCustom1Container" format="color" />
<attr name="harmonizeCustom1" format="boolean" />
<attr name="colorCustom2" format="color" />
<attr name="colorOnCustom2" format="color" />
<attr name="colorCustom2Container" format="color" />
<attr name="colorOnCustom2Container" format="color" />
<attr name="harmonizeCustom2" format="boolean" />
</resources>
W pliku themes.xml wygenerowaliśmy 4 role kolorów dla każdego koloru niestandardowego (color<name>, colorOn<name>, color<name>Container, and colorOn<nameContainer>). Właściwości harmonize<name> odzwierciedlają, czy deweloper wybrał opcję w narzędziu Material Theme Builder. Nie spowoduje to zmiany koloru w motywie podstawowym.
<resources>
<style name="AppTheme" parent="Theme.Material3.Light.NoActionBar">
<!--- Normal theme attributes ... -->
<item name="colorCustom1">#006876</item>
<item name="colorOnCustom1">#ffffff</item>
<item name="colorCustom1Container">#97f0ff</item>
<item name="colorOnCustom1Container">#001f24</item>
<item name="harmonizeCustom1">false</item>
<item name="colorCustom2">#016e00</item>
<item name="colorOnCustom2">#ffffff</item>
<item name="colorCustom2Container">#78ff57</item>
<item name="colorOnCustom2Container">#002200</item>
<item name="harmonizeCustom2">false</item>
</style>
</resources>
W pliku colors.xml określono kolory początkowe użyte do wygenerowania wymienionych powyżej ról kolorów, a także wartości logiczne określające, czy paleta kolorów zostanie przesunięta.
<resources>
<!-- other colors used in theme -->
<color name="custom1">#1AC9E0</color>
<color name="custom2">#32D312</color>
</resources>
4. Sprawdzanie koloru niestandardowego
Przybliżając panel boczny narzędzia Material Theme Builder, widzimy, że dodanie niestandardowego koloru powoduje wyświetlenie panelu z 4 głównymi rolami kolorów w jasnej i ciemnej palecie.

W widokach Androida eksportujemy te kolory, ale w tle mogą być one reprezentowane przez instancję obiektu ColorRoles.
Klasa ColorRoles ma 4 właściwości: accent, onAccent, accentContainer i onAccentContainer. Te właściwości to reprezentacja liczbowa 4 kolorów szesnastkowych.
public final class ColorRoles {
private final int accent;
private final int onAccent;
private final int accentContainer;
private final int onAccentContainer;
// truncated code
}
Możesz pobrać 4 główne role kolorów z dowolnego koloru w czasie działania programu za pomocą funkcji getColorRoles w klasie MaterialColors o nazwie getColorRoles, która umożliwia utworzenie tego zestawu 4 ról kolorów w czasie działania programu na podstawie określonego koloru podstawowego.
public static ColorRoles getColorRoles(
@NonNull Context context,
@ColorInt int color
) { /* implementation */ }
Podobnie wartości wyjściowe są rzeczywistymi wartościami kolorów, a NIE wskaźnikami do nich**.
5. Co to jest harmonizacja kolorów?
Nowy system kolorów Material jest oparty na algorytmach, które generują kolory podstawowe, dodatkowe, trzeciorzędne i neutralne na podstawie podanego koloru początkowego. Podczas rozmów z partnerami wewnętrznymi i zewnętrznymi często pojawiała się kwestia tego, jak korzystać z dynamicznych kolorów, zachowując jednocześnie kontrolę nad niektórymi z nich.
Kolory te często mają w aplikacji określone znaczenie lub kontekst, które zostałyby utracone, gdyby zastąpiono je losowym kolorem. W przeciwnym razie mogą wyglądać nieestetycznie lub nie pasować do reszty elementów.
Kolor w Material You jest opisywany za pomocą barwy, chromatyczności i tonu. Odcień koloru jest związany z tym, jak postrzegamy go jako element jednego zakresu kolorów w porównaniu z innym. Odcień określa, jak jasny lub ciemny jest kolor, a chroma to jego intensywność. Na postrzeganie odcienia mogą wpływać czynniki kulturowe i językowe, np. często wspominany brak słowa „niebieski” w starożytnych kulturach, w których kolor ten był postrzegany jako należący do tej samej rodziny co zielony.
Określony odcień może być uważany za ciepły lub chłodny w zależności od tego, gdzie znajduje się w spektrum odcieni. Przesunięcie w stronę czerwieni, pomarańczu lub żółci jest zwykle uważane za ocieplenie, a w stronę niebieskiego, zielonego lub fioletowego – za ochłodzenie. Nawet w przypadku ciepłych lub chłodnych kolorów możesz mieć ciepłe i chłodne odcienie. Poniżej „cieplejszy” odcień żółtego jest bardziej pomarańczowy, a „chłodniejszy” odcień żółtego jest bardziej zielony. 
Algorytm harmonizacji kolorów analizuje odcień nieprzesuniętego koloru i koloru, z którym ma być zharmonizowany, aby znaleźć odcień, który jest harmonijny, ale nie zmienia podstawowych właściwości koloru. Na pierwszym obrazie na spektrum zaznaczono mniej harmonijne odcienie zieleni, żółci i pomarańczu. Na kolejnej grafice kolory zielony i pomarańczowy zostały zharmonizowane z odcieniem żółtym. Nowy zielony jest cieplejszy, a nowy pomarańczowy – chłodniejszy.
Odcień pomarańczowego i zielonego uległ zmianie, ale nadal można je postrzegać jako pomarańczowy i zielony.

Jeśli chcesz dowiedzieć się więcej o niektórych decyzjach projektowych, eksploracjach i rozważaniach, przeczytaj posta na blogu, który napisali moi koledzy Ayan Daniels i Andrew Lu. Jest on bardziej szczegółowy niż ta sekcja.
6. Ręczne harmonizowanie koloru
Aby zharmonizować pojedynczy ton, w przestrzeniach MaterialColors, harmonize i harmonizeWithPrimary dostępne są 2 funkcje.
harmonizeWithPrimary używa Context jako sposobu dostępu do bieżącego motywu, a następnie do jego koloru podstawowego.
@ColorInt
public static int harmonizeWithPrimary(@NonNull Context context, @ColorInt int colorToHarmonize) {
return harmonize(
colorToHarmonize,
getColor(context, R.attr.colorPrimary, MaterialColors.class.getCanonicalName()));
}
@ColorInt
public static int harmonize(@ColorInt int colorToHarmonize, @ColorInt int colorToHarmonizeWith) {
return Blend.harmonize(colorToHarmonize, colorToHarmonizeWith);
}
Aby uzyskać zestaw 4 dźwięków, musimy wykonać jeszcze kilka czynności.
Mając już kolor źródłowy, musimy:
- określić, czy należy je zharmonizować;
- sprawdzić, czy korzystamy z trybu ciemnego,
- zwracać zharmonizowany lub niezharmonizowany obiekt
ColorRoles.
Określanie, czy należy harmonizować
W wyeksportowanym motywie z Kreatora motywów Material uwzględniliśmy atrybuty logiczne, używając nazewnictwa harmonize<Color>. Poniżej znajdziesz wygodną funkcję, która umożliwia dostęp do tej wartości.
Jeśli zostanie znaleziony, zwraca jego wartość. W przeciwnym razie stwierdza, że nie należy harmonizować koloru.
// Looks for associated harmonization attribute based on the color id
// custom1 ===> harmonizeCustom1
fun shouldHarmonize(context: Context, colorId: Int): Boolean {
val root = context.resources.getResourceEntryName(colorId)
val harmonizedId = "harmonize" + root.replaceFirstChar { it.uppercaseChar() }
val identifier = context.resources.getIdentifier(
harmonizedId, "bool", context.packageName)
return if (identifier != 0) context.resources.getBoolean(identifier) else false
}
Tworzenie zharmonizowanego obiektu ColorRoles
retrieveHarmonizedColorRoles to kolejna funkcja pomocnicza, która łączy wszystkie wymienione wyżej kroki: pobieranie wartości koloru dla nazwanego zasobu, próba rozwiązania atrybutu logicznego w celu określenia harmonizacji i zwracanie obiektu ColorRoles pochodzącego z oryginalnego lub zmieszanego koloru (w zależności od jasnego lub ciemnego schematu).
fun retrieveHarmonizedColorRoles(
view: View,
customId: Int,
isLight: Boolean
): ColorRoles {
val context = view.context
val custom = context.getColor(customId);
val shouldHarmonize = shouldHarmonize(context, customId)
if (shouldHarmonize) {
val blended = MaterialColors.harmonizeWithPrimary(context, custom)
return MaterialColors.getColorRoles(blended, isLight)
} else return MaterialColors.getColorRoles(custom, isLight)
}
7. Dodawanie kart transportu publicznego
Jak już wspomnieliśmy, do wypełniania i kolorowania kolekcji kart transportu publicznego użyjemy elementu RecyclerView i adaptera.

Przechowywanie danych o transporcie publicznym
Do przechowywania danych tekstowych i informacji o kolorach kart transportu publicznego używamy klasy danych, która przechowuje nazwę, miejsce docelowe i identyfikator zasobu koloru.
data class TransitInfo(val name: String, val destination: String, val colorId: Int)
/* truncated code */
val transitItems = listOf(
TransitInfo("53", "Irvine", R.color.custom1),
TransitInfo("153", "Brea", R.color.custom1),
TransitInfo("Orange County Line", "Oceanside", R.color.custom2),
TransitInfo("Pacific Surfliner", "San Diego", R.color.custom2)
)
Użyjemy tego koloru, aby w czasie rzeczywistym wygenerować potrzebne odcienie.
Możesz to zrobić w czasie działania programu za pomocą tej funkcji onBindViewHolder.
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val transitInfo = list.get(position)
val color = transitInfo.colorId
if (!colorRolesMap.containsKey(color)) {
val roles = retrieveHarmonizedColorRoles(
holder.itemView, color,
!isNightMode(holder.itemView.context)
)
colorRolesMap.put(color, roles)
}
val card = holder.card
holder.transitName.text = transitInfo.name
holder.transitDestination.text = transitInfo.destination
val colorRoles = colorRolesMap.get(color)
if (colorRoles != null) {
holder.card.setCardBackgroundColor(colorRoles.accentContainer)
holder.transitName.setTextColor(colorRoles.onAccentContainer)
holder.transitDestination.setTextColor(colorRoles.onAccentContainer)
}
}
8. Automatyczne harmonizowanie kolorów
Zamiast ręcznie przeprowadzać harmonizację, możesz zlecić to zadanie. HarmonizedColorOptions to klasa narzędzi, która pozwala określić większość tego, co do tej pory robiliśmy ręcznie.
Po pobraniu bieżącego kontekstu, aby mieć dostęp do bieżącego schematu dynamicznego, musisz określić kolory podstawowe, które chcesz zharmonizować, i utworzyć nowy kontekst na podstawie obiektu HarmonizedColorOptions i kontekstu z włączonymi kolorami dynamicznymi.
Jeśli nie chcesz harmonizować koloru, po prostu nie uwzględniaj go w harmonizedOptions.
val newContext = DynamicColors.wrapContextIfAvailable(requireContext())
val harmonizedOptions = HarmonizedColorsOptions.Builder()
.setColorResourceIds(intArrayOf(R.color.custom1, R.color.custom2))
.build();
harmonizedContext =
HarmonizedColors.wrapContextIfAvailable(dynamicColorsContext, harmonizedOptions)
Po ujednoliceniu koloru podstawowego możesz zaktualizować metodę onBindViewHolder, aby po prostu wywoływała funkcję MaterialColors.getColorRoles i określała, czy zwrócone role mają być jasne czy ciemne.
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
/*...*/
val color = transitInfo.colorId
if (!colorRolesMap.containsKey(color)) {
val roles = MaterialColors.getColorRoles(context.getColor(color), !isNightMode(context))
)
colorRolesMap.put(color, roles)
}
val card = holder.card
holder.transitName.text = transitInfo.name
holder.transitDestination.text = transitInfo.destination
val colorRoles = colorRolesMap.get(color)
if (colorRoles != null) {
holder.card.setCardBackgroundColor(colorRoles.accentContainer)
holder.transitName.setTextColor(colorRoles.onAccentContainer)
holder.transitDestination.setTextColor(colorRoles.onAccentContainer)
}
}
9. Automatyczne ujednolicanie atrybutów motywu
Metody przedstawione do tej pory polegają na pobieraniu ról kolorów z pojedynczego koloru. To świetnie pokazuje, że generowany jest prawdziwy ton, ale nie jest realistyczne w przypadku większości istniejących aplikacji. Prawdopodobnie nie będziesz bezpośrednio określać koloru, ale użyjesz istniejącego atrybutu motywu.
Wcześniej w tym laboratorium kodowania mówiliśmy o eksportowaniu atrybutów motywu.
<resources>
<style name="AppTheme" parent="Theme.Material3.Light.NoActionBar">
<!--- Normal theme attributes ... -->
<item name="colorCustom1">#006876</item>
<item name="colorOnCustom1">#ffffff</item>
<item name="colorCustom1Container">#97f0ff</item>
<item name="colorOnCustom1Container">#001f24</item>
<item name="harmonizeCustom1">false</item>
<item name="colorCustom2">#016e00</item>
<item name="colorOnCustom2">#ffffff</item>
<item name="colorCustom2Container">#78ff57</item>
<item name="colorOnCustom2Container">#002200</item>
<item name="harmonizeCustom2">false</item>
</style>
</resources>
Podobnie jak w przypadku pierwszej metody automatycznej możemy podać wartości w przypadku HarmonizedColorOptions i użyć HarmonizedColors, aby pobrać kontekst ze zharmonizowanymi kolorami. Istnieje jedna kluczowa różnica między tymi dwiema metodami. Musimy też udostępnić nakładkę motywu zawierającą pola, które mają być zharmonizowane.
val dynamicColorsContext = DynamicColors.wrapContextIfAvailable(requireContext())
// Harmonizing individual attributes
val harmonizedColorAttributes = HarmonizedColorAttributes.create(
intArrayOf(
R.attr.colorCustom1,
R.attr.colorOnCustom1,
R.attr.colorCustom1Container,
R.attr.colorOnCustom1Container,
R.attr.colorCustom2,
R.attr.colorOnCustom2,
R.attr.colorCustom2Container,
R.attr.colorOnCustom2Container
), R.style.AppTheme_Overlay
)
val harmonizedOptions =
HarmonizedColorsOptions.Builder().setColorAttributes(harmonizedColorAttributes).build()
val harmonizedContext =
HarmonizedColors.wrapContextIfAvailable(dynamicColorsContext, harmonizedOptions)
Twój łącznik będzie używać zharmonizowanego kontekstu. Wartości w nakładce motywu powinny odnosić się do niezharmonizowanej jasnej lub ciemnej wersji.
<style name="AppTheme.Overlay" parent="AppTheme">
<item name="colorCustom1">@color/harmonized_colorCustom1</item>
<item name="colorOnCustom1">@color/harmonized_colorOnCustom1</item>
<item name="colorCustom1Container">@color/harmonized_colorCustom1Container</item>
<item name="colorOnCustom1Container">@color/harmonized_colorOnCustom1Container</item>
<item name="colorCustom2">@color/harmonized_colorCustom2</item>
<item name="colorOnCustom2">@color/harmonized_colorOnCustom2</item>
<item name="colorCustom2Container">@color/harmonized_colorCustom2Container</item>
<item name="colorOnCustom2Container">@color/harmonized_colorOnCustom2Container</item>
</style>
W pliku układu XML możemy używać tych zharmonizowanych atrybutów w normalny sposób.
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
style="?attr/materialCardViewFilledStyle"
android:id="@+id/card"
android:layout_width="80dp"
android:layout_height="100dp"
android:layout_marginStart="8dp"
app:cardBackgroundColor="?attr/colorCustom1Container"
>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="8dp">
<TextView
android:id="@+id/transitName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="28sp"
android:textStyle="bold"
android:textColor="?attr/colorOnCustom1Container"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/transitDestination"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:textColor="?attr/colorOnCustom1Container"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
10. Kod źródłowy
package com.example.voyagi.harmonization.ui.dashboard
import android.content.Context
import android.content.res.Configuration
import android.graphics.Typeface
import android.os.Bundle
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.voyagi.harmonization.R
import com.example.voyagi.harmonization.databinding.FragmentDashboardBinding
import com.example.voyagi.harmonization.ui.home.TransitCardAdapter
import com.example.voyagi.harmonization.ui.home.TransitInfo
import com.google.android.material.card.MaterialCardView
import com.google.android.material.color.ColorRoles
import com.google.android.material.color.DynamicColors
import com.google.android.material.color.HarmonizedColorAttributes
import com.google.android.material.color.HarmonizedColors
import com.google.android.material.color.HarmonizedColorsOptions
import com.google.android.material.color.MaterialColors
class DashboardFragment : Fragment() {
enum class TransitMode { BUS, TRAIN }
data class TransitInfo2(val name: String, val destination: String, val mode: TransitMode)
private lateinit var dashboardViewModel: DashboardViewModel
private var _binding: FragmentDashboardBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
dashboardViewModel =
ViewModelProvider(this).get(DashboardViewModel::class.java)
_binding = FragmentDashboardBinding.inflate(inflater, container, false)
val root: View = binding.root
val recyclerView = binding.recyclerView
val transitItems = listOf(
TransitInfo2("53", "Irvine", TransitMode.BUS),
TransitInfo2("153", "Brea", TransitMode.BUS),
TransitInfo2("Orange County Line", "Oceanside", TransitMode.TRAIN),
TransitInfo2("Pacific Surfliner", "San Diego", TransitMode.TRAIN)
)
val dynamicColorsContext = DynamicColors.wrapContextIfAvailable(requireContext())
// Harmonizing individual attributes
val harmonizedColorAttributes = HarmonizedColorAttributes.create(
intArrayOf(
R.attr.colorCustom1,
R.attr.colorOnCustom1,
R.attr.colorCustom1Container,
R.attr.colorOnCustom1Container,
R.attr.colorCustom2,
R.attr.colorOnCustom2,
R.attr.colorCustom2Container,
R.attr.colorOnCustom2Container
), R.style.AppTheme_Overlay
)
val harmonizedOptions =
HarmonizedColorsOptions.Builder().setColorAttributes(harmonizedColorAttributes).build()
val harmonizedContext =
HarmonizedColors.wrapContextIfAvailable(dynamicColorsContext, harmonizedOptions)
val adapter = TransitCardAdapterAttr(transitItems, harmonizedContext)
recyclerView.adapter = adapter
recyclerView.layoutManager =
LinearLayoutManager(harmonizedContext, RecyclerView.HORIZONTAL, false)
return root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
class TransitCardAdapterAttr(val list: List<DashboardFragment.TransitInfo2>, context: Context) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
val colorRolesMap = mutableMapOf<Int, ColorRoles>()
private var harmonizedContext: Context? = context
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): RecyclerView.ViewHolder {
return if (viewType == DashboardFragment.TransitMode.BUS.ordinal) {
BusViewHolder(LayoutInflater.from(harmonizedContext).inflate(R.layout.transit_item_bus, parent, false))
} else TrainViewHolder(LayoutInflater.from(harmonizedContext).inflate(R.layout.transit_item_train, parent, false))
}
override fun getItemCount(): Int {
return list.size
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val item = list[position]
if (item.mode.ordinal == DashboardFragment.TransitMode.BUS.ordinal) {
(holder as BusViewHolder).bind(item)
(holder as TransitBindable).adjustNameLength()
} else {
(holder as TrainViewHolder).bind(item)
(holder as TransitBindable).adjustNameLength()
}
}
override fun getItemViewType(position: Int): Int {
return list[position].mode.ordinal
}
interface TransitBindable {
val card: MaterialCardView
var transitName: TextView
var transitDestination: TextView
fun bind(item: DashboardFragment.TransitInfo2) {
transitName.text = item.name
transitDestination.text = item.destination
}
fun Float.toDp(context: Context) =
TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
this,
context.resources.displayMetrics
)
fun adjustNameLength(){
if (transitName.length() > 4) {
val layoutParams = card.layoutParams
layoutParams.width = 100f.toDp(card.context).toInt()
card.layoutParams = layoutParams
transitName.setTypeface(Typeface.DEFAULT_BOLD);
transitName.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16.0f)
}
}
}
inner class BusViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), TransitBindable {
override val card: MaterialCardView = itemView.findViewById(R.id.card)
override var transitName: TextView = itemView.findViewById(R.id.transitName)
override var transitDestination: TextView = itemView.findViewById(R.id.transitDestination)
}
inner class TrainViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), TransitBindable {
override val card: MaterialCardView = itemView.findViewById(R.id.card)
override var transitName: TextView = itemView.findViewById(R.id.transitName)
override var transitDestination: TextView = itemView.findViewById(R.id.transitDestination)
}
}
11. Przykładowe interfejsy
Domyślne motywy i kolory niestandardowe bez harmonizacji

Harmonijne kolory niestandardowe


12. Podsumowanie
Z tego laboratorium dowiedziałeś(-aś) się:
- Podstawowe informacje o naszym algorytmie harmonizacji kolorów
- Jak generować role kolorów na podstawie danego koloru.
- Jak selektywnie harmonizować kolor w interfejsie użytkownika.
- Jak zharmonizować zestaw atrybutów w motywie.
Jeśli masz pytania, możesz je zadać w każdej chwili na @MaterialDesign na Twitterze.
Więcej treści i samouczków dotyczących projektowania znajdziesz na youtube.com/MaterialDesign