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

1. قبل از شروع

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

پیش نیازها

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

  • آشنا با مفاهیم پایه تم در اندروید
  • کار راحت با نماهای ویجت اندروید و ویژگی های آنها

چیزی که یاد خواهید گرفت

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

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

  • اگر می‌خواهید دنبال کنید، رایانه‌ای با Android نصب شده است.

2. نمای کلی برنامه

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

62ff4b2fb6c9e14a.png

3. ایجاد یک تم

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

در قسمت Extended Color می توانید رنگ ها را حذف یا تغییر نام دهید.

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>

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

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

c6ee942b2b93cd92.png

در 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 You با رنگ، رنگ و تن توصیف می شود. رنگ یک رنگ به درک فرد از آن به عنوان عضوی از یک طیف رنگی در مقابل دیگری مربوط می شود. تن توضیح می دهد که چگونه روشن یا تیره به نظر می رسد و کروما شدت رنگ است. درک رنگ می‌تواند تحت‌تاثیر عوامل فرهنگی و زبانی قرار گیرد، مانند عدم وجود کلمه‌ای برای آبی در فرهنگ‌های باستانی که در عوض در خانواده‌ای با رنگ سبز دیده می‌شود.

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

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

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

766516c321348a7c.png

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

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 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 و آداپتور برای پر کردن و رنگ آمیزی مجموعه کارت های حمل و نقل استفاده خواهیم کرد.

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 یک کلاس سازنده است که به شما اجازه می دهد تا بسیاری از کارهایی را که تاکنون با دست انجام داده ایم مشخص کنید.

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

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

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. هماهنگ کردن ویژگی های موضوع به طور خودکار

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

قبلا در این کد لبه، در مورد صادرات ویژگی های تم صحبت کردیم.

<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. رابط های کاربری مثال

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

a5a02a72aef30529.png

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

4ac88011173d6753.pngd5084780d2c6b886.png

dd0c8b90eccd8bef.pngc51f8a677b22cd54.png

12. خلاصه

در این کد لبه یاد گرفته اید:

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

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

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