Android Team Lead at blu Bank
پیاده سازی Theme در برنامه های اندرویدی
سلام !! تو این مقاله میخوام باهم تم یا چند رنگی رو در برنامه های اندرویدی رو بررسی کنیم و اینکه 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 در برنامه های اندرویدی که امیدوارم مفید بوده باشه براتون .
مطلبی دیگر از این انتشارات
چطور برنامه نویسی اندروید یاد بگیریم و پت و مت نشیم؟! راهنمایی برای مبتدی ها و بقیه
مطلبی دیگر از این انتشارات
اموزش کامل Navigation component در اندروید- قسمت اول
مطلبی دیگر از این انتشارات
درک clean code در اندروید