Change xml drawables colors dynamically [duplicate] - android

What I'm trying to achieve is to use a Drawable with a couple of layers inside it, but control some values at runtime such as the startColor for the gradient. Here's what I have in my_layered_shape.xml:
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item>
<shape android:shape="rectangle">
<stroke android:width="1dp" android:color="#FF000000" />
<solid android:color="#FFFFFFFF" />
</shape>
</item>
<item android:top="1dp" android:bottom="1dp">
<shape android:shape="rectangle">
<stroke android:width="1dp" android:color="#FF000000" />
<gradient
android:startColor="#FFFFFFFF"
android:centerColor="#FFFFFF88"
android:endColor="#FFFFFFFF"
android:gradientRadius="250"
android:centerX="1"
android:centerY="0"
android:angle="315"
/>
</shape>
</item>
</layer-list>
And if I use mMyImageView.setBackgroundResource(R.drawable.my_layered_shape) it works.
I don't mind splitting the xml if I have to, or doing the whole thing programatically as long as there's a way to get at the various color values. The concept I'm going for programmatically (i.e. my best shot at doing the same in code as this xml) is:
Drawable[] layers = new Drawable[2];
ShapeDrawable sd1 = new ShapeDrawable(new RectShape());
sd1.getPaint().setColor(0xFFFFFFFF);
sd1.getPaint().setStyle(Style.STROKE);
sd1.getPaint().setStrokeWidth(1);
// sd1.getPaint().somehow_set_stroke_color?
ShapeDrawable sd2 = new ShapeDrawable(new RectShape());
sd2.getPaint().setColor(0xFF000000);
sd2.getPaint().setStyle(Style.STROKE);
// sd2.getPaint().somehow_set_stroke_color?
// sd2.getPaint().somehow_set_gradient_params?
layers[0] = sd1;
layers[1] = sd2;
LayerDrawable composite = new LayerDrawable(layers);
mMyImageView.setBackgroundDrawable(composite);
Thanks.

It seems that is does not work with ShapeDrawable, but take a look at my GradientDrawable example:
GradientDrawable gd = new GradientDrawable(Orientation.BOTTOM_TOP, new int[]{Color.RED, Color.GREEN});
gd.setStroke(10, Color.BLUE);
You may also need following method:
gd.setGradientCenter(float x, float y);
gd.setGradientRadius(float gradientRadius);

Just gonna leave this here... Not tested yet
/**
* Created by Nedo on 09.04.2015.
*/
public class ShapeBuilder {
public static Drawable generateSelectorFromDrawables(Drawable pressed, Drawable normal) {
StateListDrawable states = new StateListDrawable();
states.addState(new int[]{ -android.R.attr.state_focused, -android.R.attr.state_pressed, -android.R.attr.state_selected}, normal);
states.addState(new int[]{ android.R.attr.state_pressed}, pressed);
states.addState(new int[]{ android.R.attr.state_focused}, pressed);
states.addState(new int[]{ android.R.attr.state_selected}, pressed);
return states;
}
public static Drawable generateShape(String colorTop, String colorBot, String colorStroke, int stokeSize, float strokeRadius) {
int top, bot, stroke;
top = Color.parseColor(colorTop);
bot = Color.parseColor(colorBot);
stroke = Color.parseColor(colorStroke);
GradientDrawable drawable = new GradientDrawable(GradientDrawable.Orientation.BOTTOM_TOP, new int[]{top, bot});
drawable.setStroke(stokeSize, stroke);
drawable.setCornerRadius(strokeRadius);
return drawable;
}
public static Drawable buildSelectorShapeFromColors(String colorNormalStroke, String colorNormalBackTop, String colorNormalBackBot,
String colorPressedStroke, String colorPressedBackTop, String colorPressedBackBot,
int strokeSize, float strokeRadius) {
Drawable pressed = generateShape(colorPressedBackTop, colorPressedBackBot, colorPressedStroke, strokeSize, strokeRadius);
Drawable normal = generateShape(colorNormalBackTop, colorNormalBackBot, colorNormalStroke, strokeSize, strokeRadius);
return generateSelectorFromDrawables(pressed, normal);
}
}
Edit: tested Now, had one mistake.
You actually have to describe every single state. If you group states they will only be triggered if all of them accure at once...

Related

Custom Background Shape of Button via Code

I have managed to create a custom button background shape via XML code
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:state_pressed="true" >
<shape android:shape="rectangle" >
<corners android:radius="24dp" />
<stroke android:width="2dp" android:color="#color/colorWhite" />
<solid android:color="#color/colorPrimary" />
</shape>
</item>
<item android:state_focused="true">
<shape android:shape="rectangle" >
<corners android:radius="24dp" />
<stroke android:width="2dp" android:color="#color/colorWhite" />
<solid android:color="#color/colorPrimary" />
</shape>
</item>
<item >
<shape android:shape="rectangle" >
<corners android:radius="24dp" />
<stroke android:width="2dp" android:color="#color/colorWhite" />
<solid android:color="#color/colorWhite" />
</shape>
</item>
</selector>
But I am wondering what is the Java code equivalent to that? Any guides please?
It's a lot more verbose to do it in Java, but here are the things you would need to do.
Create a new StateListDrawable()
For each state:
Create a new ShapeDrawable(new RoundRectShape(...)). I don't recall exactly how the constructor args work, but you can experiment.
Use shapeDrawable.getPaint() to obtain its Paint object and make modifications. You'll probably use setColor(), setStyle(), and setStrokeWidth().
Construct a state set. This is an array of integers composed of various android state attributes, like android.R.attr.state_pressed, for the state you want.
Call stateListDrawable.addState(stateSet, shapeDrawable). You can use StateSet.NOTHING (or an empty int[]) for the default state. Make sure you add them in the order they would appear in XML.
Something like this:
StateListDrawable stateListDrawable = new StateListDrawable();
Shape roundRect = new RoundRectShape(...);
// Add states in order. I'll just demonstrate one.
ShapeDrawable pressed = new ShapeDrawable(roundRect);
Paint paint = pressed.getPaint();
paint.setColor(Color.BLUE);
paint.setStyle(Style.STROKE);
paint.setStrokeWidth(10f); // this is in pixels, you'll have to convert to dp yourself
int[] pressedState = { android.R.attr.state_pressed };
stateListDrawable.addState(pressedState, pressed);
You can handle button states like this:
StateListDrawable states = new StateListDrawable();
states.addState(new int[] { android.R.attr.state_pressed }, getResources().getDrawable(R.drawable.img_pressed));
states.addState(new int[] { android.R.attr.state_focused }, getResources().getDrawable(R.drawable.img_focused));
states.addState(new int[] {}, getResources().getDrawable(R.drawable.img_normal));
button.setBackgroundDrawable(states);
See this sample for reference:
StateListDrawable states = new StateListDrawable();
states.addState(new int[] { android.R.attr.state_pressed }, getSelectedBackground());
states.addState(new int[] { android.R.attr.state_focused }, getSelectedBackground());
states.addState(new int[] {}, getNormalBackground());
button.setBackgroundDrawable(states);
private ShapeDrawable getNormalBackground() {
int r = 10;
float[] outerR = new float[] { r, r, r, r, r, r, r, r };
RoundRectShape rr = new RoundRectShape(outerR, null, null);
ShapeDrawable drawable = new ShapeDrawable(rr);
drawable.getPaint().setColor(Color.GRAY);
return drawable;
}
private ShapeDrawable getSelectedBackground() {
float[] outerR2 = new float[] { 10, 10, 10, 10, 10, 10, 10, 10 };
RectF inset2 = new RectF(3, 3, 3, 3);
float[] innerR2 = new float[] { 9, 9, 0, 0, 0, 5, 0, 0 };
ShapeDrawable sh2 = new ShapeDrawable(new RoundRectShape(outerR2, inset2, innerR2));
return sh2;
}
Check this once Defining Drawable Shape with in JAVA code

Setting Gradient Background programmatically

I have an image on which I'm putting a colored overlay, like this (the colors are taken from here):
layout/list_item_view.xml
<View
android:id="#+id/image_cover_gradient"
android:layout_width="fill_parent"
android:layout_height="80dip"
android:layout_alignParentTop="true"
android:layout_marginTop="70dp"
android:background="#drawable/gradient_blue"
/>
drawable/gradient_blue.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape>
<gradient
android:angle="90"
android:startColor="#color/CornflowerBlue"
android:endColor="#color/Transparent"
android:type="linear" />
</shape>
</item>
</selector>
This always puts a blue overlay (CornflowerBlue) and it works as expected.
Now I'm trying to do this programatically and followed some stackoverflow answers (such as this), but still can't make it work. Here's my code:
private void setColor(int color){
View gradientCover = view.findViewById(R.id.image_cover_gradient);
// this FAILS because it's a StateListDrawable
//GradientDrawable coverGd = (GradientDrawable) gradientCover.getBackground();
//coverGd.setColor(color);
//this doesn't seem to work either (I don't see any effect on the image)
GradientDrawable drawable = new GradientDrawable(
Orientation.BOTTOM_TOP, new int[] { color, resources.getColor(R.color.Transparent)
});
StateListDrawable sld = new StateListDrawable();
sld.addState(new int[] { android.R.attr.startColor, android.R.attr.endColor}, drawable);
gradientCover.setBackground(sld);
}
As #pskink suggested - removing the StateListDrawable solved it:
GradientDrawable drawable = new GradientDrawable(
Orientation.BOTTOM_TOP, new int[] { color, resources.getColor(R.color.Transparent)
});
gradientCover.setBackground(drawable);

Android: Non-pressed state color of ImageButton programmatically

Currently, the ImageButton have set android:background="#drawable/mem_btn",
mem_btn.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" >
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<stroke android:width="0dp" android:color="#color/black" />
<solid android:color="#color/pressed"/>
<padding android:left="5dp" android:top="5dp"
android:right="5dp" android:bottom="5dp" />
<corners android:radius="14dp" />
</shape>
</item>
<item>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<stroke android:width="0dp" android:color="#color/black" />
<solid android:color="#3366CC"/>
<padding android:left="5dp" android:top="5dp"
android:right="5dp" android:bottom="2dp" />
<corners android:radius="14dp" />
</shape>
</item>
</selector>
The above works perfectly.
Question:
I would like to let the user change the background color: i.e. other colors upon users' choice, yet with the pressed_color, radius remain unchanged.
In this case, how to set the information in the xml programmatically such that the color for the non-pressed state is a variable?
Thanks!
Thanks Ranjit Pati for the reference, such that I can further researched on right track and found out StateListDrawable, and the following works perfectly:
public void set_buttons(int t, int color_idd)
{
ShapeDrawable activeDrawable = new ShapeDrawable();
ShapeDrawable inactiveDrawable = new ShapeDrawable();
// The corners are ordered top-left, top-right, bottom-right, bottom-left. // For each corner, the array contains 2 values, [X_radius, Y_radius]
float[] radii = new float[8];
for (int i = 0; i <= 7; i++)
{
radii[i] = (int) getResources().getDimension(R.dimen.footer_corners);
}
inactiveDrawable.setShape(new RoundRectShape(radii, null, null));
inactiveDrawable.getPaint().setColor(color_idd);
activeDrawable.setShape(new RoundRectShape(radii, null, null));
activeDrawable.getPaint().setColor( (Color.parseColor ("#008B00")));
StateListDrawable states = new StateListDrawable();
states.addState(new int[] {-android.R.attr.state_enabled}, inactiveDrawable);
states.addState(new int[] {android.R.attr.state_pressed}, activeDrawable);
btns[t].setBackground(states);
}
if I got your main problem point correctly, this custom button solve your problem.
public class customButton extends Button{
int dColor, pColor;
public customButton(Context context, int defaultColor, final int pressedColor) {
super(context);
dColor = defaultColor;
pColor = pressedColor;
this.setBackgroundDrawable(getShape(defaultColor));
this.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
v.setBackgroundDrawable(getShape(pressedColor));
}
});
}
//A method that return a single color shape with radius corner
private Drawable getShape(int color){
GradientDrawable gradientDrawable = new GradientDrawable(GradientDrawable.Orientation.TL_BR, new int[] { color,
color, color});
gradientDrawable.setGradientType(GradientDrawable.RECTANGLE);
float[] f = {1,1,1,1,1,1,1,1}; //set the radius as you like
gradientDrawable.setCornerRadii(f);
return gradientDrawable;
}
//A method that change the default and pressed color of the button
public void changeColor(int defaultColor, int pressedColor){
this.setBackgroundDrawable(getShape(defaultColor));
pColor = pressedColor;
}
}

Android Gradient drawable programmatically

I have a gradient drawable defined in xml that I use it as a background, like this:
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:bottom="4dp">
<shape>
<gradient
android:startColor="#color/blue"
android:endColor="#color/dark_blue"
android:angle="270" />
</shape>
</item>
<item android:top="98dp">
<shape>
<gradient
android:startColor="#color/black"
android:endColor="#color/transparent_black"
android:angle="270" />
</shape>
</item>
</layer-list>
I need to implement this programmatically. I have tried to use a GradientDrawable as follows (this method is implemented on a custom view):
int[] colors1 = {getResources().getColor(R.color.black), getResources().getColor(R.color.trasparent_black)};
GradientDrawable shadow = new GradientDrawable(Orientation.TOP_BOTTOM, colors1);
shadow.setBounds(0,98, 0, 0);
int[] colors = new int[2];
colors[0] = getResources().getColor(R.color.blue);
colors[1] = getResources().getColor(R.color.dark_blue);
GradientDrawable backColor = new GradientDrawable(Orientation.TOP_BOTTOM, colors);
backColor.setBounds(0, 0,0, 4);
//finally create a layer list and set them as background.
Drawable[] layers = new Drawable[2];
layers[0] = backColor;
layers[1] = shadow;
LayerDrawable layerList = new LayerDrawable(layers);
setBackgroundDrawable(layerList);
The problem is that it seems that setting the bounds is useless or doesn't work the same way as (android:top, android:bottom xml parameters). The resulting background is each layer painted from top to bottom, one above the other.
I want to generate something like this:
Found the answer!. Possible duplicate Multi-gradient shapes.
Replaced:
backColor.setBounds(0, 0,0, 4);
shadow.setBounds(0,98, 0, 0);
for
layerList.setLayerInset(0, 0, 0, 0, 4);
layerList.setLayerInset(1, 0, 98, 0, 0);

Android - how to define ShapeDrawables programmatically?

What I'm trying to achieve is to use a Drawable with a couple of layers inside it, but control some values at runtime such as the startColor for the gradient. Here's what I have in my_layered_shape.xml:
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item>
<shape android:shape="rectangle">
<stroke android:width="1dp" android:color="#FF000000" />
<solid android:color="#FFFFFFFF" />
</shape>
</item>
<item android:top="1dp" android:bottom="1dp">
<shape android:shape="rectangle">
<stroke android:width="1dp" android:color="#FF000000" />
<gradient
android:startColor="#FFFFFFFF"
android:centerColor="#FFFFFF88"
android:endColor="#FFFFFFFF"
android:gradientRadius="250"
android:centerX="1"
android:centerY="0"
android:angle="315"
/>
</shape>
</item>
</layer-list>
And if I use mMyImageView.setBackgroundResource(R.drawable.my_layered_shape) it works.
I don't mind splitting the xml if I have to, or doing the whole thing programatically as long as there's a way to get at the various color values. The concept I'm going for programmatically (i.e. my best shot at doing the same in code as this xml) is:
Drawable[] layers = new Drawable[2];
ShapeDrawable sd1 = new ShapeDrawable(new RectShape());
sd1.getPaint().setColor(0xFFFFFFFF);
sd1.getPaint().setStyle(Style.STROKE);
sd1.getPaint().setStrokeWidth(1);
// sd1.getPaint().somehow_set_stroke_color?
ShapeDrawable sd2 = new ShapeDrawable(new RectShape());
sd2.getPaint().setColor(0xFF000000);
sd2.getPaint().setStyle(Style.STROKE);
// sd2.getPaint().somehow_set_stroke_color?
// sd2.getPaint().somehow_set_gradient_params?
layers[0] = sd1;
layers[1] = sd2;
LayerDrawable composite = new LayerDrawable(layers);
mMyImageView.setBackgroundDrawable(composite);
Thanks.
It seems that is does not work with ShapeDrawable, but take a look at my GradientDrawable example:
GradientDrawable gd = new GradientDrawable(Orientation.BOTTOM_TOP, new int[]{Color.RED, Color.GREEN});
gd.setStroke(10, Color.BLUE);
You may also need following method:
gd.setGradientCenter(float x, float y);
gd.setGradientRadius(float gradientRadius);
Just gonna leave this here... Not tested yet
/**
* Created by Nedo on 09.04.2015.
*/
public class ShapeBuilder {
public static Drawable generateSelectorFromDrawables(Drawable pressed, Drawable normal) {
StateListDrawable states = new StateListDrawable();
states.addState(new int[]{ -android.R.attr.state_focused, -android.R.attr.state_pressed, -android.R.attr.state_selected}, normal);
states.addState(new int[]{ android.R.attr.state_pressed}, pressed);
states.addState(new int[]{ android.R.attr.state_focused}, pressed);
states.addState(new int[]{ android.R.attr.state_selected}, pressed);
return states;
}
public static Drawable generateShape(String colorTop, String colorBot, String colorStroke, int stokeSize, float strokeRadius) {
int top, bot, stroke;
top = Color.parseColor(colorTop);
bot = Color.parseColor(colorBot);
stroke = Color.parseColor(colorStroke);
GradientDrawable drawable = new GradientDrawable(GradientDrawable.Orientation.BOTTOM_TOP, new int[]{top, bot});
drawable.setStroke(stokeSize, stroke);
drawable.setCornerRadius(strokeRadius);
return drawable;
}
public static Drawable buildSelectorShapeFromColors(String colorNormalStroke, String colorNormalBackTop, String colorNormalBackBot,
String colorPressedStroke, String colorPressedBackTop, String colorPressedBackBot,
int strokeSize, float strokeRadius) {
Drawable pressed = generateShape(colorPressedBackTop, colorPressedBackBot, colorPressedStroke, strokeSize, strokeRadius);
Drawable normal = generateShape(colorNormalBackTop, colorNormalBackBot, colorNormalStroke, strokeSize, strokeRadius);
return generateSelectorFromDrawables(pressed, normal);
}
}
Edit: tested Now, had one mistake.
You actually have to describe every single state. If you group states they will only be triggered if all of them accure at once...

Categories

Resources