Android : Colors and Multiple Themes
Recently, we came across a bunch of Android projects which required a change of theme or addition of a new theme. Some even want to provide multi theme support. But since these applications were not developed to support theme change it took a couple of months to make the changes and go to production. Sometimes, the changes can be too big for the business to invest its time and money just to change a few colors (well according to the business it’s “just colors”).
Learning : Always develop the application keeping in mind that the colors of an app can change at any point in time and the least possible time should be spend in making the change. Android system provides very good support for multiple themes and applying it from the beginning makes things easy.
So, In this article we will share an approach that we recommend Android devs to follow so that no one runs into this problem AGAIN!
Step 1 : Let’s start by creating color resources for a theme.
values/colors.xml
<resources> <color name="colorPrimary">#720d5d</color> <color name="colorPrimaryDark">#430033</color> <color name="colorAccent">#e30425</color> <color name="primaryText">#000000</color> <color name="buttonNormal">#e30425</color> <color name="buttonHighlight">#ff554f</color> <color name="buttonText">#FFFFFF</color> </resources>
Step 2 : Now, for applying color to views create custom attributes. To define custom attribute, you have to add a new file in “values” folder and name it “attrs.xml”. Inside this xml file, there will be one xml tag <resources/>
. All attributes will be declared and defined inside this tag as shown below:
values/attrs.xml
<resources> <attr name="primaryTextColor" format="reference"/> <attr name="buttonNormalColor" format="reference"/> <attr name="buttonHighlightColor" format="reference" /> <attr name="buttonTextColor" format="reference" /> <attr name="primaryButtonTheme" format="reference" /> </resources>
Step 3 : Values of custom attributes can be defined in the app theme to be used across the application.
values/themes.xml
<style name="Theme.App" parent="Theme.AppCompat.Light.DarkActionBar"> <item name="primaryButtonTheme"> @style/Widget.Button.Primary </item> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> <item name="primaryTextColor">@color/primaryText</item> <item name="buttonNormalColor">@color/buttonNormal</item> <item name="buttonHighlightColor">@color/buttonHighlight</item <item name="buttonTextColor">@color/buttonText</item> </style>
Step 4 : Next step is to create layout and use custom attributes for styling. Custom attributes can be applied to views using ?attr/yourAttribute
. You can also use default android attributes ?android:attr/androidAttribute
. Material design also uses custom attributes to define app colors “colorPrimary”, “colorPrimaryDark” and “colorAccent” are all custom attributes android has defined.
values/layout.xml
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin"
android:text="@string/sample_text"
android:textColor="?attr/primaryTextColor"
android:textSize=”@dimen/primary_text_size” />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/margin"
android:text="Button"
android:theme="?attr/primaryButtonTheme" />
We can also use custom attributes to set values for widget themes
values/themes.xml
<style name="Widget.Button.Primary" parent="Widget.AppCompat.Button"> <item name="colorButtonNormal">?attr/buttonNormalColor</item> <item name="colorControlHighlight"> ?attr/buttonHighlightColor </item> <item name="android:textColor">?attr/buttonTextColor</item> </style>
Here’s how your layout looks. Now you are ready with your screen where a new theme can be easily applied without any modification required on the layouts.
So let’s try it out, let’s see how long does it take to create an entirely new theme and apply it to our existing layout. We have to repeat the process to create a new theme and define its colors.
Name the existing theme “lilac” and the new theme “mint”.
values/colors.xml
<color name="mintColorPrimary">#76be2f</color> <color name="mintColorPrimaryDark">#289b23</color> <color name="mintColorAccent">#f9a825</color> <color name="mintPrimaryText">#424242</color> <color name="mintButtonNormal">#76be2f</color> <color name="mintButtonHighlight">#81c784</color> <color name="mintButtonText">#FFFFFF</color>
values/themes.xml
<style name="Theme.App.Mint" parent="Theme.AppCompat.Light.DarkActionBar"> <item name="primaryButtonTheme"> @style/Widget.Button.Primary </item> <item name="colorPrimary">@color/mintColorPrimary</item> <item name="colorPrimaryDark">@color/mintColorPrimaryDark</item> <item name="colorAccent">@color/mintColorAccent</item> <item name="primaryTextColor">@color/mintPrimaryText</item> <item name="buttonNormalColor">@color/mintButtonNormal</item> <item name="buttonHighlightColor"> @color/mintButtonHighlight </item> <item name="buttonTextColor">@color/mintButtonText</item> <item name="iconColor">@color/mintIcon</item> </style>
Voila! New theme works for our app without doing any modifications on the layout. But there is a duplicity in our themes, both use the same style for button, so we can do a small refactoring and create a base theme to resolve it.
values/themes.xml
<style name="Base.Theme.App" parent="Theme.AppCompat.Light.DarkActionBar"> <item name="primaryButtonTheme">@style/Widget.Button.Primary</item> </style>
<style name="Theme.App.Lilac" parent="Base.Theme.App"> <item name="colorPrimary">@color/colorPrimary</item> ... </style>
<style name="Theme.App.Mint" parent="Base.Theme.App"> <item name="colorPrimary">@color/mintColorPrimary</item> ... </style>
We are done, aren’t we? Well not quite, with this we will only be able to apply the new theme in the manifest i.e the new theme can be applied only with a new version of the apk. But what if we want to apply it at runtime. How to achieve that? We will start by providing another screen to select the new theme. Follow the steps below:
Step 1 : Save your theme in SharedPreference and invoke recreate(). Recreate needs to be invoked to recreate the activity so that the new theme can be applied on the current screen.
sharedPref .edit() .putString( “current_theme”, “mint”) .apply(); recreate();
Step 2 : Since content view is rendered based on the theme, we have to set the new theme in onCreate() before you set the content view
override fun onCreate() { super.onCreate() val sharedPref = PreferenceManager.getDefaultSharedPreferences(this) val currentTheme = sharedPref.getString(“current_theme”, “lilac”) if (currentTheme == “lilac”) setTheme(R.style.Theme_App_Lilac) else setTheme(R.style.Theme_App_Mint) setContentView(...) }
That’s it the new theme is applied to your activity. Now let’s go back to the previous activity. Why does it still have the old theme? Well the new theme was only applied to the Settings activity and not to the whole application. So, we need to apply the new theme on the previous activity as well.
Step 3 : Check for current theme and recreate the activity in onResume
override fun onResume() { super.onResume(); val theme = sharedPref.getString(“current_theme”, “lilac”) if (currentTheme != theme) recreate() }
Since, we need to apply the new theme on all the activities in the backstack, this implementation is required for all activities. Once again there will be code duplicity, and to remove code duplicity we will create BaseActivity and perform the check there so that the new theme can be applied to all the activities throughout the app.
Step 4 : Create BaseActivity and perform new theme check. All activities will now extend BaseActivity and will get the benefit of setting theme at runtime without any additional code.
open class BaseActivity : AppCompatActivity() { private lateinit var currentTheme: String private lateinit var sharedPref: SharedPreferences
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) sharedPref = PreferenceManager .getDefaultSharedPreferences(this) currentTheme = sharedPref.getString("current_theme", “lilac”) setAppTheme(currentTheme) } override fun onResume() { super.onResume() val theme = sharedPref.getString("current_theme", “lilac”) if(currentTheme != theme) recreate() } private fun setAppTheme(currentTheme: String) { when (currentTheme) { MINT_THEME -> setTheme(R.style.Theme_App_Mint) else -> setTheme(R.style.Theme_App_Lilac) } } }
Congratulations! You just used colors with multiple themes and applying new colors/theme to your app will now just be a matter of time. Go ahead, create your own theme and apply it to your application. Comment below and let us know what techniques did you use to apply the new theme and how long did it take.
vinitagrawal/Multi-Theme-SupportMulti-Theme-Support - This is a project to showcase the usage of colors with multiple themes for an Android applicationgithub.com