Podstawowa harmonizacja kolorów w widokach Androida

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.

62ff4b2fb6c9e14a.png

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.

20cc2cf72efef213.png

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

6c962ad528c09b4.png

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.

c6ee942b2b93cd92.png

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.

57c46d9974c52e4a.pngKonkretny 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. 597c6428ff6b9669.png

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.

766516c321348a7c.png

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:

  1. określić, czy należy go zharmonizować,
  2. określić, czy działamy w trybie ciemnym;
  3. 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.

e4555089b065b5a7.png

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

a5a02a72aef30529.png

Zharmonizowane kolory niestandardowe

4ac88011173d6753.png d5084780d2c6b886.png

dd0c8b90eccd8bef.png c51f8a677b22cd54.png

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.