Inzimam Tariq IT
Inzimam Tariq IT

Reputation: 6758

How to consider variation while switching theme?

I'm Working on an android app that has a requirement to switch theme based on the themeCode given from server. I'm using sharePref to save the theme code and applying it with setTheme(R.style.themeName);. Its working fine till the basic theme attributes like

colorPrimary
colorPrimaryDark
colorAccent
windowActionBar
windowNoTitle

For this I has created different styles in styles.xml. But I have a limitation that some fields say EditText has variation as EditText

And similarly TextView has variation as TextView

Before multi-theme requirement I had created separate themes for all as

Now I have viewed many tutorials/posts but did not find solution to the variations in attribute. Please help to apply these variation, I'll be thankful for this.

Regards: Inzimam Tariq

Upvotes: 5

Views: 147

Answers (2)

lelloman
lelloman

Reputation: 14183

What you can do is create custom attributes for your view types (e.g. TextView.Person, TextView.Date...), in your xml you can reference the attributes and then define the attributes in different themes. For instance, you style.xml could be

<resources>

    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar" >
        <item name="TextView.Date">@style/DateTextViewDefault</item>
    </style>

    <style name="DateTextViewDefault">
        <item name="android:textColor">#ff333333</item>
        <item name="android:fontFamily">monospace</item>
    </style>

    <!-- Theme A -->
    <style name="AppTheme.A">
        <item name="colorPrimary">#3F51B5</item>
        <item name="colorPrimaryDark">#303F9F</item>
        <item name="colorAccent">#FF4081</item>
        <item name="TextView.Person">@style/PersonTextViewA</item>
    </style>

    <style name="PersonTextViewA">
        <item name="android:textSize">16sp</item>
        <item name="android:fontFamily">serif</item>
        <item name="android:textColor">#ff999999</item>
    </style>

    <!-- Theme B -->
    <style name="AppTheme.B">
        <item name="colorPrimary">#888888</item>
        <item name="colorPrimaryDark">#555555</item>
        <item name="colorAccent">#000000</item>
        <item name="TextView.Person">@style/PersonTextViewB</item>
        <item name="TextView.Date">@style/DateTextViewB</item>
    </style>

    <style name="PersonTextViewB">
        <item name="android:textSize">20sp</item>
        <item name="android:fontFamily">monospace</item>
        <item name="android:textColor">#ff55aa</item>
    </style>

    <style name="DateTextViewB">
        <item name="android:textColor">#ff0000BB</item>
        <item name="android:fontFamily">sans-serif</item>
    </style>

    <attr name="TextView.Person" format="reference" />
    <attr name="TextView.Date" format="reference" />

</resources>

then your activity xml layout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        style="?attr/TextView.Person"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="John Doe" />

    <TextView
        style="?attr/TextView.Date"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="31/12/1999" />

    <Button
        android:id="@+id/buttonA"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="THEME A" />

    <Button
        android:id="@+id/buttonB"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="THEME B" />

</LinearLayout>

notice that the styles of the TextView are

style="?attr/TextView.Person"

and

style="?attr/TextView.Date"

AppTheme.A and AppTheme.B have 2 different resolutions for those attributes

In this example the attributes are entire styles for your views but you could easily have one style per view type (TextView.person) and then define generic attributes for single items of that style, e.g.

<attr name="TextView.Person.TextColor" format="color" />

and only change that single attribute in your themes.

Then in your Activity you just need to set the theme in onCreate with setTheme(int), the value could be in this case either R.style.AppTheme_A or R.style.AppTheme_B.

With this method you can add as many styles as you want without touching the layouts. Also, you can always define some default styles in your base theme and then only override that value in some of the custom themes, while others use the default one as for TextView.Date in the sample above.

If you want to give it a quick try, here's the code of the Activity I used to test style.xml and activity_main.xml above

class MainActivity : AppCompatActivity() {

    private val prefs by lazy { getSharedPreferences("SharedPrefs", Context.MODE_PRIVATE) }

    private var customTheme: Int
        get() = prefs.getInt("theme", R.style.AppTheme_A)
        set(value) = prefs.edit()
                .putInt("theme", value)
                .apply()
                .also { recreate() }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setTheme(customTheme)
        setContentView(R.layout.activity_main)

        buttonA.setOnClickListener { customTheme = R.style.AppTheme_A }
        buttonB.setOnClickListener { customTheme = R.style.AppTheme_B }
    }
}

Upvotes: 0

Dhaval Patel
Dhaval Patel

Reputation: 340

In my opinion changing app theme at runtime, will definitely need to reload activity; this in most cases will create issues at some point (if project is extended to a mid scale, having a user control like toggle or a switch and if user taps switch repeatedly app may easily crash)


I would suggest to use custom control classes (Textviews, Buttons..etc); wherein this properties are differentiated with current theme value from sharedPref. This approach has a con; it will require to change all views manually of current screen and those in already rendered in memory(if any), rest all it will be much smoother transition in compare to our conventional approach

EDIT: Example for CustomTextView ##

This is an example for customtextview class

public class CustomTextView extends android.support.v7.widget.AppCompatTextView {
private static final String TAG = "TextView";
private Typeface tf = null;
private SharedPreferenceUtils preferenceUtils = SharedPreferenceUtils.getInstance();

/**
 * @param context:This is an abstract class whose implementation is provided by Android Operating System.
 * @param attrs:A      collection of attributes, as found associated with a tag in an XML document.
 * @param defStyle:
 */
public CustomTextView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    if (preferenceUtils.getBooleanValue(PrefsKeyValue.bTheme)) {
        this.setTextColor(ResourceUtils.getColor(R.color.lightThemeTextColor));
    } else {
        this.setTextColor(ResourceUtils.getColor(R.color.colorWhite));
    }

    try {
        TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.CustomEditText, defStyle, 0);

        String str = a.getString(R.styleable.CustomTextView_FontEnum);
        int original = a.getInt(R.styleable.CustomEditText_FontEnum, 0);
        CustomEnum.CustomFontType customEnumValue = CustomEnum.CustomFontType.fromId(a.getInt(R.styleable.CustomEditText_FontEnum, 0));
        a.recycle();
        switch (customEnumValue) {
            case BOLD:
                setTypeface(HelveticaNeueBold.getInstance(context).getTypeFace());
                break;

            case LIGHT:
                setTypeface(HelveticaNeueMedium.getInstance(context).getTypeFace());
                break;

            case REGULAR:
                setTypeface(HelveticaNeue.getInstance(context).getTypeFace());
                break;

            default:
                break;
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

public CustomTextView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}


public boolean setCustomFont(Context ctx, String asset) {

    try {
        tf = Typeface.createFromAsset(ctx.getAssets(), asset);
    } catch (Exception e) {
        LogUtils.LogE(TAG, "Could not get typeface: " + e.getMessage());
        return false;
    }

    setTypeface(tf);
    return true;
}}

Herein I have changed textcolor in accordance to theme value from sharedPref

 if (preferenceUtils.getBooleanValue(PrefsKeyValue.bTheme)) {
        this.setTextColor(ResourceUtils.getColor(R.color.lightThemeTextColor));
    } else {
        this.setTextColor(ResourceUtils.getColor(R.color.colorWhite));
    }

Then use this class as textview tag in xml file.

    <com.mypkg.customview.CustomTextView
    style="@style/signup_textViewStyle"
    android:text="@string/activity_login_password" />

I believe, you can handle property variation with theme for controls in same manner.

Upvotes: 1

Related Questions