Set theme color dynamically - android

I am using themes (dynamically) in my android app, like this:
my_layout.xml (extract):
<TextView
android:id="#+id/myItem"
style="?my_item_style" />
attrs.xml (extract):
<attr name="my_item_style" format="reference" />
themes.xml (extract):
<style name="MainTheme.Blue">
<item name="my_item_style">#style/my_item_style_blue</item>
</style>
<style name="MainTheme.Green">
<item name="my_item_style">#style/my_item_style_green<item>
</style>
styles.xml (extract):
<style name="my_item_style_blue">
<item name="android:textColor">#color/my_blue</item>
</style>
<style name="my_item_style_green">
<item name="android:textColor">#color/my_blue</item>
</style>
So, as you can see, I am setting themes dynamically. I am using this class:
public class ThemeUtils {
private static int sTheme;
public final static int THEME_BLUE = 1;
public final static int THEME_GREEN = 2;
public static void changeToTheme(MainActivity activity, int theme) {
sTheme = theme;
activity.startActivity(new Intent(activity, MyActivity.class));
}
public static void onActivityCreateSetTheme(Activity activity)
{
switch (sTheme)
{
default:
case THEME_DEFAULT:
case THEME_BLUE:
activity.setTheme(R.style.MainTheme_Blue);
break;
case THEME_GREEN:
activity.setTheme(R.style.MainTheme_Green);
break;
}
}
}
What I want to know, is there a way how to do this (change theme color) in code? For example, I have following code (extract):
((TextView) findViewById(R.id.myItem)).setTextColor(R.color.blue);
It can be done by some helper method, which would use switch command for available themes and return correct color for a theme. But I would like to know if there is some better, nicer and faster way.
Thanks!

I have finally done it using following method:
public static int getColor(String colorName) {
Context ctx = getContext();
switch (sTheme) {
default:
case THEME_DEFAULT:
return ctx.getResources().getIdentifier("BLUE_" + colorName, "color", ctx.getPackageName());
case THEME_BLUE:
return ctx.getResources().getIdentifier("BLUE_" + colorName, "color", ctx.getPackageName());
case THEME_GREEN:
return ctx.getResources().getIdentifier("GREEN_" + colorName, "color", ctx.getPackageName());
}
}
This returns color according to my theme (I used prefixes).

If I understand corectly you're looking for a way to
extract a style from a theme,
extract a value (text color) from said style.
Let's get to it.
// Extract ?my_item_style from a context/activity.
final TypedArray a = context.obtainStyledAttributes(new int[] { R.attr.my_item_style });
#StyleRes final int styleResId = a.getResourceId(0, 0);
a.recycle();
// Extract values from ?my_item_style.
final TypedArray b = context.obtainStyledAttributes(styleResId, new int[] { android.R.attr.textColor });
final ColorStateList textColors = b.getColorStateList(0);
b.recycle();
// Apply extracted values.
if (textColors != null) {
textView.setTextColor(textColors);
}
A couple of notes:
TypedArray does not support getting support vector drawables and theme references in color state lists on older API levels. If you're willing to use AppCompat internal API you may want to try TintTypedArray.
Allocating int[] all the time is costly, make it a static final.
If you want to resolve multiple attributes at once the array of attributes has to be sorted! Else it crashes sometimes. <declare-styleable> generates such array and corresponding indices for you.

Have you check this MultipleThemeMaterialDesign demo?
SettingActivity:
#Override
protected void onCreate(Bundle savedInstanceState) {
Preferences.applyTheme(this);
getDelegate().installViewFactory();
getDelegate().onCreate(savedInstanceState);
super.onCreate(savedInstanceState);
setToolbar();
addPreferencesFromResource(R.xml.preferences);
Preferences.sync(getPreferenceManager());
mListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
#Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
Preferences.sync(getPreferenceManager(), key);
if (key.equals(getString(R.string.pref_theme))) {
finish();
final Intent intent = IntentCompat.makeMainActivity(new ComponentName(
SettingsActivity.this, MainActivity.class));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | IntentCompat.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
}
}
};
}
See full example for demo.

What about passing theme id via Intent?
Intent intent = new Intent(activity, MyActivity.class);
intent.putExtra("theme", R.style.MainTheme_Green);
activity.startActivity(intent);
And then in onCreate:
// assuming that MainTheme_Blue is default theme
setTheme(getIntent().getIntExtra("theme", R.style.MainTheme_Blue));

Given the fact that every resource is a field into the R class, you can look for them using reflection. That's costly, but since you are going to get an int value, you can store them after you get them and avoid the performance drop. And since the methods that use resources take any int, you can use an int variable as placeholder, and then put the desired color into it.
for getting any resource:
String awesomeColor = "blue";
int color = getResourceId(R.color, awesomeColor, false);
if(blue>0) ((TextView) findViewById(R.id.myItem)).setTextColor(color);
The function:
public static int getResourceId(Class rClass, String resourceText, boolean showExceptions){
String key = rClass.getName()+"-"+resourceText;
if(FailedResourceMap.containsKey(key)) return 0;
if(ResourceMap.containsKey(key)) return ResourceMap.get(rClass.getName()+"-"+resourceText);
try {
String originalText = resourceText;
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.GINGERBREAD){
resourceText = ValidationFunctions.normalizeText(resourceText);
}
resourceText = resourceText.replace("?", "").replace(" ", " ").replace(" ", "_").replace("(", "").replace(")", "");
int resource = rClass.getDeclaredField(resourceText).getInt(null);
ResourceMap.put(rClass.getName()+"-"+originalText, resource);
return resource;
} catch (IllegalAccessException | NullPointerException e) {
FailedResourceMap.put(key, 0);
if(showExceptions) e.printStackTrace();
} catch (NoSuchFieldException e) {
FailedResourceMap.put(key, 0);
if(showExceptions) e.printStackTrace();
}
return 0;
}
Working version here: https://github.com/fcopardo/AndroidFunctions/blob/master/src/main/java/com/grizzly/functions/TextFunctions.java
This treatment is valid for any android resource. You can set the theme this way too instead of using intermediate variables:
public static void onActivityCreateSetTheme(Activity activity)
{
int theme = getResourceId(R.style, activity.getClass().getSimpleName(), false);
if(theme > 0) activity.setTheme(theme);
}

Related

Change theme dynamically for multiple view - Android

I am trying change theme when the app device is offline. But to achieve changing the background color is not gonna help and i needed to change the whole view and the text colors. But for that getting all the view with FindViewById is not an effective method to achieve that as I got lots of views to the Activity and as i tried using Themes
<item name="android:textColor">#android:color/black</item>
<item name="android:windowBackground">#android:color/white</item>
But as it shows only one colored TextView I cant use this method to activity as it has multiple colors and changing theme has to be done before you create the activity.
Please provide a solution which supports changing theme with multiple colored View
public class Utils {
private static int sTheme;
public final static int THEME_MATERIAL_LIGHT = 0;
public final static int THEME_YOUR_CUSTOM_THEME = 1;
public static void changeToTheme(Activity activity, int theme) {
sTheme = theme;
activity.finish();
activity.startActivity(new Intent(activity, activity.getClass()));
activity.overridePendingTransition(android.R.anim.fade_in,
android.R.anim.fade_out);
}
public static void onActivityCreateSetTheme(Activity activity) {
switch (sTheme) {
default:
case THEME_MATERIAL_LIGHT:
activity.setTheme(R.style.Theme_Material_Light);
break;
case THEME_YOUR_CUSTOM_THEME:
activity.setTheme(R.style.Theme_YOUR_CUSTOM_THEME);
break;
}
}
}
In Your Activity:
#Override // Any Activity
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// MUST BE SET BEFORE setContentView
Utils.onActivityCreateSetTheme(this);
// AFTER SETTING THEME
setContentView(R.layout.activity_theme);
}

AppCompat v7 r21 changing Alpha of the ActionBar's title

With the previous versions of AppCompat it was easy to get the ActionBar's title TextView and modify it.
There is the method I was using:
private TextView getActionBarTitleView() {
int id = getResources().getIdentifier("action_bar_title", "id", "android");
return (TextView) findViewById(id);
}
And then to change the alpha's value of the title:
getActionBarTitleView().setAlpha(ratio*255);
Now for some reasons, "action_bar_title" with the lastest AppCompat version isn't working anymore. When I tried to use my method, it returned me "null". Tried to use other id's of the ActionBar but I didn't find the good one.
I saw 1 post on StackOverflow from Ahmed Nawara and the only way he found for the moment was to do an Iteration over the Toolbar's children views and whenever a TexView is found, compare it's text value with the toolbar.getTitle() to make sure that's the TexView we're looking at.
If someone could help me to integrate this solution in my case because I don't know how to do it actually.
I guess you got your method from Flavien Laurent's post on making the ActionBar not boring. If you take a closer look, he detailled another technique to set the ActionBar title's alpha inspired by Cyril Mottier.
It uses a custom AlphaForegroundColorSpan class that extends ForegroundColorSpan :
public class AlphaForegroundColorSpan extends ForegroundColorSpan
{
private float mAlpha;
public AlphaForegroundColorSpan(int color)
{
super(color);
}
public AlphaForegroundColorSpan(Parcel src)
{
super(src);
mAlpha = src.readFloat();
}
public void writeToParcel(Parcel dest, int flags)
{
super.writeToParcel(dest, flags);
dest.writeFloat(mAlpha);
}
#Override
public void updateDrawState(TextPaint ds)
{
ds.setColor(getAlphaColor());
}
public void setAlpha(float alpha)
{
mAlpha = alpha;
}
public float getAlpha()
{
return mAlpha;
}
private int getAlphaColor()
{
int foregroundColor = getForegroundColor();
return Color.argb((int) (mAlpha * 255), Color.red(foregroundColor), Color.green(foregroundColor), Color.blue(foregroundColor));
}
}
Then, using a SpannableString, you just set the alpha to the AlphaForegroundColorSpan, and then set this AlphaForegroundColorSpan to the SpannableString :
public void onCreate(Bundle savedInstanceState)
{
...
spannableString = new SpannableString("ActionBar title");
alphaForegroundColorSpan = new AlphaForegroundColorSpan(0xffffffff);
...
}
private void setActionBarTitle(int newAlpha)
{
alphaForegroundColorSpan.setAlpha(newAlpha);
spannableString.setSpan(alphaForegroundColorSpan, 0, spannableString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
getSupportActionBar().setTitle(spannableString);
}
Hope it helps. If it's not clear enough, give another look to Flavient Laurent's post!
With AppCompat you should use the new toolbar including the toolbar.xml in the activity layout and importing android.support.v7.widget.Toolbar.
In your activity, OnCreate you will have:
mtoolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(mtoolbar);
getActionBarTitleView().setAlpha(ratio*255);
at this point you are almost done and you can use reflection to access to the view (remember to import java.lang.reflect.field;) and your function will be:
private TextView getActionBarTitleView() {
TextView yourTextView = null;
try {
Field f = mToolBar.getClass().getDeclaredField("mTitleTextView");
f.setAccessible(true);
yourTextView = (TextView) f.get(mToolBar);
} catch (NoSuchFieldException e) {
} catch (IllegalAccessException e) {
}
return yourTextView;
}

Get attr color value based on current set theme

In my activity I'm maintaining a SuperActivity, in which I'm setting the theme.
public class SuperActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTheme(R.style.MyTheme);
}
}
themes.xml
<!-- ImageBackround -->
<style name="Theme.MyTheme" parent="ThemeLight">
<item name="myBgColor">#color/translucent_black</item>
</style>
Now I want to fetch this color in one of my child activity.
As mentioned in this probable answer, I wrote:
int[] attrs = new int[] { R.attr.myBgColor /* index 0 */};
TypedArray ta = ChildActivity.this.obtainStyledAttributes(attrs);
int color = ta.getColor(0, android.R.color.background_light);
String c = getString(color);
ta.recycle();
But everytime I'm getting the value of the default value of android.R.color.background_light & not of R.attr.myBgColor.
Where I'm doing wrong. Am I passing the wrong context of ChildActivity.this?
You have two possible solutions (one is what you actually have but I include both for the sake of completeness):
TypedValue typedValue = new TypedValue();
if (context.getTheme().resolveAttribute(R.attr.xxx, typedValue, true))
return typedValue.data;
else
return Color.TRANSPARENT;
or
int[] attribute = new int[] { R.attr.xxx };
TypedArray array = context.getTheme().obtainStyledAttributes(attribute);
int color = array.getColor(0, Color.TRANSPARENT);
array.recycle();
return color;
Color.TRANSPARENT could be any other default for sure. And yes, as you suspected, the context is very important. If you keep getting the default color instead of the real one, check out what context you are passing. It took me several hours to figure it out, I tried to spare some typing and simply used getApplicationContext() but it doesn't find the colors then...

Themes doesn't apply on Button's Text

I have a theme class "Util" and I have 3 buttons to change the theme in my Activity.
When I apply the theme every TextView is working well with themes, EditText is not working which made me to change it to TextView and the Buttons also don't working.
The thing that i discovered is That the field of text color have to be empty like in TextView.
I Changed my Button to TextView and its worked! But is there any way to make these button work as well??
This is my Util class:
public static void changeToTheme(Activity activity, int theme){
sTheme = theme;
activity.finish();
activity.startActivity(new Intent(activity, activity.getClass()));
}
/** Set the theme of the activity, according to the configuration. */
public static void onActivityCreateSetTheme(Activity activity){
switch (sTheme)
{
default:
case THEME_DEFAULT:
activity.setTheme(R.style.FirstTheme);
break;
case THEME_WHITE:
activity.setTheme(R.style.SecondTheme);
break;
case THEME_BLUE:
activity.setTheme(R.style.ThirdTheme);
break;
}
}
//Shared Preferences
public static int getTheme()
{
return sTheme;
}
public static boolean canSetThemeFromPrefs( final Activity activity )
{
boolean result = false;
SharedPreferences prefMngr = PreferenceManager.getDefaultSharedPreferences( activity );
if ( prefMngr.contains( "Theme_Preferences" ) )
{
result = true;
}
return result;
}
public static int getThemeFromPrefs( final Activity activity )
{
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences( activity );
final String themeFromPrefs = preferences.getString( "Theme_Preferences", "THEME_DEFAULT" );
if ( themeFromPrefs.equals( "THEME_BLUE" ) )
{
sTheme = THEME_BLUE;
}
else if ( themeFromPrefs.equals( "THEME_WHITE" ) )
{
sTheme = THEME_WHITE;
}
else
{
sTheme = THEME_DEFAULT;
}
return getTheme();
}
public static int getThemeFromPrefs( final String key )
{
if ( key.equals( "THEME_BLUE" ) )
{
sTheme = THEME_BLUE;
}
else if ( key.equals( "THEME_WHITE" ) )
{
sTheme = THEME_WHITE;
}
else
{
sTheme = THEME_DEFAULT;
}
return getTheme();
}
Here is my Button:
<Button
android:id="#+id/button3"
android:layout_width="#dimen/layout_width_numbers"
android:layout_height="#dimen/layout_width_numbers"
android:background="#drawable/numbers"
android:text="#string/button3"
android:textColor="#color/button_text_color"
android:textSize="#dimen/button_textsize"
android:textStyle="bold" />
Update:
And here is my Styles.xml
<!-- Change Layout theme. -->
<!-- Red. -->
<style name="FirstTheme" >
<item name="android:textColor">#color/first_theme_button</item>
</style>
<!-- Green. Violet -->
<style name="SecondTheme" >
<item name="android:textColor">#color/second_theme_button</item>
</style>
<!-- Blue. -->
<style name="ThirdTheme" >
<item name="android:textColor">#color/third_theme_button</item>
</style>
And also Shared Preference doesn't work and it doesn't save my last Theme!!
Thanks :)
I tested this out, and I think I know how to make it work:
<style name="SecondTheme" parent="android:Theme.Light">
<item name="android:editTextStyle">#style/SecondTheme</item>
<item name="android:buttonStyle">#style/SecondTheme</item>
<item name="android:textColor">#color/second_theme_button</item>
</style>
When I did this, I got the edit text and the button in the second_theme_button color.

Android - reference the value of an attribute in the currently-applied theme from code

the Android devGuide explains how it is possible to reference the value of an attribute in the currently-applied theme, using the question-mark (?) instead of at (#).
Does anyone know how to do this from code, e.g. in a customized component?
In XML, it would look something like this:
style="?header_background"
programmatically, it's a little trickier. In your activity:
private static Theme theme = null;
protected void onCreate(Bundle savedInstanceState) {
...
theme = getTheme();
...
}
public static int getThemeColors(int attr){
TypedValue typedvalueattr = new TypedValue();
theme.resolveAttribute(attr, typedvalueattr, true);
return typedvalueattr.resourceId;
}
And when you want to access an attribute of the theme, you would do something like this:
int outside_background = MyActivity.getThemeColors(R.attr.outside_background);
setBackgroundColor(getResources().getColor(outside_background));
It's a little more convoluted, but there you go ;-)
The above is not a good way of doing this for many reasons. NullPointerExceptions is one.
Below is the correct way of doing this.
public final class ThemeUtils {
// Prevent instantiation since this is a utility class
private ThemeUtils() {}
/**
* Returns the color value of the style attribute queried.
*
* <p>The attribute will be queried from the theme returned from {#link Context#getTheme()}.</p>
*
* #param context the caller's context
* #param attribResId the attribute id (i.e. R.attr.some_attribute)
* #param defaultValue the value to return if the attribute does not exist
* #return the color value for the attribute or defaultValue
*/
public static int getStyleAttribColorValue(final Context context, final int attribResId, final int defaultValue) {
final TypedValue tv = new TypedValue();
final boolean found = context.getTheme().resolveAttribute(attribResId, tv, true);
return found ? tv.data : defaultValue;
}
}
Then to use just do:
final int attribColor = ThemeUtils.getStyleAttribColorValue(context, R.attr.some_attr, 0x000000 /* default color */);
Just make sure the context that you pass in came from the Activity. Don't do getApplicationContext() or the theme returned will be from the Application object and not the Activity.
After several hours I finally found a working solution, the ones above returned only the ressourceId, not the color. You can use this instead:
public static int getThemeColor(Context context, int attr) {
TypedValue typedValue = new TypedValue();
context.getTheme().resolveAttribute(attr, typedValue, true);
TypedArray ta = context.obtainStyledAttributes(typedValue.resourceId, new int[]{attr});
int color = ta.getColor(0, 0);
ta.recycle();
return color;
}
Change ta.getColor(0, 0) with what you want to get, you can replace it with ta.getDimensionPixelSize(0, 0) for example. If you want to set a fallback value, replace the second 0 with whatever value you need.

Categories

Resources