Armonizzazione di base dei colori nelle viste Android

1. Prima di iniziare

In questo codelab imparerai ad armonizzare i colori personalizzati con quelli generati da un tema dinamico.

Prerequisiti

Gli sviluppatori devono:

  • Avere familiarità con i concetti di base dei temi in Android
  • Avere dimestichezza con le visualizzazioni dei widget Android e le relative proprietà

Obiettivi didattici

  • Come utilizzare l'armonizzazione dei colori nella tua applicazione utilizzando più metodi
  • Come funziona l'armonizzazione e come sposta il colore

Che cosa ti serve

  • Un computer con Android installato, se vuoi seguire i passaggi.

2. Panoramica app

Voyaĝi è un'applicazione di trasporto pubblico che utilizza già un tema dinamico. Per molti sistemi di trasporto pubblico, il colore è un indicatore importante di treni, autobus o tram e questi non possono essere sostituiti da colori primari, secondari o terziari dinamici disponibili. Ci concentreremo sul RecyclerView delle schede di trasporto pubblico colorate.

62ff4b2fb6c9e14a.png

3. Generazione di un tema

Ti consigliamo di utilizzare il nostro strumento Material Theme Builder come primo passo per creare un tema Material 3. Nella scheda personalizzata, ora puoi aggiungere altri colori al tema. A destra, verranno visualizzati i ruoli dei colori e le tavolozze tonali per questi colori.

Nella sezione dei colori estesi, puoi rimuovere o rinominare i colori.

20cc2cf72efef213.png

Il menu di esportazione mostrerà una serie di possibili opzioni di esportazione. Al momento della stesura, la gestione speciale delle impostazioni di armonizzazione di Material Theme Builder è disponibile solo in Android Views

6c962ad528c09b4.png

Informazioni sui nuovi valori di esportazione

Per consentirti di utilizzare questi colori e i relativi ruoli dei colori nei tuoi temi, indipendentemente dal fatto che tu scelga o meno di armonizzare, il download esportato ora include un file attrs.xml contenente i nomi dei ruoli dei colori per ogni colore personalizzato.

<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>

In themes.xml, abbiamo generato i quattro ruoli dei colori per ogni colore personalizzato (color<name>, colorOn<name>, color<name>Container, and colorOn<nameContainer>). Le proprietà harmonize<name> indicano se lo sviluppatore ha selezionato l'opzione in Material Theme Builder. Non sposterà il colore nel tema principale.

<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>

Nel file colors.xml, i colori di base utilizzati per generare i ruoli dei colori elencati sopra vengono specificati insieme ai valori booleani per indicare se la tavolozza dei colori verrà spostata o meno.

<resources>
   <!-- other colors used in theme -->

   <color name="custom1">#1AC9E0</color>
   <color name="custom2">#32D312</color>
</resources>

4. Esame del colore personalizzato

Se ingrandiamo il riquadro laterale di Material Theme Builder, possiamo vedere che l'aggiunta di un colore personalizzato fa apparire un riquadro con i quattro ruoli dei colori principali in una tavolozza chiara e scura.

c6ee942b2b93cd92.png

In Android Views, questi colori vengono esportati per te, ma dietro le quinte possono essere rappresentati da un'istanza dell'oggetto ColorRoles.

La classe ColorRoles ha quattro proprietà: accent, onAccent, accentContainer, e onAccentContainer. Queste proprietà sono la rappresentazione intera dei quattro colori esadecimali.

public final class ColorRoles {

  private final int accent;
  private final int onAccent;
  private final int accentContainer;
  private final int onAccentContainer;

  // truncated code

}

Puoi recuperare i quattro ruoli dei colori principali da un colore arbitrario in fase di runtime utilizzando getColorRoles nella classe MaterialColors chiamata getColorRoles, che ti consente di creare questo set di quattro ruoli dei colori in fase di runtime dato un colore di base specifico.

public static ColorRoles getColorRoles(
    @NonNull Context context,
    @ColorInt int color
) { /* implementation */ }

Allo stesso modo, i valori di output sono i valori dei colori effettivi, NON i puntatori a questi.**

5. Che cos'è l'armonizzazione dei colori?

Il nuovo sistema di colori di Material è algoritmico per progettazione e genera colori primari, secondari, terziari e neutri da un determinato colore di base. Un punto di preoccupazione che abbiamo ricevuto spesso quando abbiamo parlato con partner interni ed esterni è stato come adottare il colore dinamico mantenendo il controllo su alcuni colori.

Questi colori spesso hanno un significato o un contesto specifico nell'applicazione che andrebbe perso se venissero sostituiti da un colore casuale. In alternativa, se lasciati così come sono, questi colori potrebbero apparire visivamente stridenti o fuori luogo.

Il colore in Material You è descritto da tonalità, croma e tono. La tonalità di un colore si riferisce alla percezione di un colore come membro di una gamma di colori rispetto a un'altra. Il tono descrive quanto appare chiaro o scuro e il croma è l'intensità del colore. La percezione della tonalità può essere influenzata da fattori culturali e linguistici, come la mancanza di una parola per il blu nelle culture antiche, che invece veniva vista nella stessa famiglia del verde.

57c46d9974c52e4a.pngUna tonalità particolare può essere considerata calda o fredda a seconda della sua posizione nello spettro delle tonalità. Lo spostamento verso una tonalità rossa, arancione o gialla è generalmente considerato un aumento della temperatura, mentre verso una tonalità blu, verde o viola si dice che la temperatura diminuisce. Anche all'interno dei colori caldi o freddi, avrai toni caldi e freddi. Di seguito, il giallo "più caldo" è più arancione, mentre il giallo "più freddo" è più influenzato dal verde. 597c6428ff6b9669.png

L'algoritmo di armonizzazione dei colori esamina la tonalità del colore non spostato e il colore con cui deve essere armonizzato per individuare una tonalità armoniosa ma che non alteri le qualità del colore sottostante. Nel primo grafico, sono tracciate tonalità di verde, giallo e arancione meno armoniose su uno spettro. Nel grafico successivo, il verde e l'arancione sono stati armonizzati con la tonalità gialla. Il nuovo verde è più caldo e il nuovo arancione è più freddo.

La tonalità è cambiata sull'arancione e sul verde, ma possono comunque essere percepiti come arancione e verde.

766516c321348a7c.png

Se vuoi saperne di più su alcune delle decisioni di progettazione, esplorazioni e considerazioni, i miei colleghi Ayan Daniels e Andrew Lu hanno scritto un post del blog che approfondisce un po' di più questa sezione.

6. Armonizzazione manuale di un colore

Per armonizzare un singolo tono, esistono due funzioni in MaterialColors, harmonize e harmonizeWithPrimary.

harmonizeWithPrimary utilizza Context come mezzo per accedere al tema corrente e, di conseguenza, al colore primario.

@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);
  }

Per recuperare il set di quattro toni, dobbiamo fare un po' di più.

Dato che abbiamo già il colore di origine, dobbiamo:

  1. Determinare se deve essere armonizzato
  2. Determinare se siamo in modalità Buio e
  3. Restituire un oggetto ColorRoles armonizzato o non armonizzato.

Determinare se armonizzare

Nel tema esportato da Material Theme Builder, abbiamo incluso attributi booleani utilizzando la nomenclatura harmonize<Color>. Di seguito è riportata una funzione di convenienza per accedere a questo valore.

Se viene trovato, restituisce il relativo valore; altrimenti determina che il colore non deve essere armonizzato.

// 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
}

Creazione di un oggetto ColorRoles armonizzato

retrieveHarmonizedColorRoles è un'altra funzione di convenienza che unisce tutti i passaggi sopra menzionati: recupera il valore del colore per una risorsa denominata, tenta di risolvere un attributo booleano per determinare l'armonizzazione e restituisce un oggetto ColorRoles derivato dal colore originale o combinato (dato lo schema chiaro o scuro).

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. Popolamento delle schede di trasporto pubblico

Come accennato in precedenza, utilizzeremo un RecyclerView e un adattatore per popolare e colorare la raccolta di schede di trasporto pubblico.

e4555089b065b5a7.png

Archiviazione dei dati di trasporto pubblico

Per archiviare i dati di testo e le informazioni sui colori delle carte del trasporto pubblico, utilizziamo una classe di dati che memorizza l'ID della risorsa del nome, della destinazione e del colore.

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)
)

Utilizzeremo questo colore per generare i toni di cui abbiamo bisogno in tempo reale.

Puoi armonizzare in fase di runtime con la seguente funzione 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. Armonizzazione automatica dei colori

In alternativa alla gestione manuale dell'armonizzazione, puoi farla gestire automaticamente. HarmonizedColorOptions è una classe di creazione che ti consente di specificare gran parte di ciò che abbiamo fatto finora manualmente.

Dopo aver recuperato il contesto corrente in modo da avere accesso allo schema dinamico corrente, devi specificare i colori di base che vuoi armonizzare e creare un nuovo contesto basato sull'oggetto HarmonizedColorOptions e sul contesto in cui è abilitato DynamicColors.

Se non vuoi armonizzare un colore, non includerlo in harmonizedOptions.

val newContext = DynamicColors.wrapContextIfAvailable(requireContext())


val harmonizedOptions = HarmonizedColorsOptions.Builder()
 .setColorResourceIds(intArrayOf(R.color.custom1, R.color.custom2))
 .build();

harmonizedContext =
 HarmonizedColors.wrapContextIfAvailable(dynamicColorsContext, harmonizedOptions)

Con il colore di base armonizzato già gestito, puoi aggiornare onBindViewHolder per chiamare semplicemente MaterialColors.getColorRoles e specificare se i ruoli restituiti devono essere chiari o scuri.

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. Armonizzazione automatica degli attributi del tema

I metodi mostrati finora si basano sul recupero dei ruoli dei colori da un singolo colore. È ideale per mostrare che viene generato un tono reale, ma non è realistico per la maggior parte delle applicazioni esistenti. Probabilmente non deriverai un colore direttamente, ma utilizzerai un attributo del tema esistente.

In precedenza in questo codelab abbiamo parlato dell'esportazione degli attributi del tema.

<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>

Analogamente al primo metodo automatico, possiamo fornire valori a HarmonizedColorOptions e utilizzare HarmonizedColors per recuperare un contesto con i colori armonizzati. Esiste una differenza fondamentale tra i due metodi. Inoltre, dobbiamo fornire una sovrapposizione del tema contenente i campi da armonizzare.

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)

L'adattatore utilizzerebbe il contesto armonizzato. I valori nella sovrapposizione del tema devono fare riferimento alla variante chiara o scura non armonizzata.

<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>

All'interno del file di layout XML, possiamo utilizzare questi attributi armonizzati come di consueto.

<?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. Codice sorgente

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. Esempi di interfacce utente

Temi predefiniti e colori personalizzati senza armonizzazione

a5a02a72aef30529.png

Colori personalizzati armonizzati

4ac88011173d6753.png d5084780d2c6b886.png

dd0c8b90eccd8bef.png c51f8a677b22cd54.png

12. Riepilogo

In questo codelab hai imparato:

  • Le nozioni di base del nostro algoritmo di armonizzazione dei colori
  • Come generare i ruoli dei colori da un determinato colore visualizzato.
  • Come armonizzare selettivamente un colore in un'interfaccia utente.
  • Come armonizzare un insieme di attributi in un tema.

Se hai domande, non esitare a contattarci in qualsiasi momento utilizzando @MaterialDesign su Twitter.

Continua a seguirci per altri contenuti e tutorial di progettazione su youtube.com/MaterialDesign