programmatically create styles in Android without referring to resources - android

I'm working on an app that reads in text from an XML document and then displays that text on the screen. I want to be able to create a TextAppearanceSpan object programmatically based on parameters given in the XML document (font, size, color, bold/italic, etc.) that don't rely on Resource files (for SpannableStrings in my TextView).
I was looking at the following constructor:
TextAppearanceSpan(String family, int style, int size, ColorStateList color, ColorStateList linkColor)
but I can't seem to find any information on how ColorStateLists work. Is what I'm trying to do even possible?

You can look at the source code for ColorStateList here:
GrepCode: ColorStateList
For example, the following XML selector:
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="true" android:color="#color/testcolor1"/>
<item android:state_pressed="true" android:state_enabled="false" android:color="#color/testcolor2" />
<item android:state_enabled="false" android:color="#color/testcolor3" />
<item android:color="#color/testcolor5"/>
</selector>
is equivalent to the following code:
int[][] states = new int[4][];
int[] colors = new int[4];
states[0] = new int[] { android.R.attr.state_focused };
states[1] = new int[] { android.R.attr.state_pressed, -android.R.attr.state_enabled };
states[2] = new int[] { -android.R.attr.state_enabled };
states[3] = new int[0];
colors[0] = getResources().getColor(R.color.testcolor1);
colors[1] = getResources().getColor(R.color.testcolor2);
colors[2] = getResources().getColor(R.color.testcolor3);
colors[3] = getResources().getColor(R.color.testcolor5);
ColorStateList csl = new ColorStateList(states, colors);
The documentation for what a color state and how selectors work is here.

Related

Set GradientDrawable Color in Adapter

I got the color from the button background and store as a String in the database. Later I want to use this color String in my recyclerView adapter to set the color of my TextView. Below is my code:
#Override
public void onBindViewHolder(NoteListAdapter.NoteListHolder holder, int position) {
current = data.get(position);
final String text = current.getText();
final String get_tag_text = current.getTag();
final String get_tag_color = current.getTag_color();
int[] colors = {Color.parseColor(get_tag_color)};
GradientDrawable gd = new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM, colors);
holder.note_text.setText(text);
holder.tv_tag_text.setBackground(gd);
holder.tv_tag_text.setText(get_tag_text);
}
The error I got is "Unknown color". The saved color format in the database is (The saved color format is android.graphics.drawable.GradientDrawable#d1790a4)
Below is the code to get the color from a button background drawable file and also my button xml code
color = (GradientDrawable) tag_watchlist.getBackground().mutate();
tag_color= color.toString();
<Button
android:id="#+id/tag_watch"
style="#style/tag_buttons"
android:background="#drawable/watchlist_button"
android:text="Watchlist" />
drawable file code for the button background
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"
android:padding="10dp">
<solid android:color="#a40ce1"/>
<corners android:radius="10dp"/>
</shape>
Can anyone tell me how to resolve this issue??
Edited answer
You are getting exception Caused by: java.lang.IllegalArgumentException: Unknown color mean that you are not passing the color in supported formats to method Color.parseColor.
Make sure you pass the values in following format
#RRGGBB
#AARRGGBB
Here is the valid example
Color.parseColor("#FF4081")
For more information look at documentation Color.parseColor
As per your requirement, you can achieve this API level 24 onward. If you are using current minSdkVersion 24, try below
Change your model class to save color as Integer instead String.
GradientDrawable gradientDrawable = (GradientDrawable) tag_watchlist.getBackground().mutate();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
int color = gradientDrawable.getColor().getDefaultColor();
Log.d("TAG","Color is :"+color);
current.setTagColor(color); // where current is your model class
}
To get the color back from model
int color = current.getTagColor();
You need to provide at least two colors for the GradientDrawable the startColor and the endColor
It will probably throw an exception java.lang.IllegalArgumentException: needs >= 2 number of colors with this code:
int[] colors = {Color.parseColor(get_tag_color)};
GradientDrawable gd = new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM, colors);
Change your code with this:
int[] colors = {Color.parseColor(start_color), Color.parseColor(end_color)};
GradientDrawable gd = new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM, colors);
If you have get_tag_color for both of your startColor and endColor then replace accordingly but that won't be helpful with GradientDrawable.

Get specific drawable from state list drawable

I have a state list drawable, and i want to get a specific drawable from the state list drawable:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:kplus="http://schemas.android.com/apk/res-auto">
<item kplus:key_type_space_alt="true" android:state_pressed="true" android:drawable="#drawable/space_1_pressed" />
<item kplus:key_type_space_alt="true" android:drawable="#drawable/space_1_normal" />
<!-- TopNav keys. -->
<item kplus:key_type_topnav="true" android:state_pressed="true" android:drawable="#drawable/tab_down" />
<item kplus:key_type_topnav="true" android:state_selected="true" android:drawable="#drawable/tab_down" />
<item kplus:key_type_topnav="true" android:drawable="#drawable/tab_normal" />
<!-- TopRow keys. -->
<item kplus:key_type_toprow="true" android:state_pressed="true" android:drawable="#drawable/numeric_presseed" />
<item kplus:key_type_toprow="true" android:drawable="#drawable/numeric_normal" />
</selector>
I select the correct drawable state for each key, something like this:
if (keyIsNumbers) {
if (KPlusInputMethodService.sNumbersState == 2) {
drawableState = mDrawableStatesProvider.KEY_STATE_TOPNAV_CHECKED;
}
}
Now the states are defined like this:
KEY_STATE_TOPNAV_NORMAL = new int[] {keyTypeTopNavAttrId};
KEY_STATE_TOPNAV_PRESSED = new int[] {keyTypeTopNavAttrId, android.R.attr.state_pressed};
KEY_STATE_TOPNAV_CHECKED = new int[] {keyTypeTopNavAttrId, android.R.attr.state_selected};
Now my question is how to extract the correct drawable for each state ? I need to get the 9patch padding of the drawable, because if the state have different padding on 9patch it will get the padding only for the top drawable, and i want to set the padding manually for each key (drawable.getPadding(rect)).
There is no public API to get the drawable from the state.
There are some methods in StateListDrawable but they are #hide with the comment "pending API council".
You can invoke them by reflection... but it's at your own risk !!!. (it may change in future releases)
Those methods are :
getStateDrawableIndex
getStateDrawable
Here is how to proceed (exceptions omitted) :
int[] currentState = view.getDrawableState();
StateListDrawable stateListDrawable = (StateListDrawable)view.getBackground();
Method getStateDrawableIndex = StateListDrawable.class.getMethod("getStateDrawableIndex", int[].class);
Method getStateDrawable = StateListDrawable.class.getMethod("getStateDrawable", int.class);
int index = (int) getStateDrawableIndex.invoke(stateListDrawable,currentState);
Drawable drawable = (Drawable) getStateDrawable.invoke(stateListDrawable,index);
now it is the way to get specific drawable without reflection:
StateListDrawable pressedDrawable = (StateListDrawable) this.mPressed;
final int state = android.R.attr.state_pressed;
final int index = pressedDrawable.findStateDrawableIndex(new int[]{state});
if (index >= 0) {
Drawable drawable = pressedDrawable.getStateDrawable(index);
if (drawable instanceof GradientDrawable) {
((GradientDrawable) drawable).setCornerRadius(mRoundConerRadius);
}
}

Obtaining themed attributes from built in Android styles

Given a Context that has been themed with AppTheme (shown below), is it possible to programmatically obtain the color #ff11cc00 without referencing R.style.MyButtonStyle, R.style.AppTheme, or android.R.style.Theme_Light?
The objective is to obtain the button text color that was set by the theme without being bound to a particular theme or app-declared resource. Using android.R.style.Widget_Button and android.R.attr.textColor is okay.
<style name="AppTheme" parent="Theme.Light">
<item name="android:buttonStyle">#style/MyButtonStyle</item>
</style>
<style name="MyButtonStyle" parent="android:style/Widget.Button">
<item name="android:textColor">#ff11cc00</item>
</style>
Try the following:
TypedValue outValue = new TypedValue();
Theme theme = context.getTheme();
theme.resolveAttribute(android.R.attr.buttonStyle, outValue , true);
int[] attributes = new int[1];
attributes[0] = android.R.attr.textColor;
TypedArray styledAttributes = theme.obtainStyledAttributes(outValue.resourceId, attributes);
int color = styledAttributes.getColor(0, 0);
I can think of this round about way, may be someone else will know better:
int theme ;
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD) {
theme = android.R.style.Theme;
} else {
theme = android.R.style.Theme_DeviceDefault;
}
ContextThemeWrapper wrapper = new ContextThemeWrapper(context, theme);
Button button = new Button(wrapper);
ColorStateList colorStateList = button.getTextColors();
colorStateList.getColorForState(button.getDrawableState(), R.color.default_color);

Constructing a RatingBar using images loaded from the web

I have a form which I'm dynamically generating from data I receive from a web service. This web service provides images which need to be used in the creation of input elements. I'm having difficuly in setting the progressDrawable of a RatingBar. Though XML I'm able to apply a custom image using the following as the progressDrawable:
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="#+android:id/background" android:drawable="#drawable/custom_star" />
<item android:id="#+android:id/secondaryProgress" android:drawable="#drawable/custom_star" />
<item android:id="#+android:id/progress" android:drawable="#drawable/custom_star" />
</layer-list>
where custom_star is a simple .png image, and with #android:style/Widget.RatingBar as the RatingBar style. This works fine:
but I'm wanting to change custom_star dynamically.
In code, I have tried setting the progress drawable using a bitmap directly:
Drawable d = new BitmapDrawable(getResources(), downloadedImage);
ratingBar.setProgressDrawable(d);
and also by constructing a layer-list:
LayerDrawable layerDrawable = new LayerDrawable(new Drawable[] {
getResources().getDrawable(R.drawable.custom_star),
getResources().getDrawable(R.drawable.custom_star),
getResources().getDrawable(R.drawable.custom_star)
});
layerDrawable.setId(0, android.R.id.background);
layerDrawable.setId(1, android.R.id.secondaryProgress);
layerDrawable.setId(2, android.R.id.progress);
ratingBar.setProgressDrawable(layerDrawable);
Neither works for me; both result in the custom_star drawable appearing once, stretched by the dimensions of the RatingBar:
Any ideas?
Update:
Luksprog's answer below has made an improvement, but I'm still having a couple of issues. Now, the star drawable is not stretched and the value can be set by touch, but it appears as so with 3/5 selected:
and 5/5 selected:
I believe the scaling of the images can be fixed with a few tweaks, but annoyingly the secondaryProgress drawable doesn't seem to be set - the drawable used for the greyed out not-selected stars. Without that, it's not very usable.
Any ideas?
When using the default progressDrawable or a progressDrawable set through a theme all will be ok as in the constructor for the RatingBar(its superclass ProgressBar to be more precise) widget a method will be called to "make tiles" from that drawable. When using the setProgressDrawable method this doesn't happen and if you pass a simple BitmapDrawable or a LayerDrawable(with simple BitmapDrawables) that Drawable will simply be stretched to cover the widget's background area(what you see know).
In order to make it work you would need to manually do what the RatingBar does at start, create the tiles along with the ClipDrawables that it uses. I've written a method for this, following the source code of the ProgressBar widget:
private Drawable buildRatingBarDrawables(Bitmap[] images) {
final int[] requiredIds = { android.R.id.background,
android.R.id.secondaryProgress, android.R.id.progress };
final float[] roundedCorners = new float[] { 5, 5, 5, 5, 5, 5, 5, 5 };
Drawable[] pieces = new Drawable[3];
for (int i = 0; i < 3; i++) {
ShapeDrawable sd = new ShapeDrawable(new RoundRectShape(
roundedCorners, null, null));
BitmapShader bitmapShader = new BitmapShader(images[i],
Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
sd.getPaint().setShader(bitmapShader);
ClipDrawable cd = new ClipDrawable(sd, Gravity.LEFT,
ClipDrawable.HORIZONTAL);
if (i == 0) {
pieces[i] = sd;
} else {
pieces[i] = cd;
}
}
LayerDrawable ld = new LayerDrawable(pieces);
for (int i = 0; i < 3; i++) {
ld.setId(i, requiredIds[i]);
}
return ld;
}
Then you would use the LayerDrawable returned by this method with the setProgressDrawable method. The RatingBar set its width multiplying the width of one of the state bitmaps with the number of stars, so in order to show the right amount of stars this has to be calculated as well.
Your ids are wrong, you have to set
#android:id/background
#android:id/secondaryProgress
#android:id/progress
You can also define a drawable in xml containing your customization
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="#+android:id/background"
android:drawable="#drawable/star_empty" />
<item android:id="#+android:id/secondaryProgress"
android:drawable="#drawable/star_empty" />
<item android:id="#+android:id/progress"
android:drawable="#drawable/star_full" />
</layer-list>
And you can use setMax on the RatingBar object to set the maximum amount of stars that can be displayed
It's much easier with such solution:
RatingBar ratingBar = new RatingBar(new ContextThemeWrapper(context, R.style.AppTheme_RatingBar), null, 0);

Change button state by code

this is xml code that I use to change the button images in my app, according to the state:
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="false"
android:drawable="#drawable/button_n" />
<item android:state_pressed="true"
android:drawable="#drawable/button_p" />
</selector>
How can I do this by code? I have try this:
StateListDrawable sl = new StateListDrawable();
sl.addState(new int[]{ android.R.attr.state_pressed}, R.drawable.gridcard_button_p);
but addState takes an int array as first argument and a Drawable Object as sedon one (not an int as in my example).
How can I use this method in the right way?
StateListDrawable states = new StateListDrawable();
states.addState(new int[] {android.R.attr.state_pressed},
getResources().getDrawable(R.drawable.pressed));
states.addState(new int[] {android.R.attr.state_focused},
getResources().getDrawable(R.drawable.focused));
states.addState(new int[] { },
getResources().getDrawable(R.drawable.normal));
//... like this you can do for remaining
Button.setImageDrawable(states);

Categories

Resources