TheIcemanCometh
TheIcemanCometh

Reputation: 1056

Rounded progress within rounded progress bar

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.

Upvotes: 25

Views: 18827

Answers (6)

Aditya Vyas-Lakhan
Aditya Vyas-Lakhan

Reputation: 13555

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

Upvotes: 0

iDeveloper
iDeveloper

Reputation: 1725

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.

Upvotes: 0

jverrijt
jverrijt

Reputation: 696

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

Upvotes: 9

Xavier.S
Xavier.S

Reputation: 329

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.

Upvotes: -1

bladefury
bladefury

Reputation: 815

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

Upvotes: 0

Ando Masahashi
Ando Masahashi

Reputation: 3122

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>

Upvotes: 33

Related Questions