How to set Android SeekBar progress drawable programmatically - android

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

Related

Android Layout transparent layout background with underline

I am trying to have a layout background drawable, which will be only gradient underline with 1-2 dp height and rest is transparent, so the upper part will have the parent's background.
Here is what I have.
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android>
<!-- underline color -->
<item>
<shape>
<gradient
android:startColor="#color/colorPrimaryDark"
android:endColor="#FFFFFFFF"
android:centerY="0.5"
android:angle="0"/>
</shape>
</item>
<!-- main color -->
<item android:bottom="2.5dp">
<shape android:shape="rectangle">
<solid android:color="#color/white" />
<padding
android:top="4dp"
android:bottom="4dp" />
</shape>
</item>
If I change the solid color in "main color" to transparent, whole background will be using "underline color" settings.
The technique you use to create a line on the bottom of the view works if the color of the layer overlaying the gradient layer is opaque. What you are trying to do is to apply a transparent layer that replaces (erases) the underlying gradient. That is not how it works: A transparent overlay leaves the underlying color, here a gradient, untouched.
Here is an alternate layer-list drawable that you can use for API 23+:
underline_drawable.xml
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:gravity="bottom">
<shape>
<size android:height="2dp" />
<gradient
android:angle="0"
android:centerY="0.5"
android:endColor="#FFFFFFFF"
android:startColor="#color/colorPrimaryDark" />
</shape>
</item>
</layer-list>
Here is what it looks like:
Prior to API 23, you can use the following custom drawable, but it must be set in code.
GradientUnderline.java
public class GradientUnderline extends Drawable {
private Shader mShader;
private final Paint mPaint;
private int mHeight = -1;
private int mStartColor = Color.BLACK;
private int mEndColor = Color.WHITE;
private int mLastWidth;
public GradientUnderline() {
mPaint = new Paint();
}
public GradientUnderline(int lineHeight, int startColor, int endColor) {
mPaint = new Paint();
mHeight = lineHeight;
mStartColor = startColor;
mEndColor = endColor;
}
#Override
public void draw(#NonNull Canvas canvas) {
if (mShader == null || getBounds().width() != mLastWidth) {
mLastWidth = getBounds().width();
mShader = new LinearGradient(0, 0, getBounds().width(), mHeight, mStartColor,
mEndColor, Shader.TileMode.CLAMP);
mPaint.setShader(mShader);
}
canvas.drawRect(0, getBounds().height() - mHeight, getBounds().width(),
getBounds().height(), mPaint);
}
#Override
public void setAlpha(int alpha) {
}
#Override
public void setColorFilter(#Nullable ColorFilter colorFilter) {
}
#Override
public int getOpacity() {
return PixelFormat.OPAQUE;
}
}
I missed the availability of android:gravity initially because it is not mentioned on the "Drawable Resources" page. It is mentioned, however, in the LayerDrawable documentation.
Why problem occurs: Shape at first item will draw the gradient in entire region. After setting colour to second item will hide the top item region except ay 2.5dp at bottom. So whenever you set transparent colour to second item it automatically show the top level item that is gradient region..
Here i suggest the way to use but you can set to fixed height in view.
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:top="47dp">
<shape>
<gradient
android:startColor="#color/colorPrimaryDark"
android:endColor="#FFFFFFFF"
android:centerY="0.5"
android:angle="0"/>
</shape>
</item>
</layer-list>
View.xml
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#drawable/bottom_line">
</RelativeLayout>
Change size according to your needs..!
OUTPUT

Android CardView - How to fold corner

I want to get a CardView with the top-right corner like you can see in the image below, but I have no idea how to do so. It is like a folded paper (with no animation). I don't know if I should make a custom background drawable or how to manage the corner radius to get the desired result. Any help will be greatly appreciated, thanks
Also you can create such a drawable programmatically like this:
public static final class FoldCornerCard extends Shape {
private final float foldPart;
private final Path cardPath = new Path();
private final Path foldPath = new Path();
private final Paint foldPaint;
public FoldCornerCard(int foldColor, float foldPart) {
if (foldPart <= 0 || foldPart >= 1) {
throw new IllegalArgumentException("Fold part must be in (0,1)");
}
this.foldPart = foldPart;
this.foldPaint = new Paint();
foldPaint.setAntiAlias(true);
foldPaint.setColor(foldColor);
}
#Override
protected void onResize(float width, float height) {
super.onResize(width, height);
this.cardPath.reset();
final float leftFold = width - width * foldPart;
final float bottomFold = height * foldPart;
cardPath.lineTo(leftFold, 0);
cardPath.lineTo(width, bottomFold);
cardPath.lineTo(width, height);
cardPath.lineTo(0, height);
cardPath.close();
foldPath.reset();
foldPath.moveTo(leftFold, 0);
foldPath.lineTo(leftFold, bottomFold);
foldPath.lineTo(width, bottomFold);
foldPath.close();
}
#Override
public void draw(Canvas canvas, Paint paint) {
canvas.drawPath(cardPath, paint);
canvas.drawPath(foldPath, foldPaint);
}
}
And usage example:
final ShapeDrawable shapeDrawable = new ShapeDrawable(
new FoldCornerCard(Color.GREEN, 0.1f));
shapeDrawable.getPaint().setColor(Color.WHITE);
shapeDrawable.setIntrinsicHeight(-1);
shapeDrawable.setIntrinsicWidth(-1);
You just need to modify my snippet a bit to add round corners.
Look at here https://developer.android.com/studio/write/draw9patch.html
I think this is righ way to use custom layout. You could draw it on xml, or use 9-patch png.
Also you can create own class MyCardView and extends from CardView, then override method onDraw and draw CardView like you want, but it is not good idea.
I would reccomend you use 9-patch image
You can achieve this using your xml:
Lets Assume our xml shape is called shape.xml
In shape.xml(Which you have to create in your drawable folder..drawable/shape.xml)..
Create your layer list element with its square shape as the background of your xml:
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!--Paper-Back-->
<item
android:bottom="30dp"
android:left="18dp"
android:right="1dp"
android:top="0dp">
<shape android:shape="rectangle">
<solid android:color="#color/paperBack"/>
<corners
android:bottomLeftRadius="10dp"
android:bottomRightRadius="10dp"
android:topLeftRadius="10dp"
android:topRightRadius="78dp"/>
</shape>
</item>
<!--Paper-Back End-->
<!--Fold-->
<item
android:bottom="650dp"
android:top="0dp"
android:left="300dp"
android:right="1dp">
<shape android:shape="rectangle">
<solid android:color="#color/paperFold"/>
<corners
android:bottomLeftRadius="0dp"
android:bottomRightRadius="0dp"
android:topLeftRadius="0dp"
android:topRightRadius="100dp"
/>
</shape>
</item>
<!--Fold End-->
</layer-list>
Then in your colours.xml resource:
Add your colours:
color.xml
<color name="PaperBack">#A6F5F5F5</color>
<color name="paperFold">#A6DDDDDD</color>
In order to achieve the best results:The opacity of your colours as well as the type of colour combination for your paper background and fold colours will have to be taken into great consideration.
Now to apply the paper fold shape..in your main xml, use the shape.xml as the background in your main.xml.
using shape.xml as the background
android:background="#drawable/shape.xml"
main.xml
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/main_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#drawable/shape.xml"
android:orientation="vertical"
android:weightSum="4">
.....................

Android - how to define a configurable size shape

Following suggestions in few other posts, I implemented a circular button to be used in the app:
It is implemented using XML selector:
<?xml version="1.0" encoding="utf-8" ?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Non focused states
-->
<item android:state_focused="false" android:state_selected="false" android:state_pressed="false"
android:drawable="#drawable/big_ring_button_unfocused" />
<item android:state_focused="false" android:state_selected="true" android:state_pressed="false"
android:drawable="#drawable/big_ring_button_unfocused" />
<!-- Focused states
-->
<item android:state_focused="true" android:state_selected="false" android:state_pressed="false"
android:drawable="#drawable/big_ring_button_focused" />
<item android:state_focused="true" android:state_selected="true" android:state_pressed="false"
android:drawable="#drawable/big_ring_button_focused" />
<!-- Pressed
-->
<item android:state_pressed="true" android:drawable="#drawable/big_ring_button_pressed" />
</selector>
The contents of the ..._unfocused file (the others just change colors):
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="ring"
android:innerRadius="#dimen/big_ring_button_inner_radius"
android:thickness="#dimen/big_ring_button_thickness"
android:useLevel="false">
<solid android:color="#color/white" />
</shape>
I'd like to use this template for all rounded buttons in my app, but since the text inside the buttons change, the size of the button itself should change.
I thought that I might be able to accomplish this programmatically, so I checked the documentation of GradientDrawable - there is no method there to change innerRadius attribute.
Currently I have 4 XML files per each round button (selector, unfocused, focused and pressed) which is extremely ugly and will become a pain in the neck to maintain.
How could I make this button configurable in size (either XML or programmatically)?
Thanks
You cannot change the innerRadius attribute programmatically, as it's only read from XML attributes when inflating (see for example this answer).
However, you could achieve more or less the same effect programmatically, by using a custom drawable to draw the ring. For example:
public class RoundBorderDrawable extends GradientDrawable
{
private static final int RING_THICKNESS = 20;
private static final int PADDING = 8;
private static final int NORMAL_COLOR = Color.BLUE;
private static final int PRESSED_COLOR = Color.RED;
private Paint mPaint;
public RoundBorderDrawable()
{
mPaint = new Paint();
mPaint.setStyle(Style.STROKE);
mPaint.setStrokeWidth(RING_THICKNESS);
mPaint.setColor(NORMAL_COLOR);
}
#Override
public boolean isStateful()
{
return true;
}
#Override
protected boolean onStateChange(int[] stateSet)
{
boolean result = super.onStateChange(stateSet);
int color = NORMAL_COLOR;
for (int i = 0; i < stateSet.length; i++)
{
if (stateSet[i] == android.R.attr.state_pressed)
color = PRESSED_COLOR;
}
if (color != mPaint.getColor())
{
mPaint.setColor(color);
invalidateSelf();
result = true;
}
return result;
}
#Override
public void draw(Canvas canvas)
{
super.draw(canvas);
int diameter = Math.min(canvas.getWidth(), canvas.getHeight()) - RING_THICKNESS - 2 * PADDING;
canvas.drawCircle(canvas.getWidth() / 2, canvas.getHeight() / 2, diameter / 2, mPaint);
}
}
This will draw a circle that fits inside the Button dimensions (I assumed you wanted to draw a circle even for non-square buttons).
Then just set a new instance of RoundBorderDrawable as the background of your button (the properties that are constants in this example could, of course, be supplied via the constructor or with setter methods).
I have a better approach for doing this. Instead of Ring use Oval and give stroke with color white and width #dimen/big_ring_button_thickness
but this doesn't give you circular shape. To achieve circular shape your button should be square, I mean width and height of the button should be same.

set gradient as background in onDraw - android

I have a class which extends View. I want to set gradient as a background color.
#Override
public void onDraw(Canvas canvas)
{
GradientDrawable gradient1 = new GradientDrawable(Orientation.BOTTOM_TOP, new int[]
{Color.parseColor("#B1FCA9"),Color.parseColor("#29C413")});
gradient1.setShape(GradientDrawable.RECTANGLE);
gradient1.setCornerRadius(10.f);
GradientDrawable gradient2 = new GradientDrawable(Orientation.BOTTOM_TOP, new int[]
{Color.parseColor("#29C413"),Color.parseColor("#B1FCA9")});
gradient2.setShape(GradientDrawable.RECTANGLE);
gradient2.setCornerRadius(10.f);
if(!Const.currentLevel.isBonusLevel())
canvas.drawBitmap(Const.backgroundBitmap, 1, 1, null);
else if(this.bonusPicFrame == 0)
gradient1.draw(canvas);
else
gradient2.draw(canvas);
}
gradient1 should appear on my screen but the screen is white.
What is the problem?
You can always use a drawable resource file like this
background.xml is the name of the resource file
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient android:startColor="#B1FCA9" android:endColor="#29C413" android:angle="270" android:type="linear"/>
<corners radius="10dp"/>
</shape>
and set it to your view with setBackgroundResource(R.drawable.background);
you have to setBounds to both gradient1 and gradient2

Rectangle shape with two solid colors

I'd like to create a rectangle shape with two solid colors (horizontally) to achieve something like this:
I heard about layer-list, i though i could use it to contains two rectangle with a different color but it seems that it only lays shapes vertically.
Is there a way to achieve this using lalyer-list or should i use something totally different? I'd like to keep it simple with ability to change the shape colors at runtime.
Thanks.
this will surely draw the shape as per your Requirement :
Adjust size of <item> as you need !
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:left="50dip">
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<solid android:color="#0000FF" />
</shape>
</item>
<item android:right="50dip">
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<solid android:color="#ff0000" />
</shape>
</item>
</layer-list>
You can create custom drawable for this. Just extend Drawable class.
Here is a sample code which draws a rectangle like you wanted, you can provide any number of colors.
public class ColorBarDrawable extends Drawable {
private int[] themeColors;
public ColorBarDrawable(int[] themeColors) {
this.themeColors = themeColors;
}
#Override
public void draw(Canvas canvas) {
// get drawable dimensions
Rect bounds = getBounds();
int width = bounds.right - bounds.left;
int height = bounds.bottom - bounds.top;
// draw background gradient
Paint backgroundPaint = new Paint();
int barWidth = width / themeColors.length;
int barWidthRemainder = width % themeColors.length;
for (int i = 0; i < themeColors.length; i++) {
backgroundPaint.setColor(themeColors[i]);
canvas.drawRect(i * barWidth, 0, (i + 1) * barWidth, height, backgroundPaint);
}
// draw remainder, if exists
if (barWidthRemainder > 0) {
canvas.drawRect(themeColors.length * barWidth, 0, themeColors.length * barWidth + barWidthRemainder, height, backgroundPaint);
}
}
#Override
public void setAlpha(int alpha) {
}
#Override
public void setColorFilter(ColorFilter cf) {
}
#Override
public int getOpacity() {
return PixelFormat.OPAQUE;
}
}
This will give you two colors half and half vertically. Put this code in a drawable resource.
<item
android:top="320dip">
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<solid android:color="#color/red" />
</shape>
</item>
<item android:bottom="320dip">
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<solid android:color="#color/yellow" />
</shape>
</item>

Categories

Resources