I'm working on an application that contains some buttons defined via layout.xml like this
<?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#drawable/largebutton" >
</Button>
#drawable/largebutton looks like this
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" >
<shape>
<gradient android:startColor="#color/menu_button_active_start" android:endColor="#color/menu_button_active_end" android:angle="270" />
<stroke android:width="#dimen/largebutton_stroke" android:color="#color/menu_button_stroke" />
<corners android:radius="#dimen/largebutton_radius" />
<padding android:left="#dimen/largebutton_padding_leftright" android:top="#dimen/largebutton_padding_topbottom" android:right="#dimen/largebutton_padding_leftright" android:bottom="#dimen/largebutton_padding_topbottom" />
</shape>
</item>
<item android:state_focused="true" >
<shape>
<gradient android:startColor="#color/menu_button_focused_start" android:endColor="#color/menu_button_focused_end" android:angle="270" />
<stroke android:width="#dimen/largebutton_stroke" android:color="#color/menu_button_focused_stroke" />
<corners android:radius="#dimen/largebutton_radius" />
<padding android:left="#dimen/largebutton_padding_leftright" android:top="#dimen/largebutton_padding_topbottom" android:right="#dimen/largebutton_padding_leftright" android:bottom="#dimen/largebutton_padding_topbottom" />
</shape>
</item>
.....
</selector>
All properties like padding, stroke, radius are the same, except gradient colors in different states. My problem is that my application has to have more styles. You can imagine it as you have list of colors and when you choose one application changes all colors to selected one. So if you have 20 colors, 20 different xmls isn't the right way.
Both startColor and endColor values for all android:states are downloaded from web and saved to DB and I don't know how many of them are there.
Is there any way to achieve this behavior? I've searched all forums and the most of answers were that it is imposible. I found one 'solution' overwriting colors.xml but it doesn't seems to be the best solution to me.
So my question is, can I dynamically change color in colors.xml? Something like this
List<Colors> colors = downloadColorsFromWeb();
Button b = new Button;
b.setDrawable(drawable.with(colors));
Thank you all in advance.
nosko.
You could probably dynamically generate a drawable for each color you download. Check the GradientDrawable class. I think you can provide start/end colors during the initialization and set the stroke and corner radius properties after that. But you'll have to find out about the padding yourself. I am not sure.
After you create the drawable, you can use it in the button's setBackgroundDrawable
edit: probably setting the button's padding would do the trick
edit2: you can setState to the drawable but I am not sure how to set different background drawable for each state of the button.
Thank you #stan0 for your reply, it helped a lot, especially GradientDrawable class.
I've wrote simple class that creates button and can set style depending on its state. Maybe it helps to someone :)
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.GradientDrawable.Orientation;
import android.util.AttributeSet;
import android.widget.Button;
/**
* #author nosko
*
*/
public class TabButton extends Button {
private Context c;
private GradientDrawable selected, focused, pressed, normal;
public void setNormalState(GradientDrawable gd) {
this.normal = gd;
}
public void setSelectedState(GradientDrawable gd) {
this.selected = gd;
}
public void setFocusedState(GradientDrawable gd) {
this.focused = gd;
}
public void setPressedState(GradientDrawable gd) {
this.pressed = gd;
}
public TabButton(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
selected = pressed = focused = normal = new GradientDrawable(Orientation.TOP_BOTTOM, new int[] { Color.WHITE, Color.DKGRAY });
this.c = context;
this.setPadding(8, 8, 8, 8);
}
/**
* Change colors when button's state changes
*/
protected void drawableStateChanged() {
normal.setCornerRadius(8);
normal.setStroke(2, Color.parseColor(c.getResources().getString(R.color.tab_button_border)));
normal.setShape(GradientDrawable.RECTANGLE);
this.setBackgroundDrawable(normal);
if (isSelected()) {
selected.setCornerRadius(8);
selected.setStroke(2, Color.parseColor(c.getResources().getString(R.color.tab_button_border)));
selected.setShape(GradientDrawable.RECTANGLE);
this.setBackgroundDrawable(selected);
}
if (isFocused()) {
focused.setCornerRadius(8);
focused.setStroke(2, Color.parseColor(c.getResources().getString(R.color.tab_button_border)));
focused.setShape(GradientDrawable.RECTANGLE);
this.setBackgroundDrawable(focused);
}
if (isPressed()) {
pressed.setCornerRadius(8);
pressed.setStroke(2, Color.parseColor(c.getResources().getString(R.color.tab_button_border)));
pressed.setShape(GradientDrawable.RECTANGLE);
this.setBackgroundDrawable(pressed);
}
}
}
and use it like this
TabButton b = new TabButton(context, null);
b.setNormalState(new GradientDrawable(Orientation.TOP_BOTTOM, new int[] { Color.RED, Color.CYAN }));
b.setSelectedState(new GradientDrawable(Orientation.TOP_BOTTOM, new int[] { Color.YELLOW, Color.BLUE }));
b.setFocusedState(new GradientDrawable(Orientation.TOP_BOTTOM, new int[] { Color.YELLOW, Color.GREEN }));
b.setPressedState(new GradientDrawable(Orientation.TOP_BOTTOM, new int[] { Color.YELLOW, Color.BLACK }));
Related
This is the xml of "mydrawable" which I'm using as background for buttons
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="false" android:state_pressed="false" >
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:angle="-45"
android:endColor="#color/colorPrimary700"
android:startColor="#color/colorPrimary600"
android:type="linear" />
<corners android:radius="#dimen/ic_button_corner"></corners>
</shape>
</item>
<item android:state_focused="false" android:state_pressed="true" >
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:angle="-45"
android:endColor="#color/colorPrimary800"
android:startColor="#color/colorPrimary700"
android:type="linear" />
<corners android:radius="#dimen/ic_button_corner"></corners>
</shape>
</item>
</selector>
This is a use case
<Button
android:id="#+id/login_button"
style="?android:textAppearanceSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#drawable/mydrawable"
android:text="#string/log_in"/>
How to port this static drawable into a custom view with configurable parameters like the colors used in the gradients for each item and the size corner radius?
For example via Java
MyDrawable myDrawable=new MyDrawable();
myDrawable.setGradientColors(color1, color2);
myDrawable.setCornerRadius(size);
button.setBackground(Drawable);
Is it also possible via custom Button (MyButton, instead of MyDrawable)?
<MyButton
parameter_gradientcolor1:#color/color1
parameter_gradientcolor2:#color/color2
... />
EDIT
This is not working, neither reactions to click event nor correct gradient
public class SelectorButton extends AppCompatButton {
StateListDrawable mStateListDrawable;
public SelectorButton(Context context, AttributeSet attrs) {
super(context, attrs);
float cornerRadius = attrs.getAttributeFloatValue("app", "cornerRadius", 0);
int normalStartColor = attrs.getAttributeIntValue("app", "normalStartColor", R.color.mds_grey_400);
int normalEndColor = attrs.getAttributeIntValue("app", "normalEndColor", R.color.mds_grey_500);
int pressedStartColor = attrs.getAttributeIntValue("app", "pressedStartColor", R.color.mds_grey_400);
int pressedEndColor = attrs.getAttributeIntValue("app", "pressedEndColor", R.color.mds_grey_500);
GradientDrawable normalDrawable = new GradientDrawable(
GradientDrawable.Orientation.TOP_BOTTOM,
new int[]{normalStartColor, normalEndColor});
normalDrawable.setCornerRadius(cornerRadius);
GradientDrawable pressedDrawable = new GradientDrawable(
GradientDrawable.Orientation.TOP_BOTTOM,
new int[]{pressedStartColor, pressedEndColor});
pressedDrawable.setCornerRadius(cornerRadius);
mStateListDrawable = new StateListDrawable();
mStateListDrawable.addState(new int[]{-android.R.attr.state_pressed, -android.R.attr.state_focused},
normalDrawable);
mStateListDrawable.addState(new int[]{android.R.attr.state_pressed, -android.R.attr.state_focused},
pressedDrawable);
setBackground(mStateListDrawable);
}
}
This is in the layout
<com.utils.views.SelectorButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Login"
android:clickable="true"
app:normalEndColor="#color/mds_blue_400"
app:normalStartColor="#color/mds_red_500"
app:pressedEndColor="#color/mds_amber_500"
app:pressedStartColor="#color/mds_green_300" />
Yes, you can do this via custom button. Here is a code snippet.
public class SelectorButton extends AppCompatButton {
StateListDrawable mStateListDrawable;
public SelectorButton(Context context, AttributeSet attrs) {
super(context, attrs);
mStateListDrawable = new StateListDrawable();
GradientDrawable normalDrawable = new GradientDrawable(yourColor);
normalDrawable.setCornerRadius(yourRadius);
mStateListDrawable.addState(
new int[]{-android.R.attr.state_pressed, -android.R.attr.state_enabled}, );
setBackground(mStateListDrawable);
}
}
In order to set style via XML, you can define custom style such as colors or corner radius in attrs.xml.If you have any question, feel free to ask.
EDIT
Now I will show you how to declare custom style in XMLand use them. For example, I want to set normal and pressed state gradient color.
In yourProject/app/src/main/res/values dir, create a new file called attrs.xml.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="SelectorButton">
<attr name="normalStartColor" format="color"/>
<attr name="normalEndColor" format="color"/>
<attr name="pressedStartColor" format="color"/>
<attr name="pressedEndColor" format="color"/>
</declare-styleable>
</resources>
As you see, I define four attributes.Now you can set these attributes via xml.
<SelectorButton
app:normalStartColor=""
app:normalEndColor=""
app:pressedStartColor=""
app:pressedEndColor=""
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
EDIT: obtain values from xml
Sorry for my mistakes. You can obtain these values like this.
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.SelectorButton, 0, 0);
int normalStartColor = a.getColor(R.styleable.SelectorButton_normalStartColor, 0);
a.recycle();
And there is a pressed state. You can do like this.
mStateListDrawable.addState(new int[]{android.R.attr.state_pressed, -android.R.attr.state_enabled}, pressedDrawable);
Drawable in xml is just a drawable, so more like an image, you can not style it like that.
However, you can add custom style to your custom button MyButton.
That can be learned from over here https://developer.android.com/training/custom-views/create-view.html#customattr
I have my custom Drawable like this:
public class SeekBarBackgroundDrawable extends Drawable {
Paint mBasePaint = null;
public SeekBarBackgroundDrawable() {
super();
mBasePaint = new Paint();
mBasePaint.setAntiAlias(true);
mBasePaint.setStyle(Paint.Style.STROKE);
mBasePaint.setStrokeCap(Paint.Cap.ROUND);
mBasePaint.setStrokeWidth(10);
mBasePaint.setColor(0xFF00FF00);
}
#Override
public void draw(Canvas canvas) {
Rect r = getBounds();
canvas.drawLine(r.left, canvas.getHeight()/2,r.right,canvas.getHeight()/2, mBasePaint);
}
Now, this drawable is used in layer-list with parameters color and width like here:
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="#android:id/background">
<cdev.mypreferences.SeekBarBackgroundDrawable
android:width="1dp" android:color="#color/bg_color">
</cdev.mypreferences.SeekBarBackgroundDrawable>
</item>
<item android:id="#android:id/progress">
<clip>
<shape android:shape="rectangle">
<corners android:radius="20dp"></corners>
<solid android:color="#color/seekbar_progress"></solid>
</shape>
</clip>
</item>
</layer-list>
How can I get parameters from this xml into Drawable class? I need to set mBasePaint stroke width and color?
Declaring custom drawables in xml is possible to do from API 24 onward, though I couldn't succeed to do that using the first approach mentioned in the docs.
Nevertheless, as the question relates to other aspect, I'll try to answer that part.
Adding this in your custom Drawable class would return the values you are interested in:
private final int[] attrsArray = new int[] {
android.R.attr.width,
android.R.attr.color,
};
#Override public void inflate(#NonNull Resources r, #NonNull XmlPullParser parser,
#NonNull AttributeSet attrs) throws XmlPullParserException, IOException {
super.inflate(r, parser, attrs);
final TypedArray a = r.obtainAttributes(attrs, attrsArray);
float width = a.getDimensionPixelSize(0, 0);
#SuppressLint("ResourceType")
int color = a.getColor(1, 0);
a.recycle();
}
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);
I have a problem with programmatically setting the progress drawable of a SeekBar.
When I set it in the .xml file everything is working fine.
<SeekBar
android:id="#+id/sb"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
.....
android:progressDrawable="#drawable/seek_bar"/>
But, I have a problem when I try to set it from code like this:
seekBar.setProgressDrawable(getResources().getDrawable(R.drawable.seek_bar));
Background drawable then takes the whole seek bar, and I'm not able to modify the progress at all later on - the thumb moves but the progress drawable still fills whole seekbar. Also, seekbar looses its rounded corners. It seems that progress drawable is on top of the seekbar.
I tried the solution provided on android progressBar does not update progress view/drawable, but it doesn't work for me.
I solved the problem by using .xml shape as background for my SeekBar.
The complete SeekBar solution that can be used via setProgressDrawable() method should be like this:
//seek_bar.xml
<?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/seek_bar_background"/>
<item android:id="#android:id/progress"
android:drawable="#drawable/seek_bar_progress" />
</layer-list>
//seek_bar_background.xml
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<gradient
android:angle="270"
android:startColor="#8a8c8f"
android:endColor="#58595b" />
<corners android:radius="5dip" />
</shape>
//seek_bar_progress.xml
<?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/seek_bar_background"/>
<item android:id="#android:id/progress">
<clip android:drawable="#drawable/seek_bar_progress_fill" />
</item>
</layer-list>
//seek_bar_progress_fill.xml
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<gradient
android:startColor="#b3d27e"
android:endColor="#93c044"
android:angle="270"
/>
<corners android:radius="5dip" />
</shape>
In the code, you can use it like this:
progressBar.setProgressDrawable(getResources().getDrawable(R.drawable.seek_bar));
The answer given above is for using xml, but just in case someone wanted to do this programmatically I have it below.
public class SeekBarBackgroundDrawable extends Drawable {
private Paint mPaint = new Paint();
private float dy;
public SeekBarBackgroundDrawable(Context ctx) {
mPaint.setColor(Color.WHITE);
dy = ctx.getResources().getDimension(R.dimen.one_dp);
}
#Override
public void draw(Canvas canvas) {
canvas.drawRect(getBounds().left,getBounds().centerY()-dy/2,getBounds().right,getBounds().centerY()+dy/2,mPaint);
}
#Override
public void setAlpha(int i) {
mPaint.setAlpha(i);
}
#Override
public void setColorFilter(ColorFilter colorFilter) {
}
#Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
}
Above class is for the background of the seekbar (the part that always exists under the progress drawable)
public class SeekBarProgressDrawable extends ClipDrawable {
private Paint mPaint = new Paint();
private float dy;
private Rect mRect;
public SeekBarProgressDrawable(Drawable drawable, int gravity, int orientation, Context ctx) {
super(drawable, gravity, orientation);
mPaint.setColor(Color.WHITE);
dy = ctx.getResources().getDimension(R.dimen.two_dp);
}
#Override
public void draw(Canvas canvas) {
if (mRect==null) {
mRect = new Rect(getBounds().left, (int)(getBounds().centerY() - dy / 2), getBounds().right, (int)(getBounds().centerY() + dy / 2));
setBounds(mRect);
}
super.draw(canvas);
}
#Override
public void setAlpha(int i) {
mPaint.setAlpha(i);
}
#Override
public void setColorFilter(ColorFilter colorFilter) {
}
#Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
}
Above is the progress drawable. Notice it's a clip drawable. The cool part here is I am setting the bounds to be whatever I want, along with color. This allows for fine tuned customization of your drawable.
//Custom background drawable allows you to draw how you want it to look if needed
SeekBarBackgroundDrawable backgroundDrawable = new SeekBarBackgroundDrawable(mContext);
ColorDrawable progressDrawable = new ColorDrawable(Color.BLUE);
//Custom seek bar progress drawable. Also allows you to modify appearance.
SeekBarProgressDrawable clipProgressDrawable = new SeekBarProgressDrawable(progressDrawable,Gravity.LEFT,ClipDrawable.HORIZONTAL,mContext);
Drawable[] drawables = new Drawable[]{backgroundDrawable,clipProgressDrawable};
//Create layer drawables with android pre-defined ids
LayerDrawable layerDrawable = new LayerDrawable(drawables);
layerDrawable.setId(0,android.R.id.background);
layerDrawable.setId(1,android.R.id.progress);
//Set to seek bar
seekBar.setProgressDrawable(layerDrawable);
Above code uses custom drawables to edit seek bar. My main reason for doing this is I will be editing the look of the background drawable, so it has "notches" (although not implemented yet). You can't do that with a xml defined drawable (at-least not easily).
Another thing I noticed is that this process prevents the SeekBar's from getting as wide as the thumb drawable. It sets the bounds of the drawables so they never get to tall.
I've had issues changing seek and progress bars from code before. Once I actually had to load the drawable twice before it took effect properly. I think it was related to padding after changing the image.
I'm just guessing here, but try setting the padding afterwards with
getResources().getDrawable(R.drawable.seek_bar) // once
seekBar.setProgressDrawable(getResources().getDrawable(R.drawable.seek_bar)); //twice
seekBar.setPadding(int,int,int,int)
and also couldn't hurt to invalidate it.
seekBar.postInvalidate()
Very hacky and I dont like it, but it solved something similar for me before
you can use android: maxHeight in you XML, to limit background height
<SeekBar
android:id="#+id/original_volume"
android:layout_width="120dp"
android:layout_height="wrap_content"
android:maxHeight="2dp"/>
and it will not clip the thumb
Is it possible to change rectangle (drawn in xml) color in Java code while app is running?
My rectangle.xml:
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:id="#+id/listview_background_shape">
<stroke android:width="2dp" android:color="#ffffff" />
<padding android:left="20dp"
android:top="20dp"
android:right="20dp"
android:bottom="20dp" />
<solid android:color="#006600" />
</shape>
Drawn in main.xml by:
<View
android:id="#+id/myRectangleView"
android:layout_width="wrap_content"
android:layout_height="100dp"
android:background="#drawable/rectangle"/>
I've tried this way:
GradientDrawable sd;
View viewrectangle;
viewrectangle = (View) findViewById(R.id.myRectangleView);
sd = (GradientDrawable) viewrectangle.getBackground();
sd.setColor(0xffffff00);
sd.invalidateSelf();
It only works when I put it inside OnCreate method.
I want to change rect color by a button, so I put this code inside button's onClick() method. But when I click button while app is running color doesn't change. Any suggestions?
Used this code and it worked, alternatively consider redrawing the viewrectangle using viewrectangle.invalidate(), but it shouldn't be nescarry:
View viewrectangle;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
viewrectangle = (View) findViewById(R.id.myRectangleView);
}
public void doClick(View v) {
GradientDrawable sd = (GradientDrawable) viewrectangle.getBackground();
sd.setColor(0xffffff00);
sd.invalidateSelf();
}
In this example the "doClick()" method is set in the main.xml:
<Button android:layout_height="wrap_content"
android:layout_width="match_parent"
android:text="Button"
android:onClick="doClick"/>
You can put this code in a separate method, and that method you can call from onClick of button..
You could try a color filter. I've used it before to change the color of buttons (note that they started out as standard gray), if you start with another color it could be a very different outcome. Anyway, an example of how I did it:
Import the PorterDuff graphics stuff:
import android.graphics.PorterDuff;
In the class define the item you want to color filter and set the filter:
Button atdButton = (Button) convertView.findViewById(R.id.attendbutton);
if (atdState[position].equals("P")) {
atdButton.getBackground().setColorFilter(0xFF00FF00, // Set filter to green
PorterDuff.Mode.MULTIPLY);
} else if (atdState[position].equals("T")) {
atdButton.getBackground().setColorFilter(0xFFFFFF00, // Set filter to yellow
PorterDuff.Mode.MULTIPLY);
} else if (atdState[position].equals("E")) {
atdButton.getBackground().setColorFilter(0xFFFF6600, // Set filter to orange
PorterDuff.Mode.MULTIPLY);
} else if (atdState[position].equals("U")) {
atdButton.getBackground().setColorFilter(0xFFFF0000, // Set filter to red
PorterDuff.Mode.MULTIPLY);
} else {
atdButton.getBackground().clearColorFilter();
}