هماهنگ سازی رنگ های اولیه در نمایش های اندروید

۱. قبل از شروع

در این آزمایشگاه کد، یاد خواهید گرفت که چگونه رنگ‌های سفارشی خود را با رنگ‌های تولید شده توسط یک قالب پویا هماهنگ کنید.

پیش‌نیازها

توسعه‌دهندگان باید باشند

  • آشنایی با مفاهیم اولیه تم گذاری در اندروید
  • کار راحت با Viewهای ویجت اندروید و ویژگی‌های آنها

آنچه یاد خواهید گرفت

  • نحوه استفاده از هماهنگی رنگ در برنامه خود با استفاده از چندین روش
  • چگونه هارمونی کار می‌کند و چگونه رنگ را تغییر می‌دهد

آنچه نیاز دارید

  • یک کامپیوتر با سیستم عامل اندروید اگر مایل به ادامه هستید.

۲. بررسی اجمالی برنامه

Voyaĝi یک اپلیکیشن حمل و نقل عمومی است که از قبل از یک تم پویا استفاده می‌کند. برای بسیاری از سیستم‌های حمل و نقل عمومی، رنگ یک شاخص مهم برای قطارها، اتوبوس‌ها یا ترامواها است و نمی‌توان آنها را با هیچ رنگ پویای اولیه، ثانویه یا ثالثیه موجود جایگزین کرد. ما کار خود را بر روی RecyclerView کارت‌های حمل و نقل رنگی متمرکز خواهیم کرد.

62ff4b2fb6c9e14a.png

۳. تولید یک قالب

ما توصیه می‌کنیم از ابزار Material Theme Builder به عنوان اولین گام برای ساخت یک تم Material3 استفاده کنید. در تب سفارشی، اکنون می‌توانید رنگ‌های بیشتری به تم خود اضافه کنید. در سمت راست، نقش‌های رنگی و پالت‌های تُنال برای آن رنگ‌ها به شما نشان داده می‌شود.

در بخش رنگ‌های توسعه‌یافته، می‌توانید رنگ‌ها را حذف یا تغییر نام دهید.

20cc2cf72efef213.png

منوی خروجی تعدادی از گزینه‌های خروجی ممکن را نمایش می‌دهد. در زمان نگارش این مطلب، تنظیمات ویژه هماهنگ‌سازی Material Theme Builder فقط در Android Views در دسترس است.

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>

در فایل 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>

۴. بررسی رنگ سفارشی

با بزرگنمایی پنل کناری Material Theme Builder، می‌توانیم ببینیم که اضافه کردن یک رنگ سفارشی، پنلی با چهار نقش رنگ کلیدی در یک پالت روشن و تیره را نمایش می‌دهد.

c6ee942b2b93cd92.png

در نمای اندروید، ما این رنگ‌ها را برای شما صادر می‌کنیم، اما در پشت صحنه می‌توانند توسط نمونه‌ای از شیء 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 */ }

به همین ترتیب، مقادیر خروجی، مقادیر واقعی رنگ هستند، نه اشاره‌گر به آنها.**

۵. هماهنگی رنگ چیست؟

سیستم رنگ جدید Material از نظر طراحی الگوریتمی است و رنگ‌های اولیه، ثانویه، ثالثیه و خنثی را از یک رنگ پایه مشخص تولید می‌کند. یکی از نکات نگران‌کننده‌ای که هنگام صحبت با شرکای داخلی و خارجی زیاد دریافت کردیم، چگونگی پذیرش رنگ پویا در عین حفظ کنترل بر برخی رنگ‌ها بود.

این رنگ‌ها اغلب در کاربرد، معنا یا مفهوم خاصی را حمل می‌کنند که اگر با یک رنگ تصادفی جایگزین شوند، از بین می‌روند. از طرف دیگر، اگر به همین شکل باقی بمانند، ممکن است از نظر بصری ناموزون یا نامناسب به نظر برسند.

رنگ در متریال شما با فام (hue)، کروما (chroma) و تُن (tone) توصیف می‌شود. تُن یک رنگ به درک فرد از آن به عنوان عضوی از یک طیف رنگی در مقابل طیف رنگی دیگر مربوط می‌شود. تُن میزان روشنی یا تیرگی آن را توصیف می‌کند و کروما شدت رنگ است. درک فام می‌تواند تحت تأثیر عوامل فرهنگی و زبانی باشد، مانند فقدان مکرر کلمه‌ای برای آبی در فرهنگ‌های باستانی که در عوض در همان خانواده سبز دیده می‌شود.

57c46d9974c52e4a.png یک رنگ خاص را می‌توان بسته به جایگاهش در طیف رنگ، گرم یا سرد در نظر گرفت. تغییر به سمت رنگ قرمز، نارنجی یا زرد معمولاً آن را گرم‌تر و به سمت آبی، سبز یا بنفش آن را سردتر در نظر می‌گیرد. حتی در میان رنگ‌های گرم یا سرد، تُن‌های گرم و سرد وجود دارد. در زیر، زرد «گرم‌تر» بیشتر به رنگ نارنجی متمایل است در حالی که زرد «سردتر» بیشتر تحت تأثیر سبز قرار دارد. 597c6428ff6b9669.png

الگوریتم هماهنگ‌سازی رنگ، رنگِ تغییرنیافته و رنگی که باید با آن هماهنگ شود را بررسی می‌کند تا رنگی را پیدا کند که هماهنگ باشد اما ویژگی‌های رنگی زیرین آن را تغییر ندهد. در نمودار اول، رنگ‌های سبز، زرد و نارنجی با هماهنگی کمتر روی یک طیف ترسیم شده‌اند. در نمودار بعدی، سبز و نارنجی با رنگ زرد هماهنگ شده‌اند. سبز جدید گرم‌تر و نارنجی جدید سردتر است.

رنگ نارنجی و سبز تغییر کرده است اما هنوز هم می‌توان آنها را نارنجی و سبز درک کرد.

766516c321348a7c.png

اگر مایلید درباره برخی از تصمیمات، کاوش‌ها و ملاحظات طراحی بیشتر بدانید، همکارانم آیان دنیلز و اندرو لو در وبلاگی مطلبی نوشته‌اند که کمی عمیق‌تر از این بخش است.

۶. هماهنگ‌سازی دستی رنگ‌ها

برای هماهنگ‌سازی یک تُن واحد، دو تابع در 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 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
}

ایجاد یک شیء Harmonized 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)
}

۷. پر کردن کارت‌های حمل و نقل عمومی

همانطور که قبلاً اشاره شد، ما از یک 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)
   }
}

۸. هماهنگ‌سازی خودکار رنگ‌ها

یک جایگزین برای مدیریت دستی هماهنگ‌سازی، این است که می‌توانید آن را برای خود مدیریت کنید. HarmonizedColorOptions یک کلاس سازنده است که به شما امکان می‌دهد بسیاری از کارهایی را که تاکنون به صورت دستی انجام داده‌ایم، مشخص کنید.

پس از بازیابی زمینه فعلی و دسترسی به طرح پویای فعلی، باید رنگ‌های پایه‌ای را که می‌خواهید هماهنگ کنید مشخص کنید و یک زمینه جدید بر اساس شیء HarmonizedColorOptions و زمینه فعال‌شده DynamicColors ایجاد کنید.

اگر نمی‌خواهید رنگی را هماهنگ کنید، آن را در harmonyizedOptions قرار ندهید.

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

۹. هماهنگ‌سازی خودکار ویژگی‌های قالب

روش‌هایی که تاکنون نشان داده شده‌اند، بر بازیابی نقش‌های رنگی از یک رنگ منفرد متکی هستند. این روش برای نشان دادن اینکه یک تُن واقعی تولید می‌شود اما برای اکثر برنامه‌های موجود واقع‌بینانه نیست، عالی است. احتمالاً شما مستقیماً یک رنگ را استخراج نمی‌کنید، بلکه از یک ویژگی تم موجود استفاده می‌کنید.

پیش از این در این آزمایشگاه کد، در مورد اکسپورت کردن ویژگی‌های قالب صحبت کردیم.

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

۱۰. کد منبع

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

۱۱. رابط‌های کاربری نمونه

قالب‌بندی پیش‌فرض و رنگ‌های سفارشی بدون هماهنگی

a5a02a72aef30529.png

رنگ‌های سفارشی هماهنگ

4ac88011173d6753.pngd5084780d2c6b886.png

dd0c8b90eccd8bef.pngc51f8a677b22cd54.png

۱۲. خلاصه

در این آزمایشگاه کد، شما موارد زیر را آموخته‌اید:

  • اصول اولیه الگوریتم هماهنگ‌سازی رنگ ما
  • چگونه می‌توان نقش‌های رنگی را از یک رنگ دیده شده‌ی مشخص تولید کرد.
  • چگونه می‌توان به صورت انتخابی یک رنگ را در رابط کاربری هماهنگ کرد.
  • چگونه مجموعه‌ای از ویژگی‌ها را در یک قالب هماهنگ کنیم.

اگر سوالی دارید، می‌توانید هر زمان که خواستید با استفاده از @MaterialDesign در توییتر از ما بپرسید.

برای مطالب و آموزش‌های بیشتر در زمینه طراحی، در youtube.com/MaterialDesign با ما همراه باشید.