سلام !! تو این مقاله میخوام باهم تم یا چند رنگی رو در برنامه های اندرویدی رو بررسی کنیم و اینکه Theme شب و روز (Light , Dark) رو پیاده سازی کنیم ، که با انجام اینکار پیاده سازی رنگ بندی های دیگ تفاوتی نداره و به سادگی قابل پیاده سازیه .

خب ما در برنامه آیگپ از چندین تم و رنگ های مختلفی رو پشتیبانی میکنیم و دست کاربر در انتخاب رنگ و ظاهر برنامه بازه ، خیلی ها مثل من پرسپولیسی اند و تم قرمز رو انتخاب میکنن و خیلیا با حالت شب (Dark Theme) احساس بهتری حین استفاده از برنامه دارند و هر کسی طبق سلیقه خودش میتونه رنگ و تم مورد علاقه ش رو انتخاب کنه .
برای پیاده سازی این مبحث اگر پروژه ای دارید که شروع نکردید کار سختی جز یسری کانفیگ ها ندارید و اگر هم پروژه طراحی شده و بزرگی دارید سخت ترین کارش تغییر لایه هاست و رنگ های ثابت رو به Attribute ها تغییر بدین که نسبت به تعداد لایه ها کار زمان بری میتونه باشه.
از طرف دیگ پشتیانی Theme در اندروید زیر ۵ (Api 21) هم درد سر های خاص خودش رو داره که اگر حواستون نباشه باگ های زیادی رو متاسفانه برای طیف وسیعی از این کاربران :( بوجود میاره ...
خب بعد از توضیح یکسری موارد بریم برای پیاده سازی Theme در برنامه هامون:
برای اینکار بهتره ساختار برنامه تون از Single Activity پشتیبانی کنه ولی باز هم با همون ساختار اکتیویتی هم میتونید پیاده سازی کنید که نیاز به یسری کد های اضافه و مدیریت اون در هر اکتیویتی داره.
برای نگهداری وضعیت تم انتخابی کاربر میتونیم از Shared Preferences استفاده کنیم و وضعیت رو در اون ذخیره و بازیابی کنیم .
همونطور که در کد بالا میبینید با استفاده از enum تم Dark و Light رو تعریف و در Shared Preferences با استفاده از متد ها ذخیره و بازیابی میکنم .
در اولین متد یعنی getTheme یسری استایل با بررسی وضعیت تم انتخابی برگشت داده میشه که این Style رو در MainActivity و متد OnCreate و قبل از صدا زدن Super اون مقدار دهی میکنیم.
خب حالا این استایل ها چی اند ؟
ما تو اکثر پروژه ها یسری رنگ های ثابت داریم ; رنگ عنوان ها ، متون معمولی ، رنگ پس زمینه ، رنگ تولبار ، ساب تایتل ها ، دکمه ها و... که ما این رنگ ها رو بصورت Attribute تعریف و در لایه هامون به جای رنگ های ثابت استفاده میکنیم .
برای اینکار فایل attrs.xml رو میسازیم و attribute ها و موارد ثابتی که بالا شمردیم رو تعریف میکنیم ، که فرمت هر attr میتونه color باشه که در TextColor , HintColor , Background و هرجایی که رنگ ثابت میدادیم و reference برای جاهایی که drawable , shape میگیرن مقدار دهی کنیم .
خب بعد از این کار در فایل styles.xml با تعریف رنگ های Accent و Primary و همینطور فونت و.. یک فایل در پوشه res بنام themes.xml می سازیم که از BaseTheme تعریف شده ارث میبرن و رنگ هامون در هر تم رو مشخص میکنند.
همونطور که تو کد های بالا میبینید تمام رنگ ها و Attribute های که در پروژه و نسبت به UI نیاز بود رو پیاده سازی کردیم .
در کد بالا علاوه بر یسری رنگ ها Drawable هایی هم دیده میشه که ما میتونیم در لایه ها استفاده کنیم .
خب میرسیم به استفاده از این مقادیر در لایه های برنامه مون که برای این کار به راحتی بجای رنگ و مقادیر ثابت با علامت ? و نام اون Attribute لایه مون رو رنگ بندی میکنیم.
<ImageView android:id="@+id/icSeen" android:layout_width="@dimen/icons_size" android:layout_height="@dimen/icons_size" android:tint="?appTextTitleColor" app:srcCompat="@drawable/ic_eye" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="?appTextTitleColor" android:textSize="@dimen/medium_textSize" tools:text="قسمت ۰۱" />
برای همه بخش ها به این صورت عمل میکنیم و رنگ ها رو با استفاده از attribute ها مقدار دهی میکنیم .
همینطور Theme پیش فرض رو در بخش application در فایل manifests تغییر میدیم :
android:theme="@style/LightTheme"
برای دیدن وضعیت رنگ ها در لایه مون نیازی نیست هر بار ران کنیم و در لایه XML و بخش Preview و قسمت بالا میتونیم بین Theme ها سوییچ کنیم .
تمام کانفیگ ها انجام شد و حالا فقط سوییچ کردن بین تم ها مونده که بعضی برنامه ها برای انجام اینکار کارهای عجیبی مثل باز و بستن برنامه یا pop کردن تمام فرگمنت های اکتیویتی و شروع از نقطه اولیه و.. که هیچکدوم از این ها پیشنهاد نمیشه و تغییر تم باید بصورت smooth باشه و کاربر همون نقطه ای که سوییچ کرده از همون صفحه بتونه به کارش ادامه بده و مجبور به تکرار نباشه .
برای تغییر تم همونطور که گفتم باید از پترن Single Activity استفاده کنیم و تمام فرگمنت های زیری رو Attach/deAttach کنیم و اینکار هم نیاز نیست لیست تمام فرگمنت ها رو داشته باشیم و با attach/deattch کردن فرگمنت روت مون یا آیدی container مون بقیه صفحات آپدیت میشن .
private fun updateViewForNewTheme(isDark: Boolean) { theme.setTheme(if (isDark) Theme.ThemeType.DARK else Theme.ThemeType.LIGHT) val fragment = activity?.supportFragmentManager?.findFragmentById(R.id.fragment_container) fragment?.let { activity?.supportFragmentManager?.beginTransaction()?.let { it.detach(fragment) it.attach(fragment) it.commit() } } }
برای اینکار بهتره یک فرگمنت روت داشته باشیم به عنوان Main Fragment و بقیه صفحات رو تو اون اد کنیم و در خط اول onCreateView و قبل از super تم رو اپلای کنیم .
context?.theme?.applyStyle(theme.getTheme(), true)
خب بعد از ذخیره تم در Shared Preferences و تغییر اون یسری نکات ضروریه که گفته بشه:
برای پشتیبانی Theme در دیوایس های زیر اندروید ۵ بایستی یک فایل values-21/themes.xml داشته باشید که فایل تم که بالا ساختیم رو به این فایل انتقال بدیم و برای زیر ۵ از فایل values/themes.xml استفاده میکنیم که تنها تفاوتش در shape و drawable هایی که بصورت زیر تعریف شده :
<shape android:shape="rectangle" xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="?cardBackgroundColor"/> <corners android:radius="@dimen/dp10"/> </shape>
ست کردن رنگ ها در drawable ها و SVG ها باعث کرش در اندروید های زیر ۵ میشه و برای اینکار از دو فایل بالا استفاده میکنیم و در زیر ۵ از رنگ های ثابت برای هر تم استفاده میکنیم .
مورد دوم که باید توجه کرد اینه که در inflate کردن لایه ها مخصوصا در فرگمنت و اداپتر ها برای پیروی لایه از Theme جاری برنامه از Context اکتیویتی بجای Application استفاده کنید چون تم در Main Acitivy ست شده و باید از کانتکست اون استفاده بشه :
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { return ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.row_profile_avatar , parent , false)) }
و مورد سوم که شاید شما لایه ها و ویو هاتون رو بصورت کد جاوا یا کاتلین و بدون xml بنویسید که برای گرفتن تم و ست کردن رنگ ها از کد زیر میتونیم استفاده کنیم :
private fun getColorFromAttr(context:Context , attrResId: Int): Int { val typedValue = TypedValue() val a = context.obtainStyledAttributes(typedValue.data, intArrayOf(attrResId)) val color = a.getColor(0, 0) a.recycle() return color }
که حتما Context اکتیویتی و اون attribute که مدنظرتون رو بدید و رنگ دریافتی رو ست کنید.
lastEnableTabTitle?.setTextColor(theme.getColorFromAttr(context ,R.attr.appTabDisableColor))
خب این هم از پیاده سازی Theme در برنامه های اندرویدی که امیدوارم مفید بوده باشه براتون .