הרמוניזציה בסיסית של צבעים בתצוגות של Android

1. לפני שמתחילים

ב-Codelab הזה תלמדו איך ליצור הרמוניה לצבעים המותאמים אישית שלכם עם אלה שנוצרו על ידי עיצוב דינמי.

דרישות מוקדמות

המפתחים צריכים להיות

  • הכרת המושגים הבסיסיים של נושאי עיצוב ב-Android
  • עבודה נוחה עם תצוגות בווידג'ט של Android והמאפיינים שלהן

מה תלמדו

  • איך להשתמש בהרמוניזציה של צבעים באפליקציה באמצעות מספר שיטות
  • איך ההרמוניה פועלת ואיך היא משנה את הצבעים

למה תזדקק?

  • מחשב שמותקנת בו מערכת Android, אם אתם רוצים לעקוב אחריו.

2. סקירה כללית של היישום

Voyaëi היא אפליקציית תחבורה ציבורית שכבר משתמשת בעיצוב דינמי. בהרבה מערכות תחבורה ציבורית, הצבע הוא אינדיקציה חשובה לרכבות, לאוטובוסים או לחשמליות, ואי אפשר להחליף אותם בצבעים דינמיים של ראשי, משני או שלישוני. נתמקד בעבודה שלנו ב-RecyclerView של כרטיסים צבעוניים לתחבורה ציבורית.

62ff4b2fb6c9e14a.png

3. יצירת עיצוב

בתור התחנה הראשונה ליצירת עיצוב Material3, מומלץ להשתמש בכלי שלנו, Material Design Builder. בכרטיסייה 'בהתאמה אישית', אפשר עכשיו להוסיף עוד צבעים לעיצוב. בצד שמאל יוצגו תפקידי הצבעים ולוחות הצבעים הטונלים של הצבעים האלה.

בקטע 'צבע מורחב' אפשר להסיר צבעים או לשנות את השם שלהם.

20cc2cf72efef213.png

בתפריט הייצוא יוצגו כמה אפשרויות ייצוא. נכון לזמן הכתיבה, הטיפול המיוחד של Material Design Builder בהגדרות ההרמוניה זמין רק בתצוגות ב-Android

6c962ad528c09b4.png

הסבר על ערכי הייצוא החדשים

כדי לאפשר לך להשתמש בצבעים האלה ובתפקידי הצבע המשויכים אליהם בעיצובים, גם אם בחרתם ליצור הרמוניה וגם אם לא, ההורדה המיוצאת כוללת עכשיו קובץ attrs.xml שמכיל את שמות תפקידי הצבעים של כל צבע מותאם אישית.

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

בקובץ theme.xml, יצרנו את ארבעת תפקידי הצבעים לכל צבע מותאם אישית (color<name>, colorOn<name>, color<name>Container, and colorOn<nameContainer>). המאפיינים של harmonize<name> משקפים אם המפתח בחר באפשרות הזו בכלי ליצירת עיצובים מהותיים. הצבע לא ישתנה בעיצוב המרכזי.

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

בקובץ colors.xml, צבעי המקור שמשמשים ליצירת תפקידי הצבעים שמפורטים למעלה מצוינים עם ערכים בוליאניים.

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

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

4. בדיקת צבע מותאם אישית

כשאנחנו מגדילים את החלונית הצדדית של הכלי Material Design, אפשר לראות שהוספת צבע מותאם אישית מציגה לוח עם ארבעת התפקידים המרכזיים בלוח צבעים בהיר כהים.

c6ee942b2b93cd92.png

ב-Android View, אנחנו מייצאים את הצבעים האלה עבורכם, אבל מאחורי הקלעים הם יכולים לייצג אותם על ידי מופע של האובייקט ColorRoles.

במחלקה ColorRoles יש ארבעה מאפיינים: accent, onAccent, accentContainer ו-onAccentContainer. מאפיינים אלה הם ייצוגים של מספרים שלמים של ארבעת הצבעים ההקסדצימליים.

public final class ColorRoles {

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

  // truncated code

}

אפשר לאחזר את ארבעת תפקידי צבעי המפתח מצבע שרירותי בזמן ריצה באמצעות getColorRoles במחלקה MaterialColors שנקרא getColorRoles. כך אפשר ליצור את הקבוצה של 4 תפקידי הצבע בזמן ריצה בהינתן צבע בסיס ספציפי.

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

בדומה לכך, ערכי הפלט הם ערכי הצבעים בפועל, ולא מצביעים אליהם.**

5. מהי הרמוניזציה של צבעים?

מערכת הצבעים החדשה של חומר מבוססת על אלגוריתם, ויוצרת צבעים ראשיים, משניים, שלישיים וניטרליים מצבע מקור נתון. אחד מהחששות שקיבלנו הרבה כששוחחנו עם שותפים פנימיים וחיצוניים היה השימוש בצבעים דינמיים תוך שמירה על שליטה בצבעים מסוימים.

לצבעים האלה יש בדרך כלל משמעות או הקשר מסוימים באפליקציה, שיאבדו אם הם יוחלפו בצבע אקראי. לחלופין, אם תשאירו את התמונה כפי שהיא, הצבעים האלה עלולים להיראות שונים באופן חזותי או מיותרים.

צבע ב'חומר': מתואר לפי גוון, כרומה וגוון. גוון של צבע מסוים קשור לתפיסה של אדם מסוים לגביו כשייך לטווח צבעים אחד לעומת אחר. 'טון' מתאר כמה בהיר או כהה הוא נראה ועוצמת הצבע היא 'כרומה'. גורמים תרבותיים ולשוניים יכולים להשפיע על תפיסת הגוון, למשל אם בתרבויות עתיקות אין מילה לכחול, אלא יש את אותה משפחה כירוק.

57c46d9974c52e4a.pngגוון מסוים יכול להיחשב כחם או קר, בהתאם למיקום שלו על ספקטרום הגוונים. שינוי לגוון אדום, כתום או צהוב נחשב בדרך כלל לגוון חם יותר, ונאמר שצבעו של כחול, ירוק או סגול מרענן את הגוון. גם בצבעים החמימים או הקרירים, יישמעו גוונים חמים וקרים. למטה מופיע הכיתוב "החם" צהוב הוא בגוון כתום יותר ואילו "קירור" צהוב מושפע יותר מירוק. 597c6428ff6b9669.png

האלגוריתם של תהליך הרמוניזציה של צבעים בוחן את הגוון של הצבע שלא השתנה ואת הצבע שצריך להתאים לו כדי למצוא גוון הרמוני שלא משנה את תכונות הצבע הבסיסיות שלו. בגרפיקה הראשונה מוצגים גוונים פחות הרמוניים של ירוק, צהוב וכתום על ספקטרום. בגרפיקה הבאה, הירוק והכתום הושתלבו עם הגוון הצהוב. הירוק החדש חם יותר והכתום החדש קריר יותר.

הגוון השתנה על הכתום והירוק, אבל עדיין אפשר לראות אותם ככתום וירוק.

766516c321348a7c.png

כדי לקבל מידע נוסף על חלק מההחלטות, החקירות והשיקולים בעיצוב, העמיתים שלי, איאן דניאלס ואנדרו לו, כתבו פוסט בבלוג עם קצת יותר מעמיק מהקטע הזה.

6. איזון צבעים באופן ידני

כדי ליצור הרמוניה לטון אחד, יש שתי פונקציות ב-MaterialColors, ב-harmonize וב-harmonizeWithPrimary.

ב-harmonizeWithPrimary נעשה שימוש ב-Context כאמצעי גישה לעיצוב הנוכחי, ולאחר מכן לצבע הראשי שלו.

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

כדי לאחזר את סט של ארבעת הגוונים, אנחנו צריכים לעשות קצת יותר.

מכיוון שכבר יש לנו את צבע המקור, עלינו:

  1. כדי לקבוע אם צריך לשלב ביניהם
  2. כדי לקבוע אם אנחנו במצב כהה,
  3. מחזיר אובייקט ColorRoles הרמוני או לא הרמוני.

להחליט אם ליצור הרמוניה

בעיצוב שייצא מ-Material Design Builder, כללנו מאפיינים בוליאניים באמצעות השמות harmonize<Color>. בהמשך מוצגת פונקציית נוחות לגישה לערך הזה.

אם הוא נמצא, הוא מחזיר את הערך שלו. אחרת הוא יקבע שלא צריך ליצור הרמוניה לצבע.

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

יצירת אובייקט ColorRoles מתואם

retrieveHarmonizedColorRoles היא פונקציית נוחות נוספת שמאחדת את כל השלבים שלמעלה: אחזור ערך הצבע של משאב בעל שם, מנסה לפתור מאפיין בוליאני כדי לקבוע הרמוניה, ומחזירה אובייקט ColorRoles שנגזר מהצבע המקורי או המעורב (בצבע בהיר או כהה).

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. הכרטיסים לתחבורה ציבורית בתהליך אכלוס

כפי שציינו קודם, אנחנו נשתמש ב-RecyclerView ובמתאם כדי לאכלס את האוסף של הכרטיסים לתחבורה ציבורית ולצבע אותם.

e4555089b065b5a7.png

אחסון נתוני תחבורה ציבורית

כדי לאחסן את נתוני הטקסט ואת פרטי הצבעים של הכרטיסים לתחבורה ציבורית, אנחנו משתמשים בסיווג נתונים ששומר את השם, היעד ומזהה המשאב של הצבע.

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

נשתמש בצבע הזה כדי ליצור את הגוונים הדרושים לנו בזמן אמת.

אפשר ליצור הרמוניה בזמן ריצה באמצעות הפונקציה 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. איזון צבעים באופן אוטומטי

כחלופה לטיפול בהרמוניה באופן ידני, אפשר לטפל בזה בשבילכם. HarmonizedColorOptions היא מחלקה של ה-builder שמאפשרת לציין הרבה ממה שעשינו עד עכשיו ידנית.

לאחר אחזור ההקשר הנוכחי כדי לקבל גישה לסכמה הדינמית הנוכחית, עליך לציין את צבעי הבסיס שברצונך ליצור הרמוניה וליצור הקשר חדש שמבוסס על האובייקט HarmonizedColorOptions וההקשר שמופעל על ידי DynamicColors.

אם אתם לא רוצים ליצור הרמוניה של צבע, פשוט אל תכללו אותו ב-HaronizedOptions.

val newContext = DynamicColors.wrapContextIfAvailable(requireContext())


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

harmonizedContext =
 HarmonizedColors.wrapContextIfAvailable(dynamicColorsContext, harmonizedOptions)

עם צבע הבסיס ההרמוני שכבר בוצע, אפשר לעדכן את onBindViewHolder כך שיקרא ל-MaterialColors.getColorRoles ולציין אם התפקידים המוחזרים יהיו בהירים או כהים.

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. איזון אוטומטי של מאפייני העיצוב

השיטות שהוצגו עד עכשיו מסתמכות על אחזור תפקידי הצבעים מצבע ספציפי. היא יכולה לעשות את זה כדי להוכיח שנוצר טון אמיתי, אבל הוא לא מציאותי לרוב האפליקציות הקיימות. סביר להניח שלא גזירה של צבע באופן ישיר, אלא משתמשים במאפיין קיים של עיצוב.

מוקדם יותר ב-Codelab הזה דיברנו על ייצוא מאפייני עיצוב.

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

בדומה לשיטה האוטומטית הראשונה, אנחנו יכולים לספק ערכים ל-HarmonizedColorOptions ולהשתמש ב-HarmonizedColors כדי לאחזר הקשר עם הצבעים ההרמוניים. יש הבדל עיקרי אחד בין שתי השיטות. אנחנו גם צריכים לספק שכבת-על של עיצוב שמכילה את השדות כדי ליצור הרמוניה.

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)

המתאם ישתמש בהקשר ההרמוני. הערכים בשכבת-העל של העיצוב צריכים להתייחס לווריאנט הבהיר או הכהה לא הרמוני.

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

בתוך קובץ הפריסה בפורמט XML, ניתן להשתמש במאפיינים ההרמוניים האלה כרגיל.

<?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. קוד מקור

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. ממשקי משתמש לדוגמה

עיצוב ברירת המחדל וצבעים בהתאמה אישית ללא הרמוניזציה

a5a02a72aef30529.png

צבעים משתלבים בהתאמה אישית

4ac88011173d6753.png d5084780d2c6b886.png

dd0c8b90eccd8bef.png c51f8a677b22cd54.png

12. סיכום

ב-Codelab הזה למדת:

  • העקרונות הבסיסיים של האלגוריתם להרמוניית הצבעים שלנו
  • איך יוצרים תפקידי צבע מצבע נתון שמוצג.
  • איך ליצור הרמוניה של צבע באופן סלקטיבי בממשק משתמש
  • איך ליצור הרמוניה של קבוצת מאפיינים בעיצוב

אם יש לכם שאלות, אתם מוזמנים לשאול אותנו בכל שלב באמצעות @Material Design ב-Twitter.

כדאי להמשיך להתעדכן בתכנים נוספים ובמדריכים בנושא עיצוב בכתובת youtube.com/MaterialDesign