১. শুরু করার আগে
এই কোডল্যাবে আপনি শিখবেন, কীভাবে একটি ডাইনামিক থিম দ্বারা তৈরি রঙের সাথে আপনার নিজস্ব রঙগুলোকে সামঞ্জস্যপূর্ণ করতে হয়।
পূর্বশর্ত
ডেভেলপারদের হওয়া উচিত
- অ্যান্ড্রয়েডে থিমিং-এর প্রাথমিক ধারণা সম্পর্কে পরিচিত।
- অ্যান্ড্রয়েড উইজেট ভিউ এবং তাদের প্রোপার্টি নিয়ে স্বাচ্ছন্দ্যে কাজ করতে পারি।
আপনি যা শিখবেন
- একাধিক পদ্ধতি ব্যবহার করে আপনার অ্যাপ্লিকেশনে কীভাবে রঙের সামঞ্জস্য ব্যবহার করবেন
- সমন্বয় কীভাবে কাজ করে এবং কীভাবে এটি রঙ পরিবর্তন করে
আপনার যা যা লাগবে
- আপনি যদি সাথে সাথে অনুসরণ করতে চান, তাহলে অ্যান্ড্রয়েড ইনস্টল করা একটি কম্পিউটার প্রয়োজন হবে।
২. অ্যাপের সংক্ষিপ্ত বিবরণ
Voyaĝi একটি ট্রানজিট অ্যাপ্লিকেশন যা ইতিমধ্যেই একটি ডাইনামিক থিম ব্যবহার করে। অনেক ট্রানজিট সিস্টেমের জন্য, ট্রেন, বাস বা ট্রামের একটি গুরুত্বপূর্ণ নির্দেশক হলো রঙ এবং উপলব্ধ যেকোনো ডাইনামিক প্রাইমারি, সেকেন্ডারি বা টারশিয়ারি রঙ দিয়ে এগুলোকে প্রতিস্থাপন করা যায় না। আমরা রঙিন ট্রানজিট কার্ডগুলোর RecyclerView-এর উপর আমাদের কাজকে কেন্দ্রীভূত করব।

৩. একটি থিম তৈরি করা
একটি Material3 থিম তৈরি করার জন্য প্রথম পদক্ষেপ হিসেবে আমরা আমাদের টুল Material Theme Builder ব্যবহার করার পরামর্শ দিই। কাস্টম ট্যাবে, আপনি এখন আপনার থিমে আরও রঙ যোগ করতে পারবেন। ডানদিকে, আপনাকে সেই রঙগুলোর জন্য কালার রোল এবং টোনাল প্যালেট দেখানো হবে।
বর্ধিত রঙ বিভাগে, আপনি রঙগুলি সরাতে বা নাম পরিবর্তন করতে পারেন।

এক্সপোর্ট মেনুতে বেশ কিছু সম্ভাব্য এক্সপোর্ট অপশন দেখা যাবে। এই প্রতিবেদন লেখার সময়, ম্যাটেরিয়াল থিম বিল্ডারের হারমোনাইজেশন সেটিংস পরিচালনার বিশেষ সুবিধাটি শুধুমাত্র অ্যান্ড্রয়েড ভিউ-এর ক্ষেত্রেই উপলব্ধ।

নতুন রপ্তানি মূল্য বোঝা
আপনি সামঞ্জস্য বিধান করতে চান বা না চান, আপনার থিমে এই রঙগুলো এবং এদের সংশ্লিষ্ট কালার রোলগুলো ব্যবহার করার সুযোগ দিতে, এক্সপোর্ট করা ডাউনলোডে এখন একটি 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> প্রপার্টিটি নির্দেশ করে যে ডেভেলপার ম্যাটেরিয়াল থিম বিল্ডারে অপশনটি নির্বাচন করেছেন কিনা। এটি কোর থিমের রঙ পরিবর্তন করবে না।
<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>
৪. কাস্টম রঙ পরীক্ষা করা
ম্যাটেরিয়াল থিম বিল্ডারের সাইড প্যানেলে জুম করলে দেখা যায় যে, একটি কাস্টম রঙ যোগ করলে হালকা ও গাঢ় রঙের প্যালেটে চারটি প্রধান রঙের ভূমিকা সহ একটি প্যানেল প্রদর্শিত হয়।

অ্যান্ড্রয়েড ভিউতে আমরা এই রঙগুলো আপনার জন্য এক্সপোর্ট করে দিই, কিন্তু নেপথ্যে এগুলোকে 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 */ }
একইভাবে আউটপুট মানগুলি হল প্রকৃত রঙের মান, সেগুলির পয়েন্টার নয়।
৫. রঙের সামঞ্জস্য বিধান বলতে কী বোঝায়?
ম্যাটেরিয়ালের নতুন কালার সিস্টেমটি নকশাগতভাবেই অ্যালগরিদমিক, যা একটি প্রদত্ত সিড কালার থেকে প্রাইমারি, সেকেন্ডারি, টারশিয়ারি এবং নিউট্রাল কালার তৈরি করে। অভ্যন্তরীণ ও বাহ্যিক অংশীদারদের সাথে কথা বলার সময় আমরা যে একটি উদ্বেগের বিষয় বারবার পেয়েছি, তা হলো কিছু রঙের উপর নিয়ন্ত্রণ বজায় রেখে কীভাবে ডাইনামিক কালারকে গ্রহণ করা যায়।
অ্যাপ্লিকেশনটিতে এই রঙগুলো প্রায়শই একটি নির্দিষ্ট অর্থ বা প্রেক্ষাপট বহন করে, যা কোনো এলোমেলো রঙ দিয়ে প্রতিস্থাপন করলে হারিয়ে যাবে। অন্যদিকে, অপরিবর্তিত রাখলে এই রঙগুলো দেখতে দৃষ্টিকটু বা বেমানান লাগতে পারে।
ম্যাটেরিয়াল ইউ-তে রঙকে হিউ, ক্রোমা এবং টোন দ্বারা বর্ণনা করা হয়। একটি রঙের হিউ বলতে বোঝায়, সেটিকে এক রঙের পরিসরের সদস্য হিসেবে এবং অন্যটির সদস্য হিসেবে একজনের উপলব্ধি। টোন বর্ণনা করে রঙটি কতটা হালকা বা গাঢ় এবং ক্রোমা হলো রঙের তীব্রতা। হিউ-এর উপলব্ধি সাংস্কৃতিক এবং ভাষাগত কারণ দ্বারা প্রভাবিত হতে পারে, যেমন প্রাচীন সংস্কৃতিতে নীল রঙের জন্য কোনো শব্দ না থাকার বিষয়টি, যেখানে এটিকে সবুজ রঙের পরিবারের অন্তর্ভুক্ত বলে মনে করা হতো।
রঙের বর্ণালীতে একটি নির্দিষ্ট রঙের অবস্থান অনুযায়ী সেটিকে উষ্ণ বা শীতল হিসেবে বিবেচনা করা যেতে পারে। সাধারণত লাল, কমলা বা হলুদ রঙের দিকে ঝোঁকলে সেটিকে উষ্ণতর এবং নীল, সবুজ বা বেগুনি রঙের দিকে ঝোঁকলে সেটিকে শীতলতর বলে মনে করা হয়। এমনকি উষ্ণ বা শীতল রঙের মধ্যেও উষ্ণ এবং শীতল টোন থাকে। নিচে, 'উষ্ণতর' হলুদ রঙটিতে কমলার আভা বেশি, যেখানে 'শীতলতর' হলুদ রঙটি সবুজের দ্বারা বেশি প্রভাবিত। 
রঙ সামঞ্জস্যকরণ অ্যালগরিদমটি অপরিবর্তিত রঙের হিউ এবং যে রঙের সাথে এটিকে সামঞ্জস্য করা উচিত, তা পরীক্ষা করে এমন একটি হিউ খুঁজে বের করে যা সামঞ্জস্যপূর্ণ কিন্তু এর অন্তর্নিহিত রঙের গুণাবলী পরিবর্তন করে না। প্রথম গ্রাফিকটিতে, একটি বর্ণালীতে কম সামঞ্জস্যপূর্ণ সবুজ, হলুদ এবং কমলা হিউগুলো প্লট করা আছে। পরবর্তী গ্রাফিকটিতে, সবুজ এবং কমলা রঙকে হলুদ হিউয়ের সাথে সামঞ্জস্যপূর্ণ করা হয়েছে। নতুন সবুজ রঙটি আরও উষ্ণ এবং নতুন কমলা রঙটি আরও শীতল।
কমলা ও সবুজের আভা পরিবর্তিত হয়েছে, কিন্তু সেগুলোকে এখনও কমলা ও সবুজ হিসেবেই উপলব্ধি করা যায়।

আপনি যদি কিছু ডিজাইন সংক্রান্ত সিদ্ধান্ত, অন্বেষণ এবং বিবেচ্য বিষয় সম্পর্কে আরও জানতে চান, তাহলে আমার সহকর্মী আয়ান ড্যানিয়েলস এবং অ্যান্ড্রু লু একটি ব্লগ পোস্ট লিখেছেন যা এই অংশের চেয়ে কিছুটা বেশি বিশদভাবে আলোচনা করে ।
৬. ম্যানুয়ালি একটি রঙের সামঞ্জস্য বিধান করা
একটি একক টোনকে সামঞ্জস্যপূর্ণ করতে MaterialColors এ দুটি ফাংশন রয়েছে: harmonize এবং harmonizeWithPrimary ।
harmonizeWithPrimary বর্তমান থিম এবং ফলস্বরূপ সেখান থেকে প্রাথমিক রঙটি অ্যাক্সেস করার মাধ্যম হিসেবে Context ব্যবহার করে।
@ColorInt
public static int harmonizeWithPrimary(@NonNull Context context, @ColorInt int colorToHarmonize) {
return harmonize(
colorToHarmonize,
getColor(context, R.attr.colorPrimary, MaterialColors.class.getCanonicalName()));
}
@ColorInt
public static int harmonize(@ColorInt int colorToHarmonize, @ColorInt int colorToHarmonizeWith) {
return Blend.harmonize(colorToHarmonize, colorToHarmonizeWith);
}
চারটি সুরের সেটটি পুনরুদ্ধার করতে আমাদের আরও কিছুটা করতে হবে।
যেহেতু আমাদের কাছে ইতিমধ্যেই উৎস রঙটি আছে, আমাদের যা করতে হবে তা হলো:
- এটিকে সামঞ্জস্যপূর্ণ করা উচিত কিনা তা নির্ধারণ করুন,
- আমরা ডার্ক মোডে আছি কিনা তা নির্ধারণ করুন, এবং,
- একটি সামঞ্জস্যপূর্ণ অথবা অসামঞ্জস্যপূর্ণ
ColorRolesঅবজেক্ট ফেরত দিন।
সামঞ্জস্য বিধান করা হবে কিনা তা নির্ধারণ করা
ম্যাটেরিয়াল থিম বিল্ডার থেকে এক্সপোর্ট করা থিমটিতে, আমরা 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)
}
৭. ট্রানজিট কার্ড পূরণ করা
যেমনটা আগে উল্লেখ করা হয়েছে, আমরা ট্রানজিট কার্ডের সংগ্রহটি পূরণ করতে এবং তাতে রঙ করতে একটি RecyclerView ও অ্যাডাপ্টার ব্যবহার করব।

ট্রানজিট ডেটা সংরক্ষণ
ট্রানজিট কার্ডের টেক্সট ডেটা এবং রঙের তথ্য সংরক্ষণ করার জন্য, আমরা একটি ডেটা ক্লাস ব্যবহার করছি যেখানে নাম, গন্তব্য এবং কালার রিসোর্স আইডি সংরক্ষিত থাকে।
data class TransitInfo(val name: String, val destination: String, val colorId: Int)
/* truncated code */
val transitItems = listOf(
TransitInfo("53", "Irvine", R.color.custom1),
TransitInfo("153", "Brea", R.color.custom1),
TransitInfo("Orange County Line", "Oceanside", R.color.custom2),
TransitInfo("Pacific Surfliner", "San Diego", R.color.custom2)
)
রিয়েল-টাইমে আমাদের প্রয়োজনীয় টোনগুলো তৈরি করতে আমরা এই রঙটি ব্যবহার করব।
আপনি নিম্নলিখিত onBindViewHolder ফাংশনটির সাহায্যে রানটাইমে সামঞ্জস্য বিধান করতে পারেন।
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val transitInfo = list.get(position)
val color = transitInfo.colorId
if (!colorRolesMap.containsKey(color)) {
val roles = retrieveHarmonizedColorRoles(
holder.itemView, color,
!isNightMode(holder.itemView.context)
)
colorRolesMap.put(color, roles)
}
val card = holder.card
holder.transitName.text = transitInfo.name
holder.transitDestination.text = transitInfo.destination
val colorRoles = colorRolesMap.get(color)
if (colorRoles != null) {
holder.card.setCardBackgroundColor(colorRoles.accentContainer)
holder.transitName.setTextColor(colorRoles.onAccentContainer)
holder.transitDestination.setTextColor(colorRoles.onAccentContainer)
}
}
৮. স্বয়ংক্রিয়ভাবে রঙের সামঞ্জস্য বিধান
ম্যানুয়ালি সামঞ্জস্য বিধান করার বিকল্প হিসেবে, আপনি এই কাজটি অন্যকে দিয়ে করিয়ে নিতে পারেন। 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)
}
}
৯. থিমের বৈশিষ্ট্যগুলোকে স্বয়ংক্রিয়ভাবে সামঞ্জস্য করা
এখন পর্যন্ত দেখানো পদ্ধতিগুলো একটি নির্দিষ্ট রঙ থেকে তার ভূমিকাগুলো বের করার উপর নির্ভর করে। একটি আসল টোন তৈরি হচ্ছে তা দেখানোর জন্য এটি চমৎকার, কিন্তু বেশিরভাগ বিদ্যমান অ্যাপ্লিকেশনের জন্য এটি বাস্তবসম্মত নয়। আপনি সম্ভবত সরাসরি কোনো রঙ তৈরি না করে, বরং একটি বিদ্যমান থিম অ্যাট্রিবিউট ব্যবহার করবেন।
এই কোডল্যাবের শুরুতে আমরা থিম অ্যাট্রিবিউট এক্সপোর্ট করা নিয়ে আলোচনা করেছি।
<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)
}
}
১১. উদাহরণ UI
ডিফল্ট থিমিং এবং কাস্টম রঙের মধ্যে কোনো সামঞ্জস্য নেই

সমন্বিত কাস্টম রঙ




১২. সারসংক্ষেপ
এই কোডল্যাবে, আপনি শিখেছেন:
- আমাদের রঙ সামঞ্জস্যকরণ অ্যালগরিদমের মূল বিষয়গুলি
- প্রদত্ত কোনো দৃশ্যমান রঙ থেকে কীভাবে রঙের ভূমিকা তৈরি করা যায়।
- ইউজার ইন্টারফেসে কীভাবে বেছে বেছে কোনো রঙের সামঞ্জস্য বিধান করা যায়।
- একটি থিমের মধ্যে থাকা বিভিন্ন বৈশিষ্ট্যকে কীভাবে সামঞ্জস্যপূর্ণ করা যায়।
আপনার কোনো প্রশ্ন থাকলে, টুইটারে @MaterialDesign ব্যবহার করে যেকোনো সময় নির্দ্বিধায় আমাদের জিজ্ঞাসা করতে পারেন।
আরও ডিজাইন কন্টেন্ট এবং টিউটোরিয়ালের জন্য youtube.com/MaterialDesign- এ চোখ রাখুন।