Grundlegende Farbharmonisierung in Android-Ansichten

1. Hinweis

In diesem Codelab erfahren Sie, wie Sie Ihre benutzerdefinierten Farben mit denen abstimmen, die von einem dynamischen Design generiert wurden.

Vorbereitung

Entwickelnde sollten

  • Machen Sie sich mit den grundlegenden Themenkonzepten in Android vertraut.
  • Praktisches Arbeiten mit Android-Widget-Ansichten und ihren Eigenschaften

Lerninhalte

  • So verwenden Sie die Farbharmonisierung in Ihrer Anwendung mithilfe mehrerer Methoden
  • Wie Harmonisierung funktioniert und wie sie die Farbe ändert

Voraussetzungen

  • Einen Computer, auf dem Android installiert ist

2. App-Übersicht

Voyaĝi ist eine Verkehrsanwendung, die bereits ein dynamisches Design verwendet. Bei vielen Verkehrsnetzen sind Farben ein wichtiger Indikator für Züge, Busse und Straßenbahnen und können nicht durch die verfügbaren dynamischen Primär-, Sekundär- oder Tertiärfarben ersetzt werden. Wir konzentrieren uns dabei auf RecyclerView für farbige Fahrkarten.

62ff4b2fb6c9e14a.png

3. Design erstellen

Wir empfehlen, als Erstes unser Tool Material Theme Builder zu verwenden, um ein Material3-Design zu erstellen. Auf dem Tab Benutzerdefiniert können Sie Ihrem Design jetzt weitere Farben hinzufügen. Rechts sehen Sie die Farbrollen und Tonpaletten für diese Farben.

Im erweiterten Farbbereich können Sie Farben entfernen oder umbenennen.

20cc2cf72efef213.png

Das Exportmenü enthält eine Reihe von möglichen Exportoptionen. Zum Zeitpunkt der Entstehung dieses Artikels war die spezielle Handhabung der Harmonisierungseinstellungen von Material Theme Builder nur in Android Views verfügbar.

6c962ad528c09b4.png

Informationen zu den neuen Exportwerten

Damit Sie diese Farben und die zugehörigen Farbrollen in Ihren Designs verwenden können, unabhängig davon, ob Sie sie harmonisieren möchten oder nicht, enthält der exportierte Download jetzt eine attrs.xml-Datei mit den Farbrollennamen für jede benutzerdefinierte Farbe.

<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 Themen.xml haben wir die vier Farbrollen für jede benutzerdefinierte Farbe (color<name>, colorOn<name>, color<name>Container, and colorOn<nameContainer>) generiert. harmonize<name>-Eigenschaften geben an, ob der Entwickler die Option im Material Theme Builder ausgewählt hat. Die Farbe im Hauptthema wird dadurch nicht verändert.

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

In der Datei colors.xml werden die Startfarben, die zum Generieren der oben aufgeführten Farbrollen verwendet werden, zusammen mit booleschen Werten angegeben, die festlegen, ob die Farbpalette verschoben wird oder nicht.

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

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

4. Benutzerdefinierte Farbe wird untersucht

Wenn wir in die Seitenleiste des Material Theme Builder hineinzoomen, sehen wir, dass durch das Hinzufügen einer benutzerdefinierten Farbe ein Steuerfeld mit den vier Hauptfarbrollen in einer hellen und einer dunklen Palette angezeigt wird.

c6ee942b2b93cd92.png

In Android Views exportieren wir diese Farben für Sie. Im Hintergrund können sie jedoch durch eine Instanz des ColorRoles-Objekts dargestellt werden.

Die ColorRoles-Klasse hat vier Attribute: accent, onAccent, accentContainer und onAccentContainer. Diese Eigenschaften sind die Ganzzahldarstellung der vier hexadezimalen Farben.

public final class ColorRoles {

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

  // truncated code

}

Sie können zur Laufzeit die vier Hauptfarbrollen aus einer beliebigen Farbe abrufen. Verwenden Sie dazu getColorRoles in der MaterialColors-Klasse namens getColorRoles. Damit können Sie diesen Satz von vier Farbrollen zur Laufzeit für eine bestimmte Startfarbe erstellen.

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

Ebenso sind die Ausgabewerte die tatsächlichen Farbwerte, NICHT auf sie verweisen.**

5. Was ist Farbharmonisierung?

Das neue Farbsystem von Material basiert auf einem Algorithmus und generiert primäre, sekundäre, tertiäre und neutrale Farben aus einer bestimmten Samenfarbe. Ein Punkt, den wir in Gesprächen mit internen und externen Partnern häufig geäußert haben, war, wie wir dynamische Farben einsetzen und gleichzeitig die Kontrolle über bestimmte Farben behalten sollten.

Diese Farben haben in der Anwendung häufig eine bestimmte Bedeutung oder einen bestimmten Kontext, der verloren gehen würde, wenn sie durch eine zufällige Farbe ersetzt werden würden. Wenn Sie sie unverändert lassen, können diese Farben aber auch auffällig oder deplatziert wirken.

Farbe in Material You wird durch Farbton, Chroma und Ton beschrieben. Der Farbton bezieht sich auf die Wahrnehmung einer Farbe als Element eines Farbbereichs gegenüber einem anderen. Der Ton beschreibt, wie hell oder dunkel er erscheint, und Chroma entspricht der Intensität der Farbe. Die Wahrnehmung des Farbtons kann durch kulturelle und linguistische Faktoren beeinflusst werden, wie das häufig erwähnte fehlende Wort für Blau in alten Kulturen, wobei es stattdessen in derselben Familie wie Grün gesehen wird.

57c46d9974c52e4a.pngEin bestimmter Farbton kann je nachdem, wo er innerhalb des Farbspektrums liegt, als warm oder kalt eingestuft werden. Eine Verschiebung in Richtung eines roten, orangefarbenen oder gelben Farbtons wird im Allgemeinen als wärmer zu betrachten, in Richtung eines blauen, grün oder lilafarbenen Farbtons, um ihn kühler zu machen. Auch in warmen oder kühlen Farben gibt es warme und kühle Töne. Darunter ist der „wärmere“ Gelb ist eher orange gefärbt, während das "kühlere" Gelb wird eher von Grün beeinflusst. 597c6428ff6b9669.png

Der Farbharmonisierungsalgorithmus untersucht den Farbton der nicht verschobenen Farbe und die Farbe, mit der sie harmonisiert werden sollte, um einen harmonischen Farbton zu finden, der die zugrunde liegenden Farbqualitäten nicht ändert. In der ersten Grafik werden in einem Spektrum weniger harmonische Grün-, Gelb- und Orangetöne dargestellt. In der nächsten Grafik wurden Grün und Orange mit dem gelben Farbton harmoniert. Das neue Grün ist warmer und das neue Orange kühler.

Der Farbton hat sich nach Orange und Grün verschoben, kann aber immer noch als Orange und Grün wahrgenommen werden.

766516c321348a7c.png

Wenn Sie mehr über einige Designentscheidungen, explorative Datenanalysen und Überlegungen erfahren möchten, haben meine Kollegen Ayan Daniels und Andrew Lu einen Blogpost verfasst, der etwas ausführlicher geht.

6. Manuelle Harmonisierung einer Farbe

Um einen einzelnen Ton zu harmonisieren, gibt es in MaterialColors, harmonize und harmonizeWithPrimary zwei Funktionen.

harmonizeWithPrimary verwendet Context, um auf das aktuelle Design und anschließend seine Hauptfarbe zuzugreifen.

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

Um den Satz aus vier Tönen abzurufen, müssen wir ein wenig mehr machen.

Da die Quellfarbe bereits bekannt ist, müssen wir:

  1. ob es harmonisiert werden sollte,
  2. ob wir uns im dunklen Modus befinden
  3. gibt entweder ein harmonisiertes oder nicht harmonisiertes ColorRoles-Objekt zurück.

Entscheiden, ob eine Harmonisierung durchgeführt werden soll

In das aus Material Theme Builder exportierte Design haben wir boolesche Attribute mit der Nomenklatur harmonize<Color> eingefügt. Unten sehen Sie eine praktische Funktion, mit der Sie auf diesen Wert zugreifen können.

Wenn der Schlüssel gefunden wird, wird der zugehörige Wert zurückgegeben. sonst wird die Farbe nicht harmonisiert.

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

Harmonisiertes ColorRoles-Objekt erstellen

retrieveHarmonizedColorRoles ist eine weitere praktische Funktion, die alle oben genannten Schritte kombiniert: Das Abrufen des Farbwerts für eine benannte Ressource, der Versuch, ein boolesches Attribut aufzulösen, um die Harmonisierung zu bestimmen, und ein ColorRoles-Objekt, das von der ursprünglichen oder der gemischten Farbe (bei einem hellen oder dunklen Schema) abgeleitet ist.

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. Fahrkarten werden ausgefüllt

Wie bereits erwähnt, werden wir eine RecyclerView und einen Adapter verwenden, um die Sammlung von Fahrkarten zu füllen und einzufärben.

e4555089b065b5a7.png

Daten zu öffentlichen Verkehrsmitteln speichern

Zum Speichern der Textdaten und Farbinformationen für die Fahrkarten verwenden wir eine Datenklasse, die den Namen, das Ziel und die Farbressourcen-ID speichert.

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

Mit dieser Farbe erzeugen wir die benötigten Töne in Echtzeit.

Mit der folgenden onBindViewHolder-Funktion können Sie zur Laufzeit harmonisieren.

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. Automatische Farbanpassung

Anstatt die Harmonisierung manuell durchzuführen, können Sie sie auch für Sie übernehmen. HarmonizedColorOptions ist eine Builder-Klasse, mit der Sie einen Großteil der bisher manuellen Aktionen angeben können.

Nachdem Sie den aktuellen Kontext abgerufen haben, damit Sie Zugriff auf das aktuelle dynamische Schema haben, müssen Sie die Grundfarben angeben, die Sie harmonisieren möchten, und einen neuen Kontext erstellen, der auf dem HarmonizedColorOptions-Objekt und dem für DynamicColors aktivierten Kontext basiert.

Wenn Sie eine Farbe nicht harmonisieren möchten, nehmen Sie sie einfach nicht in harmonizedOptions auf.

val newContext = DynamicColors.wrapContextIfAvailable(requireContext())


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

harmonizedContext =
 HarmonizedColors.wrapContextIfAvailable(dynamicColorsContext, harmonizedOptions)

Wenn die harmonisierte Grundfarbe bereits verarbeitet wurde, können Sie den onBindViewHolder aktualisieren, um einfach MaterialColors.getColorRoles aufzurufen und anzugeben, ob die zurückgegebenen Rollen hell oder dunkel sein sollen.

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. Automatische Harmonisierung von Designattributen

Die bisher gezeigten Methoden basieren auf dem Abrufen der Farbrollen aus einer einzelnen Farbe. Das ist gut, um zu zeigen, dass ein echter Ton erzeugt wird, der für die meisten vorhandenen Anwendungen jedoch nicht realistisch ist. Sie werden eine Farbe wahrscheinlich nicht direkt ableiten, sondern mithilfe eines vorhandenen Designattributs.

In diesem Codelab haben wir bereits über das Exportieren von Themenattributen gesprochen.

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

Ähnlich wie bei der ersten automatischen Methode können wir Werte für HarmonizedColorOptions bereitstellen und HarmonizedColors verwenden, um einen Kontext mit den harmonisierten Farben abzurufen. Es gibt einen wesentlichen Unterschied zwischen den beiden Methoden. Zusätzlich muss ein Design-Overlay mit den Feldern bereitgestellt werden, die harmonisiert werden sollen.

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)

Ihr Adapter würde den harmonisierten Kontext verwenden. Die Werte im Design-Overlay sollten sich auf die nicht harmonisierte helle oder dunkle Variante beziehen.

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

Innerhalb der XML-Layoutdatei können wir diese harmonisierten Attribute wie gewohnt verwenden.

<?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. Quellcode

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. Beispiel-UIs

Standarddesigns und benutzerdefinierte Farben ohne Harmonisierung

a5a02a72aef30529.png

Harmonisierte benutzerdefinierte Farben

4ac88011173d6753.png d5084780d2c6b886.png

dd0c8b90eccd8bef.png c51f8a677b22cd54.png

12. Zusammenfassung

In diesem Codelab haben Sie Folgendes gelernt:

  • Die Grundlagen unseres Farbharmonisierungsalgorithmus
  • So generieren Sie Farbrollen aus einer bestimmten sichtbaren Farbe.
  • Wie Sie eine Farbe in einer Benutzeroberfläche selektiv harmonisieren.
  • So harmonisieren Sie mehrere Attribute in einem Thema.

Wenn Sie Fragen haben, können Sie sich jederzeit unter @MaterialDesign auf Twitter an uns wenden.

Weitere Inhalte und Anleitungen zum Thema Design findest du unter youtube.com/MaterialDesign.