Runtime/Dynamically Change Android Application Theme

Before android lollipop most of the apps provides only two or three themes to change at run time because they have already bundled image object for that theme color. I mean images and all selector color are defined and drawable already created with all theme colors because there is no primary color and color accent attribute that allow you to change color dynamically before lollipop. In Lollipop they have add selector that change all objects like edit text and switch with the theme color and that's very cool. All object look similar and contrasting to your theme.

So how the app developer change themes run time ?
How to change full app theme run time?

Well for answer of above questions go through full tutorial and you will learn how application default theme changed with newly selected color at run time.

androprogrammer.com


Steps

Step - 1
Create activity where user can select theme color. in this tutorial it is ChangeThemeDemo. I have taken only two colors but you can add as many as you want. It is really lengthy process but if you go step by step it will be so much easy for you. Now create theme.xml in values folder where you will define all themes that you want to implement. And user can change theme from it run time.

values/themes.xml

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

    <style name="AppTheme.Green" parent="Theme.AppCompat.Light">
        <item name="colorPrimary">@color/primary_green</item>
        <item name="colorPrimaryDark">@color/primary_dark_green</item>
        <item name="colorAccent">@color/accent_green</item>
        <item name="colorControlHighlight">@color/primary_green</item>
        <item name="android:textColorPrimary">@color/primary_text</item>
        <item name="android:textColorSecondary">@color/secondary_text</item>
    </style>

    <style name="AppTheme.Purple" parent="Theme.AppCompat.Light">
        <item name="colorPrimary">@color/primary_purple</item>
        <item name="colorPrimaryDark">@color/primary_dark_purple</item>
        <item name="colorAccent">@color/accent_purple</item>
        <item name="colorControlHighlight">@color/primary_purple</item>
        <item name="android:textColorPrimary">@color/primary_text</item>
        <item name="android:textColorSecondary">@color/secondary_text</item>
    </style>

</resources>

values/color.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="primary_green">#52bf90</color>
    <color name="primary_dark_green">#398564</color>
    <color name="accent_green">#FF4081</color>
    <color name="activity_bg">#e4e5e5</color>
    <color name="primary_purple">#673AB7</color>
    <color name="primary_dark_purple">#512DA8</color>
    <color name="primary_light">#D1C4E9</color>
    <color name="accent_purple">#FF9800</color>
    <color name="primary_text">#212121</color>
    <color name="secondary_text">#727272</color>
    <color name="icons">#FFFFFF</color>
    <color name="divider">#B6B6B6</color>
    <color name="activity_bg_black">#374046</color>
</resources>

Ok, now your basic theme is set. i have created themes.xml file for only declaring base of theme. You can create your hole theme over here as i have created in styles.xml. check out below code for different themes i have created light and dark and both version of my theme in it.

values/styles.xml


<resources>

    <style name="AppTheme.Base.Green" parent="AppTheme.Green">
        <item name="drawerArrowStyle">@style/DrawerArrowStyle</item>
        <item name="windowActionModeOverlay">true</item>
        <item name="windowActionBarOverlay">true</item>
        <item name="android:windowActionBarOverlay">true</item>
        <item name="android:windowBackground">@color/activity_bg</item>
        <item name="windowActionBar">false</item>
        <item name="windowNoTitle">true</item>
    </style>

    <style name="AppTheme.Base.Green.Dark" parent="AppTheme.Green">
        <item name="drawerArrowStyle">@style/DrawerArrowStyle</item>
        <item name="windowActionModeOverlay">true</item>
        <item name="windowActionBarOverlay">true</item>
        <item name="android:windowActionBarOverlay">true</item>
        <item name="android:windowBackground">@color/activity_bg_black</item>
        <item name="windowActionBar">false</item>
        <item name="windowNoTitle">true</item>
    </style>

    <style name="AppTheme.Base.Purple" parent="AppTheme.Purple">
        <item name="drawerArrowStyle">@style/DrawerArrowStyle</item>
        <item name="windowActionModeOverlay">true</item>
        <item name="windowActionBarOverlay">true</item>
        <item name="android:windowActionBarOverlay">true</item>
        <item name="android:windowBackground">@color/activity_bg</item>
        <item name="windowActionBar">false</item>
        <item name="windowNoTitle">true</item>
    </style>

    <style name="AppTheme.Base.Purple.Dark" parent="AppTheme.Purple">
        <item name="drawerArrowStyle">@style/DrawerArrowStyle</item>
        <item name="windowActionModeOverlay">true</item>
        <item name="windowActionBarOverlay">true</item>
        <item name="android:windowActionBarOverlay">true</item>
        <item name="android:windowBackground">@color/activity_bg_black</item>
        <item name="windowActionBar">false</item>
        <item name="windowNoTitle">true</item>
    </style>

    <!-- Base application themes. -->
    <style name="ThemeApp.Green" parent="AppTheme.Base.Green"/>
    <style name="ThemeApp.Green.Dark" parent="AppTheme.Base.Green.Dark"/>
    <style name="ThemeApp.Purple" parent="AppTheme.Base.Purple"/>
    <style name="ThemeApp.Purple.Dark" parent="AppTheme.Base.Purple.Dark"/>


    <style name="DrawerArrowStyle" parent="Widget.AppCompat.DrawerArrowToggle">
        <item name="spinBars">true</item>
        <item name="color">@android:color/white</item>
    </style>

</resources>


Step - 2
In step - 1 we have set all things for themes. now in this step i am going to create layout for theme chooser. you can create as you want but for basic setup you can put below code into your activity. it will allow user to select weather they want to apply light or dark theme. i have directly given values for text and padding and margin but for this tutorial only. it is good practice that you declare it in strings.xml and dimen.xml and then apply here. so it will be reusable.

layout/activity_changetheme_demo.xml

<RelativeLayout 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:paddingTop="?attr/actionBarSize"
    tools:context="com.androprogrammer.tutorials.samples.ChangeThemeDemo">

    <LinearLayout
        android:id="@+id/layout_header"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:background="?attr/colorPrimary">


        <android.support.v7.widget.SwitchCompat
            android:id="@+id/switch_darkTheme"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dip"
            android:layout_marginLeft="10dip"
            android:layout_marginRight="5dip"
            android:textColor="@android:color/white"
            android:text="Dark Theme"
            tools:text="Dark Theme"/>

    </LinearLayout>

    <LinearLayout
        android:layout_below="@id/layout_header"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dip"
        android:padding="10dip"
        android:background="@android:color/white"
        android:id="@+id/Layout_green">

        <LinearLayout
            android:layout_width="30dip"
            android:layout_height="35dip"
            android:gravity="center"
            android:clickable="false"
            android:elevation="5dip"
            android:background="@color/primary_green"/>

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Green"
            android:gravity="center"
            android:clickable="false"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:padding="8dip"
            android:layout_marginLeft="10dip"/>

    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dip"
        android:padding="10dip"
        android:layout_below="@id/Layout_green"
        android:background="@android:color/white"
        android:id="@+id/Layout_purple">

        <LinearLayout
            android:layout_width="30dip"
            android:layout_height="35dip"
            android:gravity="center"
            android:clickable="false"
            android:elevation="5dip"
            android:background="@color/primary_purple"/>

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Purple"
            android:gravity="center"
            android:clickable="false"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:padding="8dip"
            android:layout_marginLeft="10dip"/>

    </LinearLayout>

</RelativeLayout>


Step - 3
Now to change theme in overall screen in your application you have to put below code in your Base or Main Activity so when ever you start app your your new themes gets apply for whole app. in my case i have BaseActivty which i am extending in all other activities so if i put that code in that activity my whole app themes get changed. MainController is application class in my app you can find code here. you have to put below code before calling super.onCreate(savedInstanceState); because setTheme Method will change theme through out the app.

if (!MainController.preferenceGetString("AppliedTheme","").equals(""))
        {
            if (MainController.preferenceGetString("AppliedTheme","").equals("Green"))
            {
                setTheme(R.style.ThemeApp_Green);
            }
            else if (MainController.preferenceGetString("AppliedTheme","").equals("Green_Dark"))
            {
                setTheme(R.style.ThemeApp_Green_Dark);
            }
            else if (MainController.preferenceGetString("AppliedTheme","").equals("Purple_Dark"))
            {
                setTheme(R.style.ThemeApp_Purple_Dark);
            }
            else if (MainController.preferenceGetString("AppliedTheme","").equals("Purple"))
            {
                setTheme(R.style.ThemeApp_Purple);
            }
        }
        else
        {
            setTheme(R.style.ThemeApp_Green);
        }

As i have already told, you don't have to use strings directly you have to define in strings.xml so if you want to change some thing you have to change it at only one place.



Step - 4
Well now all set, now put below code in Java class in this tutorial it is ChangeThemeDemo.java. where you have to change preference on the basis of user selection. which theme is selected by user and weather light version or dark.

samples/ChangeThemeDemo.java

package com.androprogrammer.tutorials.samples;


import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.TaskStackBuilder;
import android.support.v7.widget.SwitchCompat;
import android.transition.Explode;
import android.transition.Transition;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.widget.CompoundButton;
import android.widget.LinearLayout;

import com.androprogrammer.tutorials.MainController;
import com.androprogrammer.tutorials.R;
import com.androprogrammer.tutorials.activities.Baseactivity;
import com.androprogrammer.tutorials.activities.Listactivity;


/**
 * Created by Wasim on 15-08-2015.
 */
public class ChangeThemeDemo extends Baseactivity implements View.OnClickListener{

    protected View view;
    private LinearLayout layout_green, layout_purple;
    private SwitchCompat switch_dark;
    private boolean isDarkTheme;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
        {
            getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);

            //set the transition
            Transition ts = new Explode();
            ts.setDuration(5000);
            getWindow().setEnterTransition(ts);
            getWindow().setExitTransition(ts);
        }

        super.onCreate(savedInstanceState);

        setReference();

        setToolbarElevation(0);

        setToolbarSubTittle(this.getClass().getSimpleName());

        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
    }

    @Override
    public void setReference() {
        view = LayoutInflater.from(this).inflate(R.layout.activity_changetheme_demo,container);

        switch_dark = (SwitchCompat) view.findViewById(R.id.switch_darkTheme);
        layout_green = (LinearLayout) view.findViewById(R.id.Layout_green);
        layout_purple = (LinearLayout) view.findViewById(R.id.Layout_purple);

        switch_dark.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {

                if (isChecked)
                {
                    isDarkTheme = true;
                    MainController.preferencePutBoolean("DarkTheme", true);
                }
                else {
                    isDarkTheme = false;
                    MainController.preferencePutBoolean("DarkTheme", false);
                }
            }
        });

        if (MainController.preferenceGetBoolean("DarkTheme", false))
        {
            switch_dark.setChecked(true);
        }

        layout_green.setOnClickListener(this);
        layout_purple.setOnClickListener(this);
    }


    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        switch (item.getItemId()) {
            // Respond to the action bar's Up/Home button
            case android.R.id.home:
                finish();
                break;
        }

        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onClick(View v) {

        switch (v.getId())
        {
            case R.id.Layout_green:
                if (isDarkTheme)
                    MainController.preferencePutString("AppliedTheme", "Green_Dark");
                else
                    MainController.preferencePutString("AppliedTheme", "Green");

                TaskStackBuilder.create(this)
                        .addNextIntent(new Intent(this, Listactivity.class))
                        .addNextIntent(getIntent())
                        .startActivities();
                break;

            case R.id.Layout_purple:

                if (isDarkTheme)
                    MainController.preferencePutString("AppliedTheme", "Purple_Dark");
                else
                    MainController.preferencePutString("AppliedTheme", "Purple");

                TaskStackBuilder.create(this)
                        .addNextIntent(new Intent(this, Listactivity.class))
                        .addNextIntent(getIntent())
                        .startActivities();
                break;
        }

    }
}


After changing theme you have to restart your activity. you also have to change themes in previous screens also. because they are already opened and in Activity stacktrace. So to change theme in it  also i have created TaskStackBuilder. which allow us to recreate Activity stack trace. in my case i have given only two intents but you have to pass activity hierarchy so user don't know that whole app is restarted.

TaskStackBuilder.create(this)
                        .addNextIntent(new Intent(this, Listactivity.class))
                        .addNextIntent(getIntent())
                        .startActivities();

That's it from my side. now run the app and see the result. if you have any query or suggestion please let me know in below comment box.

keep coding...

Source code

2 comments :

  1. Nice Article. Want to ask that
    "TaskStackBuilder.create(this)
    .addNextIntent(...)
    .startActivities();" will run on Android 2.3.7 or not?

    ReplyDelete
    Replies
    1. Thanks Kasim,
      You can learn more about Taskbuilder from official doc - https://developer.android.com/reference/android/support/v4/app/TaskStackBuilder.html

      As you can see it is in v4 library so it will works in android 2.3.

      Delete