تنسيق الألوان الأساسي في طرق عرض Android

1. قبل البدء

في هذا الدرس التطبيقي حول الترميز، ستتعلّم كيفية تنسيق ألوانك المخصّصة مع الألوان التي تم إنشاؤها باستخدام مظهر ديناميكي.

المتطلبات الأساسية

يجب أن يكون المطورين

  • كنت على دراية بمفاهيم إنشاء المواضيع الأساسية في Android.
  • سهولة العمل مع طرق عرض أدوات Android وخصائصها

ما ستتعرَّف عليه

  • كيفية استخدام تنسيق الألوان في تطبيقك باستخدام طرق متعددة
  • كيف تعمل التناغم وكيفية تغيير اللون

المتطلبات

  • جهاز كمبيوتر تم تثبيت Android عليه إذا أردت المتابعة.

2. نظرة عامة على التطبيق

Voyaّب هو تطبيق نقل عام يستخدم حاليًا مظهرًا ديناميكيًا. بالنسبة للعديد من أنظمة النقل، يعد اللون مؤشرًا مهمًا للقطارات أو الحافلات أو الترام ولا يمكن استبدالها بأي ألوان ديناميكية أو أساسية أو ثانوية أو ثلاثية متاحة. سنركّز جهودنا على RecyclerView من بطاقات النقل العام الملونة.

62ff4b2fb6c9e14a.png

3- إنشاء موضوع

ننصحك باستخدام أداتنا Material Theme Builder، وهي محطتك الأولى لإنشاء مظهر Material3. في علامة التبويب "مخصّص"، يمكنك الآن إضافة المزيد من الألوان إلى المظهر. على اليمين، ستظهر لك أدوار الألوان ولوحات الدرجات اللونية لتلك الألوان.

في قسم الألوان الموسّع، يمكنك إزالة الألوان أو إعادة تسميتها.

20cc2cf72efef213.png

ستعرض قائمة التصدير عددًا من خيارات التصدير الممكنة. في وقت كتابة هذا التقرير، كانت المعالجة الخاصة لإعدادات التناغم في Material Theme 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>

في products.xml، أنشأنا أدوار اللون الأربعة لكل لون مخصص (color<name>, colorOn<name>, color<name>Container, and colorOn<nameContainer>). تعكس سمات harmonize<name> ما إذا كان المطوّر قد حدّد الخيار في أداة إنشاء المظاهر Material. لن يؤدي ذلك إلى تغيير اللون في المظهر الأساسي.

<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، نصدّر هذه الألوان نيابةً عنك، ولكن يمكن تمثيلها في الخلفية من خلال عنصر 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 من خلال تدرُّج اللون والكروما والدرجة اللونية. يتعلق تدرج اللون بتصور المرء له كعضو في نطاق ألوان مقابل آخر. تصف الدرجة اللونية كيف يظهر فاتح أو غامق والكروما هو كثافة اللون. يمكن أن يتأثر تصور اللون بالعوامل الثقافية واللغوية، مثل الافتقار إلى كلمة يشير إلى اللون الأزرق في الثقافات القديمة، إلى جانب ظهور اللون في نفس عائلة اللون الأخضر.

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

إذا كنت لا تريد تنسيق لون، فما عليك سوى عدم تضمينه في 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. تنسيق سمات المظهر تلقائيًا

تعتمد الطرق المعروضة حتى الآن على استرداد أدوار اللون من لون فردي. وهذا أمر رائع لإظهار أنه يتم إنشاء نبرة حقيقية، ولكنها ليست واقعية لمعظم التطبيقات الحالية. لن تستمد اللون على الأرجح اللون مباشرةً، ولكن بدلاً من ذلك تستخدم سمة مظهر حالية.

تحدثنا في وقت سابق من هذا الدرس التطبيقي عن تصدير سمات المظاهر.

<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. ملخّص

في هذا الدرس التطبيقي حول الترميز، تعرّفت على ما يلي:

  • أساسيات خوارزمية تناغم الألوان لدينا
  • كيفية إنشاء أدوار اللون من لون مرئي معيّن.
  • كيفية مواءمة لون بشكل انتقائي في واجهة مستخدم.
  • كيفية تنسيق مجموعة من السمات في موضوع معيّن

إذا كانت لديك أسئلة، يُرجى طرحها علينا في أي وقت باستخدام @MaterialDesign على Twitter.

ترقَّب المزيد من المحتوى حول التصميم والبرامج التعليمية على youtube.com/MaterialDesign.