پیاده سازی Theme در برنامه های اندرویدی

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

البته من فقط بیس رو میگم و خروجی این نیست :)))
البته من فقط بیس رو میگم و خروجی این نیست :)))


خب ما در برنامه آیگپ از چندین تم و رنگ های مختلفی رو پشتیبانی میکنیم و دست کاربر در انتخاب رنگ و ظاهر برنامه بازه ، خیلی ها مثل من پرسپولیسی اند و تم قرمز رو انتخاب میکنن و خیلیا با حالت شب (Dark Theme) احساس بهتری حین استفاده از برنامه دارند و هر کسی طبق سلیقه خودش میتونه رنگ و تم مورد علاقه ش رو انتخاب کنه .

برای پیاده سازی این مبحث اگر پروژه ای دارید که شروع نکردید کار سختی جز یسری کانفیگ ها ندارید و اگر هم پروژه طراحی شده و بزرگی دارید سخت ترین کارش تغییر لایه هاست و رنگ های ثابت رو به Attribute ها تغییر بدین که نسبت به تعداد لایه ها کار زمان بری میتونه باشه.

از طرف دیگ پشتیانی Theme در اندروید زیر ۵ (Api 21) هم درد سر های خاص خودش رو داره که اگر حواستون نباشه باگ های زیادی رو متاسفانه برای طیف وسیعی از این کاربران :( بوجود میاره ...



خب بعد از توضیح یکسری موارد بریم برای پیاده سازی Theme در برنامه هامون:

برای اینکار بهتره ساختار برنامه تون از Single Activity پشتیبانی کنه ولی باز هم با همون ساختار اکتیویتی هم میتونید پیاده سازی کنید که نیاز به یسری کد های اضافه و مدیریت اون در هر اکتیویتی داره.

برای نگهداری وضعیت تم انتخابی کاربر میتونیم از Shared Preferences استفاده کنیم و وضعیت رو در اون ذخیره و بازیابی کنیم .

https://gist.github.com/alirezanazari/5ee9a04153b2214a301c494c90c1efce

همونطور که در کد بالا میبینید با استفاده از enum تم Dark و Light رو تعریف و در Shared Preferences با استفاده از متد ها ذخیره و بازیابی میکنم .

در اولین متد یعنی getTheme یسری استایل با بررسی وضعیت تم انتخابی برگشت داده میشه که این Style رو در MainActivity و متد OnCreate و قبل از صدا زدن Super اون مقدار دهی میکنیم.

https://gist.github.com/alirezanazari/f37606361ec1b634f20ab72bb08bcd71

خب حالا این استایل ها چی اند ؟

ما تو اکثر پروژه ها یسری رنگ های ثابت داریم ; رنگ عنوان ها ، متون معمولی ، رنگ پس زمینه ، رنگ تولبار ، ساب تایتل ها ، دکمه ها و... که ما این رنگ ها رو بصورت Attribute تعریف و در لایه هامون به جای رنگ های ثابت استفاده میکنیم .

برای اینکار فایل attrs.xml رو میسازیم و attribute ها و موارد ثابتی که بالا شمردیم رو تعریف میکنیم ، که فرمت هر attr میتونه color باشه که در TextColor , HintColor , Background و هرجایی که رنگ ثابت میدادیم و reference برای جاهایی که drawable , shape میگیرن مقدار دهی کنیم .

https://gist.github.com/alirezanazari/9a6bf225b6bffac390cf44d9d504894c


خب بعد از این کار در فایل styles.xml با تعریف رنگ های Accent و Primary و همینطور فونت و.. یک فایل در پوشه res بنام themes.xml می سازیم که از BaseTheme تعریف شده ارث میبرن و رنگ هامون در هر تم رو مشخص میکنند.

https://gist.github.com/alirezanazari/8e52d02aaac5d576199078d90f433a02

همونطور که تو کد های بالا میبینید تمام رنگ ها و Attribute های که در پروژه و نسبت به UI نیاز بود رو پیاده سازی کردیم .

در کد بالا علاوه بر یسری رنگ ها Drawable هایی هم دیده میشه که ما میتونیم در لایه ها استفاده کنیم .

خب میرسیم به استفاده از این مقادیر در لایه های برنامه مون که برای این کار به راحتی بجای رنگ و مقادیر ثابت با علامت ? و نام اون Attribute لایه مون رو رنگ بندی میکنیم.

<ImageView
    android:id=&quot@+id/icSeen&quot
    android:layout_width=&quot@dimen/icons_size&quot
    android:layout_height=&quot@dimen/icons_size&quot
    android:tint=&quot?appTextTitleColor&quot
    app:srcCompat=&quot@drawable/ic_eye&quot />

<TextView
    android:layout_width=&quotwrap_content&quot
    android:layout_height=&quotwrap_content&quot
    android:textColor=&quot?appTextTitleColor&quot
    android:textSize=&quot@dimen/medium_textSize&quot
    tools:text=&quotقسمت ۰۱&quot />

برای همه بخش ها به این صورت عمل میکنیم و رنگ ها رو با استفاده از 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=&quotrectangle&quot
    xmlns:android=&quothttp://schemas.android.com/apk/res/android&quot>

    <solid
        android:color=&quot?cardBackgroundColor&quot/>

    <corners
        android:radius=&quot@dimen/dp10&quot/>

</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 در برنامه های اندرویدی که امیدوارم مفید بوده باشه براتون .

وب سایت من