Android Set Theme Dynamically

0

In this article we will see how to change the theme dynamically. We want to provide a new setting which will allow one to choose from a list of themes. After the theme is selected, when we go back to our activity, we want the theme to be applied.

Steps include the following:

  1. If you want to use any custom attributes, define those attributes first.
  2. If you are relying on colors or dimensions, define the values and associate them with proper names.
  3. Create few themes. When you group a set of style attributes and give a name to it, it becomes your theme.
  4. Create your main screen. You should be able to set the layout attributes to the custom attributes defined.
  5. Apply theme programatically before you call the super.onCreate() and setContentView()
  6. You will set the theme calling Activity.setTheme(resource)

Let’s try the above steps on our example.

Define Attributes

We will define few custom attributes using attr element. It has a name attribute and format. This is just like declaring your program variables.

attrs.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <attr name="background" format="color" />
    <attr name="textColor" format="color" />
    <attr name="textSize" format="dimension" />

</resources>

Define Themes

Now we will create our themes. Before that you can define some style attributes and assign a value. For example, below we define few color resources.

colors.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="welcome_text_color">#FF0000</color>
    <color name="sky_blue">#6698FF</color>
    <color name="green_apple">#629632</color>
</resources>

Now we will group our custom attributes together, give a name to the style and assign some values to them. We have created two themes Theme1 and Theme2.
Each theme consists of textColoe, background and textSize.

styles.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android">

    <style name="Theme1">
        <item name="textColor">@color/green_apple</item>
        <item name="background">#F4F0CB</item>
        <item name="textSize">15sp</item>
    </style>

    <style name="Theme2">
        <item name="textColor">@color/sky_blue</item>
        <item name="background">#ffffff</item>
        <item name="textSize">35sp</item>
    </style>

</resources>

Main Screen

In our main screen, we will refer to our custom attributes using ?attr, for example ?attr/textColor. Once a theme is applied, the actual value will replace the placeholder.

welcome.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="?attr/background"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin" >

    <TextView
        android:id="@+id/welcome"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="40dp"
        android:layout_marginTop="18dp"
        android:text="@string/welcome"
        android:textColor="?attr/textColor" />

    <Button
        android:id="@+id/apply_theme_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="3dip"
        android:background="?attr/textColor"
        android:paddingBottom="10dip"
        android:paddingTop="10dip"
        android:text="@string/OK"
        android:textColor="?attr/background" />

</LinearLayout>

Theme Settings

In order to know which theme to apply, we will use android’s perferences. We will define a ListPreference which will display a list of theme names. User will select one of them.
The theme values are specified in string-array.

preferences.xml:

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
    <ListPreference 
        android:defaultValue="@string/def_theme"
        android:entries="@array/themes"
        android:entryValues="@array/themes"
        android:key="theme"
        android:title="@string/theme_dialog_title"
        android:summary="@string/theme_summary"
        android:dialogTitle="@string/theme_dialog_title"/>

</PreferenceScreen>

Our theme values will be defined in themes string-array.

strings.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">JavArticles<;/string>
    <string name="welcome">Theme Example<;/string>

    <string-array name="themes">
        <item>Theme1<;/item>
        <item>Theme2<;/item>
    </string-array>

    <string name="OK">OK<;/string>
    <string name="def_theme">Theme1<;/string>
    <string name="theme_summary">Appearance<;/string>
    <string name="theme_dialog_title">Theme<;/string>
    <string name="settings">Settings<;/string>

</resources>

Add Themes to Settings

ThemePreferenceActivity is our PreferenceActivity. We will set its content view to preferences.xml using addPreferencesFromResource
The user will chose the theme from the displayed themes. After the selection when we navigate back to the previous activity, we want the theme to be applied which means the activity which called the prefernces activity should get notified of the change when it returns back to the activity.
We will set an OnPreferenceChangeListener to set a result code. This is done so that the Activity which called the perference activity gets notified and from the result code we know whether the theme preference was changed. The result code will be accessed by the Activity class in onActivityResult.

ThemePreferenceActivity:

package com.javarticles.android;


import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.Preference.OnPreferenceChangeListener;

public class ThemePreferenceActivity extends PreferenceActivity {
	public static final int RESULT_CODE_THEME_UPDATED = 1;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		addPreferencesFromResource(R.xml.preferences);
		findPreference("theme").setOnPreferenceChangeListener(new RefershActivityOnPreferenceChangeListener(RESULT_CODE_THEME_UPDATED));
	}
	
    private class RefershActivityOnPreferenceChangeListener implements OnPreferenceChangeListener {
        private final int resultCode;
        public RefershActivityOnPreferenceChangeListener(int resultCode) {
            this.resultCode = resultCode;
        }

        @Override
        public boolean onPreferenceChange(Preference p, Object newValue) {
            setResult(resultCode);
            return true;
        }
    }
}

Main Activity

Before super.onCreate and setContentView is called, we read our theme from the preferences and apply using setTheme method. When we change our theme from settings, the main activity should get notified of the change so that it can restart itself and which in turn will re-apply the new theme. This is important so that the new theme gets applied dynamically.

The trick here is to call the perferences activity using startActivityForResult.

startActivityForResult(new Intent(this,	ThemePreferenceActivity.class), SETTINGS_ACTION);

We pass a request code along with the activity class name. We will get this request code back in the callback onActivityResult(). This way we will know from which activity we are coming back.

In the main activity’s onActivityResult(), we will look into the request code and result code. The request code will help us to know something about previous activity. Likewise, we can use the result code to hint about some action that we performed in our previous activity.

MainActivity:

package com.javarticles.android;

import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;

public class MainActivity extends Activity {
	private int SETTINGS_ACTION = 1;

	public void onCreate(final Bundle savedInstanceState) {
		SharedPreferences pref = PreferenceManager
				.getDefaultSharedPreferences(this);
		String themeName = pref.getString("theme", "Theme1");
		if (themeName.equals("Theme1")) {
			setTheme(R.style.Theme1);
		} else if (themeName.equals("Theme2")) {
			Toast.makeText(this, "set theme", Toast.LENGTH_SHORT).show();
			setTheme(R.style.Theme2);
		}
		Toast.makeText(this, "Theme has been reset to " + themeName,
				Toast.LENGTH_SHORT).show();
		super.onCreate(savedInstanceState);
		setContentView(R.layout.welcome);
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		getMenuInflater().inflate(R.menu.menu, menu);
		return super.onCreateOptionsMenu(menu);
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		int id = item.getItemId();
		switch (id) {
		case R.id.settings:
			startActivityForResult(new Intent(this,
					ThemePreferenceActivity.class), SETTINGS_ACTION);
		}
		return super.onOptionsItemSelected(item);
	}

	@Override
	public void onActivityResult(int requestCode, int resultCode, Intent data) {
		if (requestCode == SETTINGS_ACTION) {
			if (resultCode == ThemePreferenceActivity.RESULT_CODE_THEME_UPDATED) {
				finish();
				startActivity(getIntent());
				return;
			}
		}
		super.onActivityResult(requestCode, resultCode, data);
	}

}

Reset Theme Dynamically

Main screen opens with the default Theme.

Default Theme

Default Theme

Let’s change the theme use the settings.

Change Theme in Settings

Change Theme in Settings

Reset the theme to Theme2.

Set the theme to Theme2

Set the theme to Theme2

Theme2 is applied, Main screen look is changed.

Theme2 applied

Theme2 applied

Download source code

This was an example about applying themes dynamically. You can download the source code here: themeExample.zip

Share.

Comments are closed.