1. לפני שמתחילים
ב-codelab הזה נסביר איך ליצור התאמה בין הצבעים המותאמים אישית לבין הצבעים שנוצרו על ידי עיצוב דינמי.
דרישות מוקדמות
מפתחים צריכים
- היכרות עם מושגי בסיס בנושא עיצובים ב-Android
- נוח לכם לעבוד עם תצוגות של ווידג'טים ל-Android והמאפיינים שלהן
מה תלמדו
- איך משתמשים בהרמוניית צבעים באפליקציה באמצעות כמה שיטות
- איך ההרמוניה פועלת ואיך היא משנה את הצבע
הדרישות
- מחשב עם מערכת Android מותקנת, אם רוצים לעקוב אחרי ההוראות.
2. סקירה כללית של היישום
Voyaĝi היא אפליקציה לתחבורה ציבורית שכבר משתמשת בעיצוב דינמי. במערכות רבות של תחבורה ציבורית, הצבע הוא אינדיקטור חשוב לרכבות, לאוטובוסים או לחשמליות, ואי אפשר להחליף אותו בצבעים דינמיים ראשיים, משניים או שלישוניים שזמינים. נתמקד בעבודה על RecyclerView של כרטיסים צבעוניים לתחבורה ציבורית.

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

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

הסבר על ערכי הייצוא החדשים
כדי לאפשר לכם להשתמש בצבעים האלה ובתפקידי הצבע המשויכים שלהם בערכות הנושא, בין אם בחרתם ליצור הרמוניה ובין אם לא, ההורדה המיוצאת כוללת עכשיו קובץ 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>
ב-themes.xml, יצרנו את ארבעת תפקידי הצבע לכל צבע מותאם אישית (color<name>, colorOn<name>, color<name>Container, and colorOn<nameContainer>). מאפייני harmonize<name> משקפים אם המפתח בחר באפשרות בכלי Material Theme Builder. הצבע לא ישתנה בעיצוב הליבה.
<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 Theme Builder, נוכל לראות שכשמוסיפים צבע בהתאמה אישית, מוצגת חלונית עם ארבעת תפקידי הצבע העיקריים בפלטת צבעים בהירה וכהה.

ב-Android Views, אנחנו מייצאים את הצבעים האלה בשבילכם, אבל מאחורי הקלעים הם יכולים להיות מיוצגים על ידי מופע של אובייקט 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. המחלקה הזו מאפשרת ליצור את קבוצת ארבעת תפקידי הצבעים בזמן ריצה בהינתן צבע בסיס ספציפי.
public static ColorRoles getColorRoles(
@NonNull Context context,
@ColorInt int color
) { /* implementation */ }
באופן דומה, ערכי הפלט הם ערכי הצבע בפועל, ולא מצביעים עליהם.**
5. מהי התאמת צבעים?
מערכת הצבעים החדשה של Material מבוססת על אלגוריתמים, והיא יוצרת צבעים ראשיים, משניים, שלישוניים וניטרליים מצבע בסיס נתון. נקודה אחת שהעלנו הרבה כשדיברנו עם שותפים פנימיים וחיצוניים הייתה איך להשתמש בצבעים דינמיים תוך שמירה על שליטה בצבעים מסוימים.
לצבעים האלה יש בדרך כלל משמעות או הקשר ספציפיים באפליקציה, שייעלמו אם הם יוחלפו בצבע אקראי. לחלופין, אם לא משנים את הצבעים, הם עלולים להיראות לא מתאימים או לא נעימים לעין.
הצבע ב-Material You מתואר באמצעות גוון, כרומה וטון. הגוון של צבע מסוים קשור לתפיסה שלו כחלק מטווח צבעים אחד לעומת טווח אחר. הגוון מתאר את מידת הבהירות או הכהות של הצבע, והכרומה היא עוצמת הצבע. התפיסה של גוון יכולה להיות מושפעת מגורמים תרבותיים ובלשניים, כמו העובדה שבתרבויות עתיקות רבות לא הייתה מילה לתיאור הצבע הכחול, והוא נתפס כחלק מאותה משפחה של הצבע הירוק.
גוון מסוים יכול להיחשב חם או קר, בהתאם למיקום שלו בספקטרום הגוונים. שינוי הגוון לאדום, לכתום או לצהוב נחשב בדרך כלל לחימום, ושינוי הגוון לכחול, לירוק או לסגול נחשב לקירור. גם בתוך הצבעים החמים או הקרים, יהיו לכם גוונים חמים וקרים. בתמונה שלמטה, הצבע הצהוב ה'חם' יותר נוטה לכתום, ואילו הצבע הצהוב ה'קר' יותר נוטה לירוק. 
האלגוריתם של התאמת הצבעים בודק את הגוון של הצבע שלא הוזז ואת הצבע שאליו הוא צריך להיות מותאם, כדי למצוא גוון הרמוני שלא משנה את איכויות הצבע הבסיסיות. בגרפיקה הראשונה, גוונים פחות הרמוניים של ירוק, צהוב וכתום מסודרים על ספקטרום. בגרפיקה הבאה, הצבעים הירוק והכתום הותאמו לגוון הצהוב. הירוק החדש חם יותר והכתום החדש קר יותר.
הגוון של הכתום והירוק השתנה, אבל עדיין אפשר לראות אותם ככתום וירוק.

אם אתם רוצים לקרוא מידע נוסף על חלק מההחלטות, הבדיקות והשיקולים שהיו לנו בתהליך התכנון, אתם יכולים לעיין בפוסט הזה בבלוג שבו הקולגות שלי, איין דניאלס ואנדרו לו, מסבירים את הנושא בצורה מפורטת יותר.
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);
}
כדי לאחזר את סט ארבעת הטונים, אנחנו צריכים לעשות עוד קצת.
מכיוון שכבר יש לנו את צבע המקור, אנחנו צריכים:
- להחליט אם צריך להוסיף הרמוניה,
- לקבוע אם אנחנו במצב כהה, וגם
- מחזירה אובייקט
ColorRolesשעבר הרמוניזציה או שלא עבר הרמוניזציה.
האם להוסיף הרמוניה
בנושא המיוצא מ-Material Theme 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 ובמתאם כדי לאכלס את אוסף כרטיסי התחבורה הציבורית ולצבוע אותם.

אחסון נתוני מעבר
כדי לאחסן את נתוני הטקסט ואת פרטי הצבע של כרטיסי התחבורה הציבורית, אנחנו משתמשים במחלקת נתונים שמאחסנת את השם, היעד ומזהה משאב הצבע.
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 היא מחלקה ליצירת אובייקטים שמאפשרת לציין הרבה מהפעולות שביצענו עד עכשיו באופן ידני.
אחרי שמקבלים את ההקשר הנוכחי כדי לקבל גישה לסכמת הצבעים הדינמית הנוכחית, צריך לציין את צבעי הבסיס שרוצים להוסיף להם הרמוניה וליצור הקשר חדש על סמך אובייקט HarmonizedColorOptions וההקשר DynamicColors המופעל.
אם לא רוצים להוסיף הרמוניה לצבע מסוים, פשוט לא כוללים אותו ב-harmonizedOptions.
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 כדי לאחזר Context עם הצבעים ההרמוניים. יש הבדל עיקרי אחד בין שתי השיטות. בנוסף, אנחנו צריכים לספק שכבת-על של נושא שמכילה את השדות שצריך להוסיף.
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. ממשקי משתמש לדוגמה
עיצוב ברירת המחדל וצבעים בהתאמה אישית ללא התאמה

צבעים מותאמים אישית בהרמוניה


12. סיכום
ב-codelab הזה למדתם:
- העקרונות הבסיסיים של האלגוריתם שלנו ליצירת הרמוניה בין צבעים
- איך ליצור תפקידי צבע מצבע נתון שנראה.
- איך יוצרים הרמוניה בין צבעים בממשק משתמש.
- איך ליצור הרמוניה בין קבוצת מאפיינים בערכת נושא.
אם יש לך שאלות, אפשר לפנות אלינו בכל שלב באמצעות @MaterialDesign בטוויטר.
בקרוב יפורסמו עוד תכנים ומדריכים בנושא עיצוב בכתובת youtube.com/MaterialDesign