25

I'm trying to create a custom progress bar in an app we're working on, and the last piece I need is the progress indicator itself. Instead, I want to have that bar be rounded just like the left- and right-hand edges of the progress bar itself, like in the second image (minus the red circle of course).

enter image description here

enter image description here

Here's the layout definition of the progress bar (the rest of the layout file omitted for brevity):

<ProgressBar
    android:id="@+id/progress_bar"
    android:layout_width="fill_parent"
    android:layout_height="60dp"
    android:padding="3dp"
    android:max="100"
    android:progressDrawable="@drawable/progress_bar_states"
    android:layout_marginTop="10dp"
    style="?android:attr/progressBarStyleHorizontal"
    android:indeterminateOnly="false"
    android:layout_below="@id/textview2"
    android:layout_alignLeft="@id/textview2"
    />

and here's the progress_bar_states.xml file:

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:id="@android:id/background">
        <shape>
            <solid android:color="#20ffff00"/>
            <corners android:radius="60dp"/>
        </shape>
    </item>

    <item android:id="@android:id/progress">
        <clip>
            <shape>
                <solid android:color="#20ffffff"/>
                <corners android:radius="60dp"/>
            </shape>
        </clip>
    </item>

</layer-list>

How do I do this? I don't see any options to allow me to define the progress indicator at all. Should I be doing this a different way? I'm still pretty new at Android, so any guidance is appreciated.

@Ando - your solution is very close to what I need. The start of the indicator movement is the issue now, and I'm guessing it's because it's not a clip like the original. When the indicator starts, and up to a certain point in the beginning, it looks like it is "squeezed"

enter image description here

until it gets to the appropriate point then it looks fine:

enter image description here

How do we accomplish the 'clipping' at the start with this solution?

For @bladefury - as you can see, compared to the leading edge of the image above, it does appear to be flattened.

enter image description here

EDIT : After not looking at this for quite some time, I looked at a new component at https://github.com/akexorcist/Android-RoundCornerProgressBar/issues, and it has the same issue as the suggestions here.

TheIcemanCometh
  • 1,055
  • 2
  • 17
  • 31
  • I dont understand your concern. android:progress=".." should work just fine. You can also set it to 'indeterminate' – harveyslash Aug 05 '14 at 02:58
  • 1
    I'm not referring to incrementing the progress - I'm talking about the presentation of the progress. Instead of the progress being a vertical 'bar' like you usually see in a progress bar, I want it to be a rounded indicator to match what the outer boundaries of the bar itself look like. I will try and update the question with an image of what I want it to look like. – TheIcemanCometh Aug 05 '14 at 10:16

6 Answers6

33

just Change your progress_bar_states.xml file: like below example

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >

<item android:id="@android:id/background">
    <shape>
        <solid android:color="#20ffff00" />

        <corners android:radius="20dp" />
    </shape>
</item>
<item android:id="@android:id/progress">

    <!-- <clip> -->
    <!-- <shape> -->
    <!-- <solid android:color="@android:color/black" /> -->


    <!-- <corners -->
    <!-- android:bottomRightRadius="30dp" -->
    <!-- android:radius="60dp" -->
    <!-- android:topRightRadius="30dp" /> -->
    <!-- </shape> -->
    <!-- </clip> -->

    <scale
        android:drawable="@drawable/custom_progress_primary"
        android:scaleWidth="98%" /><!-- change here -->
</item>

here custom_progress_primary.xml file: like below

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >

<corners
    android:radius="20dp"
    android:topLeftRadius="20dp"
    android:topRightRadius="20dp" />

<solid android:color="@android:color/white" />

<stroke
    android:width="1dp"
    android:color="@android:color/darker_gray" />

</shape>
Ando Masahashi
  • 3,112
  • 2
  • 24
  • 41
  • This is **very** close, but there's a small issue at the start of the indicator movement. I posted a couple of images in the original question to show it. – TheIcemanCometh Aug 05 '14 at 12:33
  • @TheIcemanCometh might be try to use clip and i try to provide best one – Ando Masahashi Aug 05 '14 at 12:45
  • check my updated answer and pls change size of progress bar i have tested with 20dp as height and radius 20dp every where @TheIcemanCometh – Ando Masahashi Aug 05 '14 at 13:10
  • 2
    @AndoMasahashi change scaleWidth to 96% and then it worked perfectly fine for me even at progress value 1. Can you explain what scaleWidth is doing here? – Akhil Dad Jan 19 '16 at 06:25
  • @AndoMasahashi how to set movable loading gradient? – Akbar Oct 20 '16 at 12:34
  • @AndoMasahashi the scaleWidth to 98% is a perfect answer, thought i don't know why, but it like magic just clip the edge. – SDJSK Nov 16 '16 at 03:42
  • This solution worked for me using firstly this one https://stackoverflow.com/a/50098416/10287345 – E.Akio Dec 30 '19 at 15:37
9

An option that's perhaps a bit more practical is implementing a custom Progress Bar view.

public class CustomProgressBar extends View
{
    // % value of the progressbar.
    int progressBarValue = 0;

    public CustomProgressBar(Context context)
    {
        super(context);
    }

    public CustomProgressBar(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        progressBarValue = attrs.getAttributeIntValue(null, "progressBarValue", 0);
    }

    public void setValue(int value)
    {
        progressBarValue = value;
        invalidate();
    }

    protected void onDraw(Canvas canvas)
    {
        super.onDraw(canvas);

        float cornerRadius = 30.0f;

        // Draw the background of the bar.
        Paint backgroundPaint = new Paint();
        backgroundPaint.setColor(Color.LTGRAY);
        backgroundPaint.setStyle(Paint.Style.FILL);
        backgroundPaint.setAntiAlias(true);

        RectF backgroundRect = new RectF(0, 0, canvas.getWidth(), canvas.getHeight());
        canvas.drawRoundRect(backgroundRect, cornerRadius, cornerRadius, backgroundPaint);

        // Draw the progress bar.
        Paint barPaint = new Paint();
        barPaint.setColor(Color.GREEN);
        barPaint.setStyle(Paint.Style.FILL);
        barPaint.setAntiAlias(true);

        float progress = (backgroundRect.width() / 100) * progressBarValue;
        RectF barRect = new RectF(0, 0, progress, canvas.getClipBounds().bottom);

        canvas.drawRoundRect(barRect, cornerRadius, cornerRadius, barPaint);

        // Draw progress text in the middle.
        Paint textPaint = new Paint();
        textPaint.setColor(Color.WHITE);
        textPaint.setTextSize(34);

        String text = progressBarValue + "%";
        Rect textBounds = new Rect();

        textPaint.getTextBounds(text, 0, text.length(), textBounds);

        canvas.drawText(text,
                backgroundRect.centerX() - (textBounds.width() / 2),
                backgroundRect.centerY() + (textBounds.height() / 2),
                textPaint);

    }
}

Then in your layout:

<view.CustomProgressBar
    android:layout_height="30dp"
    android:layout_width="match_parent"
    progressBarValue="66"/>

Which yields the result:

Custom progress bar result

jverrijt
  • 696
  • 4
  • 10
0

ProgressBar uses ClipDrawable, which clips drawable with a rectangle. So, you can set a drawable which clips round corner rectangle to progress bar, and here's how:

first, remove <clip> tag in your progress bar drawable:

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

<item android:id="@android:id/background">
    <shape>
        <solid android:color="#20ffff00"/>
        <corners android:radius="60dp"/>
    </shape>
</item>

<item android:id="@android:id/progress">
        <shape>
            <solid android:color="#20ffffff"/>
            <corners android:radius="60dp"/>
        </shape>
</item>

then, extend progress bar like this:

public class RoundIndicatorProgressBar extends ProgressBar {

    private static final float CORNER_RADIUS_DP = 60.0f;

    public RoundIndicatorProgressBar(Context context) {
        this(context, null);
    }

    public RoundIndicatorProgressBar(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public RoundIndicatorProgressBar(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }


    @Override
    public void setProgressDrawable(Drawable d) {
        super.setProgressDrawable(tileify(d, false));
    }

    private Drawable tileify(Drawable drawable, boolean clip) {
        if (drawable instanceof LayerDrawable) {
            LayerDrawable background = (LayerDrawable) drawable;
            final int N = background.getNumberOfLayers();
            Drawable[] outDrawables = new Drawable[N];

            for (int i = 0; i < N; i++) {
                int id = background.getId(i);
                outDrawables[i] = tileify(background.getDrawable(i),
                        (id == android.R.id.progress || id == android.R.id.secondaryProgress));
            }

            LayerDrawable newBg = new LayerDrawable(outDrawables);

            for (int i = 0; i < N; i++) {
                newBg.setId(i, background.getId(i));
            }

            return newBg;

        } else {
            if (clip) {
                return new RoundClipDrawable(drawable, dp2px(getContext(), CORNER_RADIUS_DP));
            }
        }
        return drawable;
    }
    public static float dp2px(Context context, float dp) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return dp * scale;
    }

}

and here is RoundClipDrawable, modified from ClipDrawable:

RoundClipDrawable.java Gist

bladefury
  • 825
  • 6
  • 16
  • See my comment addition in the original question. The leading edge of the indicator from your solution seems to be flattened ... – TheIcemanCometh Aug 10 '14 at 04:09
  • @TheIcemanCometh of course it's flattened because the CORNER_RADIUS in RoundIndicatorProgressBar is 50.0f, for demonstration purpose. In your case, you should set it to a appropriate value. – bladefury Aug 10 '14 at 15:09
  • You're basing your comment on the assumption that I've kept the same values you have above, which I've already changed to match my requirements (they are 60dp everywhere). :-) – TheIcemanCometh Aug 10 '14 at 19:15
  • Much, much closer. Now it's at about the same point that @Ando's solution is, except yours does the clipping. But both solutions have the same issue now - the start of the progress indication. When the progress starts at zero, and up until it gets to about 20%, it's still flattened at the leading edge of the indicator. It needs to be fully rounded at the start - from 0% to 100%. Here's a screencast of the sample app I'm using to test: http://screencast.com/t/ifdJEJMy2. I also have the sample app on GitHub, at https://github.com/TheIcemanCometh/RoundedProgressSample1. – TheIcemanCometh Aug 11 '14 at 12:41
0

you can use seekbar for this too

check my post here : https://stackoverflow.com/a/41206481/6033946

Try this , you may be able to do what you want by modifying the color and width and height of the thumb and the seekbar in xml.

Community
  • 1
  • 1
iDeveloper
  • 1,699
  • 22
  • 47
0

You can use this library for rounded corner progress bar

<com.akexorcist.roundcornerprogressbar.RoundCornerProgressBar
        android:layout_width="dimension"
        android:layout_height="dimension"
        app:rcProgress="float"
        app:rcSecondaryProgress="float"
        app:rcMax="float"
        app:rcRadius="dimension"
        app:rcBackgroundPadding="dimension"
        app:rcReverse="boolean"
        app:rcProgressColor="color"
        app:rcSecondaryProgressColor="color"
        app:rcBackgroundColor="color" />

Gradle

compile 'com.akexorcist:RoundCornerProgressBar:2.0.3'

https://github.com/akexorcist/Android-RoundCornerProgressBar

Aditya Vyas-Lakhan
  • 13,409
  • 16
  • 61
  • 96
-1

How do we accomplish the 'clipping' at the start with this solution?

What you need is to limit the min progress value, like:

final int minProgress = pb.getMax() * {20dp} / pb.getWidth();
progress = Math.max(minProgress, progress);
pb.setProgress(progress);

Use code above to postprocess your progress value is ok. It is not a drawable bug, but a right stretch effect.

Xavier.S
  • 319
  • 2
  • 7