Android व्यू के लिए, कलर हार्मनाइज़ेशन का बुनियादी तरीका

1. शुरू करने से पहले

इस कोडलैब में, आपको अपनी पसंद के मुताबिक बनाए गए रंगों को डाइनैमिक थीम से जनरेट हुए रंगों के साथ तालमेल बिठाने का तरीका बताया जाएगा.

ज़रूरी शर्तें

डेवलपर के पास ये जानकारी होनी चाहिए

  • Android में थीमिंग के बुनियादी कॉन्सेप्ट की जानकारी
  • Android विजेट व्यू और उनकी प्रॉपर्टी के साथ काम करने का अनुभव

आपको क्या सीखने को मिलेगा

  • अपने ऐप्लिकेशन में, एक से ज़्यादा तरीकों का इस्तेमाल करके, कलर हारमोनिज़ेशन की सुविधा का इस्तेमाल करने का तरीका
  • कलर हारमोनिज़ेशन की सुविधा काम कैसे करती है और यह रंग को कैसे बदलती है

आपको किन चीज़ों की ज़रूरत होगी

  • अगर आपको साथ-साथ कोडलैब में दिए गए निर्देशों को आज़माना है, तो Android वाला कंप्यूटर.

2. ऐप्लिकेशन का अवलोकन

Voyaĝi, बस, मेट्रो वगैरह के लिए एक ऐसा ऐप्लिकेशन है जो पहले से ही डाइनैमिक थीम का इस्तेमाल करता है. बस, मेट्रो वगैरह के कई सिस्टम के लिए, रंग, ट्रेन, बस या ट्राम की जानकारी देने वाला एक अहम इंडिकेटर होता है. इन्हें डाइनैमिक प्राइमरी, सेकंडरी या टर्शियरी कलर से नहीं बदला जा सकता. हमारा फ़ोकस, रंगीन बस, मेट्रो वगैरह के लिए कार्ड के RecyclerView पर होगा.

62ff4b2fb6c9e14a.png

3. कोई थीम जनरेट करना

हमारा सुझाव है कि Material3 थीम बनाने के लिए, हमारे टूल Material Theme Builder का इस्तेमाल करें. कस्टम टैब पर जाकर, अब अपनी थीम में ज़्यादा रंग जोड़े जा सकते हैं. दाईं ओर, आपको उन रंगों के लिए कलर रोल और टोनल पैलेट दिखेंगे.

एक्सटेंड किए गए कलर सेक्शन में, रंगों को हटाया या उनका नाम बदला जा सकता है.

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

}

MaterialColors क्लास में getColorRoles का इस्तेमाल करके, रनटाइम में किसी भी कलर से चार मुख्य कलर रोल वापस पाए जा सकते हैं. 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 की सुविधा चालू करने वाले कॉन्टेक्स्ट के आधार पर, एक नया कॉन्टेक्स्ट बनाना होगा.

अगर आपको किसी कलर को हारमोनाइज़ नहीं करना है, तो उसे 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. थीम एट्रिब्यूट को अपने-आप हारमोनाइज़ करना

अब तक दिखाए गए तरीकों में, किसी एक कलर से कलर रोल वापस पाए जाते हैं. इससे यह दिखाया जा सकता है कि असली टोन जनरेट हो रहा है. हालांकि, यह ज़्यादातर मौजूदा ऐप्लिकेशन के लिए सही नहीं है. ऐसा हो सकता है कि आप सीधे तौर पर कोई कलर न लें, बल्कि मौजूदा थीम एट्रिब्यूट का इस्तेमाल करें.

हमने इस कोडलैब में पहले, थीम एट्रिब्यूट को एक्सपोर्ट करने के बारे में बताया था.

<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 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. खास जानकारी

इस कोडलैब में, आपने इनके बारे में जानकारी पाई:

  • हमारे कलर हारमोनिज़ेशन एल्गोरिदम की बुनियादी जानकारी
  • दिए गए सीड कलर से कलर रोल जनरेट करने का तरीका.
  • यूज़र इंटरफ़ेस में किसी कलर को चुनिंदा तरीके से हारमोनाइज़ करने का तरीका.
  • किसी थीम में एट्रिब्यूट के सेट को हारमोनाइज़ करने का तरीका.

अगर आपके कोई सवाल हैं, तो Twitter पर @MaterialDesign का इस्तेमाल करके, हमसे कभी भी पूछें.

डिज़ाइन से जुड़े ज़्यादा कॉन्टेंट और ट्यूटोरियल के लिए, youtube.com/MaterialDesign पर बने रहें