1. Zanim zaczniesz
Z tego ćwiczenia w Codelabs dowiesz się, jak harmonizować kolory niestandardowe z kolorami wygenerowanymi przez motyw dynamiczny.
Wymagania wstępne
Programiści powinni
- Zapoznanie się z podstawowymi pojęciami dotyczącymi motywów na Androidzie
- Wygoda pracy z widokami widżetów na Androida i ich właściwościami
Czego się nauczysz
- Jak stosować harmonizację kolorów w aplikacji za pomocą wielu metod
- Jak działa harmonizacja i jak zmienia ona kolory
Czego potrzebujesz
- Komputer z zainstalowanym Androidem.
2. Aplikacje ogółem
Voyaĝi to aplikacja transportu publicznego, która korzysta już z motywu dynamicznego. W wielu systemach transportu publicznego kolor jest ważnym wskaźnikiem, na który znajdują się pociągi, autobusy czy tramwaje, i nie można ich zastąpić żadnymi dynamicznymi, dodatkowymi ani trzeciorzędnymi kolorami. Skoncentrujemy się na funkcji RecyclerView z kolorowymi kartami transportu publicznego.
3. Generowanie motywu
Zalecamy, aby w pierwszej kolejności użyć naszego narzędzia Material Theme Builder. Na karcie niestandardowej możesz teraz dodać do motywu więcej kolorów. Po prawej stronie zostaną wyświetlone role i palety kolorów dla tych kolorów.
W rozszerzonej sekcji kolorów możesz usuwać kolory lub zmieniać ich nazwy.
W menu eksportowania będzie dostępnych kilka opcji eksportowania. W momencie pisania tego tekstu specjalna obsługa ustawień harmonizacji w Material Theme Builder jest dostępna tylko w widokach Androida
Informacje o nowych wartościach eksportu
Aby umożliwić korzystanie z tych kolorów i powiązanych z nimi ról kolorów w motywach niezależnie od ich harmonizacji, wyeksportowany plik do pobrania zawiera teraz plik attrs.xml zawierający nazwy ról kolorów dla każdego koloru niestandardowego.
<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 projects.xml wygenerowano 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>
wskazują, czy deweloper wybrał odpowiednią opcję w Material Theme Builder. Nie spowoduje to zmiany koloru w głównym motywie.
<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
kolory podstawowe używane do generowania ról kolorów wymienionych powyżej są określone razem z wartościami logicznymi, które określają, 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. Badanie koloru niestandardowego
Powiększając panel boczny aplikacji Material Theme Builder, widzimy, że dodanie koloru niestandardowego 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ć reprezentowane przez wystąpienie obiektu ColorRoles
.
Klasa ColorRoles ma 4 właściwości: accent
, onAccent
, accentContainer
i onAccentContainer
. Właściwości te stanowią całkowitą reprezentację czterech kolorów w systemie szesnastkowym.
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 role kolorów kluczowych z dowolnego koloru w czasie działania za pomocą funkcji getColorRoles
w klasie MaterialColors o nazwie getColorRoles
. Pozwala to utworzyć taki zestaw 4 ról kolorów w czasie działania na podstawie określonego koloru wyjściowego.
public static ColorRoles getColorRoles(
@NonNull Context context,
@ColorInt int color
) { /* implementation */ }
Te same wartości wyjściowe to rzeczywiste wartości kolorów, a NIE wskaźniki do nich.**
5. Czym jest harmonizacja kolorów?
Nowy system kolorów w Material jest z założenia algorytmiczny, który generuje kolory podstawowe, drugo-, trzeciorzędne i neutralne na podstawie danego koloru nasion. Podczas rozmów z partnerami wewnętrznymi i zewnętrznymi często zastanawialiśmy się, jak wprowadzić dynamiczne kolory przy zachowaniu kontroli nad niektórymi kolorami.
Kolory te często mają w aplikacji określone znaczenie lub kontekst, które nie zostałyby usunięte, gdyby zostały zastąpione losowym kolorem. Z drugiej strony, pozostawione bez zmian, kolory mogą wydawać się drażniące lub nie na miejscu.
Kolory w Material You są określane za pomocą barwy, chromaty i tonu. Odcień koloru wynika z postrzegania go jako należącego do jednej palety kolorów w porównaniu z innym. Odcień określa, jak jasne lub ciemne jest światło, a kolor to intensywność koloru. Na postrzeganie barwy wpływają czynniki kulturowe i lingwistyczne, np. często wspominany brak słowa „niebieskiego” w starożytnych kulturach, dlatego ten kolor należy do tej samej rodziny co kolor zielony.
Konkretny odcień może być uznawany za ciepły lub chłodny w zależności od tego, w którym miejscu widma barw. Przejście na odcień czerwonego, pomarańczowego lub żółtego zwykle uważa się za cieplejsze, a w kierunku niebieskiego, zielonego lub fioletowego – za chłodniejsze. Nawet w ciepłych lub chłodnych odcieniach pojawiają się ciepłe i chłodne odcienie. Poniżej określenie „cieplejsze” żółty jest bardziej pomarańczowy, a „chłodniejszy” a kolor żółty – większy od koloru zielonego.
Algorytm harmonizacji kolorów analizuje odcień nieprzesuniętego koloru i kolor, z którym powinien zostać zharmonizowany, aby znaleźć odcień, która jest harmonijna, ale nie zmienia jej podstawowej jakości. Na pierwszej grafiki widać mniej harmoniczne odcienie zielonego, żółtego i pomarańczowego w spektrum. Na następnej ilustracji kolory zielony i pomarańczowy zostały zharmonizowane z żółtym. Nowy zielony jest bardziej ciepły, a nowy pomarańczowy – chłodniejszy.
Odcień pomarańczowy i zielony zmienił się, ale nadal są one postrzegane jako pomarańczowe i zielone.
Jeśli chcesz dowiedzieć się więcej o niektórych decyzjach projektowych, eksploracjach i zagadnieniach, moi współpracownicy, Ayan Daniels i Andrew Lu, opublikowali posta na blogu bardziej szczegółowo.
6. Ręczne harmonizowanie koloru
Aby harmonizować jeden ton, są dostępne 2 funkcje w funkcjach MaterialColors
, harmonize
i harmonizeWithPrimary
.
Element harmonizeWithPrimary
używa koloru Context
, aby uzyskać dostęp do bieżącego motywu, a potem do 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 pobrać zestaw 4 ton, musimy zrobić trochę więcej.
Ponieważ znamy już kolor źródła, musimy:
- określić, czy należy go zharmonizować,
- określić, czy działamy w trybie ciemnym;
- zwraca zharmonizowany lub niezharmonizowany obiekt
ColorRoles
.
Podejmowanie decyzji o zharmonizacji
W wyeksportowanym motywie z Material Theme Builder dodaliśmy atrybuty logiczne korzystające z nomenklatury harmonize<Color>
. Poniżej przedstawiamy udogodnienie pozwalające uzyskać dostęp do tej wartości.
Jeśli zostanie znaleziony, zwraca wartość. w przeciwnym razie określa, że nie powinien on 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, która łączy wszystkie wyżej wymienione kroki: pobiera wartość koloru dla nazwanego zasobu, próbuje rozpoznać atrybut logiczny w celu określenia harmonizacji i zwraca obiekt ColorRoles
wyodrębniony z koloru pierwotnego lub mieszanego (ze względu na jasny lub ciemny schemat).
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. Zapełniam karty transportu publicznego
Jak wspomnieliśmy, do wypełnienia i kolorowania kolekcji kart transportu publicznego użyjemy obiektu RecyclerView i adaptera.
Przechowywanie danych o transporcie publicznym
Do przechowywania danych tekstowych i informacji o kolorze kart transportu publicznego używamy klasy danych, w której są przechowywane nazwa, 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 wygenerować potrzebne tony w czasie rzeczywistym.
W czasie działania możesz zharmonizować tę funkcję z tą funkcją 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
Alternatywą dla ręcznej harmonizacji jest obsługa tej funkcji za Ciebie. HarmonizedColorOptions to klasa kreatora umożliwiająca ręczne określenie dużej części tego, co zrobiliśmy do tej pory.
Po pobraniu bieżącego kontekstu, aby uzyskać dostęp do bieżącego schematu dynamicznego, musisz określić kolory podstawowe, które chcesz zharmonizować, i utworzyć nowy kontekst na podstawie tego obiektu HarmonizedColorOptions i kontekstu z włączoną obsługą DynamicColors.
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 zharmonizowanym kolorze podstawowym możesz zaktualizować obiekt onBindViewHolder tak, aby wywoływał funkcję MaterialColors.getColorRoles
i określał, czy zwracane 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 harmonizowanie atrybutów motywu
Dotychczasowe metody polegają na pobieraniu ról kolorów z pojedynczego koloru. To świetny sposób na pokazanie, że generowany jest rzeczywisty ton, ale niezbyt realistyczny w większości istniejących aplikacji. Prawdopodobnie nie będziesz bezpośrednio wybierać koloru, ale raczej korzystać z istniejącego atrybutu motywu.
Wcześniej w tym ćwiczeniu w Codelabs mówiliśmy o eksportowaniu atrybutów motywów.
<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 HarmonizedColorOptions i użyć HarmonizedColors w celu pobrania kontekstu ze zharmonizowanymi kolorami. Między tymi metodami jest jedna kluczowa różnica. Musimy dodatkowo udostępnić nakładkę motywu zawierającą pola do zharmonizowanego tekstu.
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)
Twoja przejściówka będzie korzystać ze zharmonizowanego kontekstu. Wartości w nakładce z motywem powinny odnosić się do nieharmonizowanej wersji jasnej lub ciemnej.
<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 jak zwykle.
<?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 użytkownika
Domyślny motyw i kolory niestandardowe bez harmonizacji
Zharmonizowane kolory niestandardowe
12. Podsumowanie
Dzięki tym ćwiczeniom w Codelabs dowiesz się:
- Podstawy naszego algorytmu harmonizacji kolorów
- Generowanie ról kolorów na podstawie oglądanego koloru.
- Jak selektywnie zharmonizować kolor w interfejsie.
- Jak zharmonizować zestaw atrybutów w motywie.
Jeśli masz pytania, w każdej chwili możesz je nam zadać, korzystając z konta @MaterialDesign na Twitterze.
Więcej filmów i samouczków o projektowaniu znajdziesz na stronie youtube.com/MaterialDesign.