Question

I want semi circle progress bar in background of image. just like below image.

enter image description here

i have tried to draw using canvas but can't get success. i have also tired some custom progress bar library but result is same.

any suggestions.

looking for one time development and used in every screen size.

Was it helpful?

Solution

This can be implemented by clipping a canvas containing an image at an angle (By drawing an arc).

You can use an image something like this

enter image description here

And clip that image by drawing an arc.

Here is how you can do it.

//Convert the progress in range of 0 to 100 to angle in range of 0 180. Easy math.
float angle = (progress * 180) / 100;
mClippingPath.reset();
//Define a rectangle containing the image
RectF oval = new RectF(mPivotX, mPivotY, mPivotX + mBitmap.getWidth(), mPivotY + mBitmap.getHeight());
//Move the current position to center of rect
mClippingPath.moveTo(oval.centerX(), oval.centerY());
//Draw an arc from center to given angle
mClippingPath.addArc(oval, 180, angle);
//Draw a line from end of arc to center
mClippingPath.lineTo(oval.centerX(), oval.centerY());

And once you get the path, you can use clipPath function to clip the canvas in that path.

canvas.clipPath(mClippingPath);

Here is the Complete code

SemiCircleProgressBarView.java

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.View;



public class SemiCircleProgressBarView extends View {

    private Path mClippingPath;
    private Context mContext;
    private Bitmap mBitmap;
    private float mPivotX;
    private float mPivotY;

    public SemiCircleProgressBarView(Context context) {
        super(context);
        mContext = context;
        initilizeImage();
    }

    public SemiCircleProgressBarView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        initilizeImage();
    }

    private void initilizeImage() {
        mClippingPath = new Path();

        //Top left coordinates of image. Give appropriate values depending on the position you wnat image to be placed
        mPivotX = getScreenGridUnit();
        mPivotY = 0;

        //Adjust the image size to support different screen sizes
        Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.circle);
        int imageWidth = (int) (getScreenGridUnit() * 30);
        int imageHeight = (int) (getScreenGridUnit() * 30);
        mBitmap = Bitmap.createScaledBitmap(bitmap, imageWidth, imageHeight, false);
    }

    public void setClipping(float progress) {

        //Convert the progress in range of 0 to 100 to angle in range of 0 180. Easy math.
        float angle = (progress * 180) / 100;
        mClippingPath.reset();
        //Define a rectangle containing the image
        RectF oval = new RectF(mPivotX, mPivotY, mPivotX + mBitmap.getWidth(), mPivotY + mBitmap.getHeight());
        //Move the current position to center of rect
        mClippingPath.moveTo(oval.centerX(), oval.centerY());
        //Draw an arc from center to given angle
        mClippingPath.addArc(oval, 180, angle);
        //Draw a line from end of arc to center
        mClippingPath.lineTo(oval.centerX(), oval.centerY());
        //Redraw the canvas
        invalidate();
    }

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

        //Clip the canvas
        canvas.clipPath(mClippingPath);
        canvas.drawBitmap(mBitmap, mPivotX, mPivotY, null);

    }

    private float getScreenGridUnit() {
        DisplayMetrics metrics = new DisplayMetrics();
        ((Activity)mContext).getWindowManager().getDefaultDisplay().getMetrics(metrics);
        return metrics.widthPixels / 32;
    }

}

And using it in any activity is very easy.

activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

    <com.example.progressbardemo.SemiCircleProgressBarView
        android:id="@+id/progress"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</RelativeLayout>   

Note that clipPath function doesn't work if the hardware acceleration is turned on. You can turn off the hardware acceleration only for that view.

   //Turn off hardware accleration
  semiCircleProgressBarView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);

MainActivity.java

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        SemiCircleProgressBarView semiCircleProgressBarView = (SemiCircleProgressBarView) findViewById(R.id.progress);
        semiCircleProgressBarView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);

        semiCircleProgressBarView.setClipping(70);
    }

}  

As and when the progress changes you can set the progressbar by calling function,

semiCircleProgressBarView.setClipping(progress);

Ex: semiCircleProgressBarView.setClipping(50); //50% progress

enter image description here

semiCircleProgressBarView.setClipping(70); //70% progress

enter image description here

You can use your own Image to match the requirements. Hope it helps!!

Edit : To move the semi circle to bottom of the screen, change mPivotY value. Something like this

//In `SemiCircleProgressBarView.java`
//We don't get the canvas width and height initially, set `mPivoyY` inside `onWindowFocusChanged` since `getHeight` returns proper results by that time
        public void onWindowFocusChanged(boolean hasWindowFocus) {
            super.onWindowFocusChanged(hasWindowFocus);

            mPivotX = getScreenGridUnit();
            mPivotY = getHeight() - (mBitmap.getHeight() / 2);
        }

OTHER TIPS

You can try SeekArc Library. I know its a different kind of seekbar, but with some minor customization, you can use it for your app as a progressbar. I've done the same. You just need to change some properties like
seekarc:touchInside="false".
Its fairly simple.

Now the custom implementation on my app looks somewhat like this:

Custom progressbar in CleanMaster

img src: CleanMaster at Google Play

You can also use native ProgressBar to achieve semi circle. Define ProgressBar like this:

<ProgressBar
    android:id="@+id/progressBar"
    style="?android:attr/progressBarStyleHorizontal"
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:layout_alignParentBottom="true"
    android:layout_centerHorizontal="true"
    android:max="200"
    android:progress="0"
    android:progressDrawable="@drawable/circular" />

Create drawable:

circular (API Level < 21):

<shape
   android:innerRadiusRatio="2.3"
   android:shape="ring"
   android:thickness="5sp" >
   <solid android:color="@color/someColor" />
</shape>

circular (API Level >= 21):

<shape
   android:useLevel="true"
   android:innerRadiusRatio="2.3"
   android:shape="ring"
   android:thickness="5sp" >
   <solid android:color="@color/someColor" />
</shape>

useLevel is false by default in API Level 21.

Now since we have set max = 200, to achieve semi circle, range of the progress should be 0 to 100. You can play around with these values to achieve desired shape.

Thus use it like this:

ProgressBar progressBar = (Progressbar) view.findViewById(R.id.progressBar);
progressBar.setProgress(value); // 0 <= value <= 100

This is a view which has height equal to half its width. Use the setters to adjust the behaviour as desired. By default the progress is 0 and the width of the arc is 20. Calling setProgress() will invalidate the view with the progress given. Adding a background drawable is possible and the progress bar will be draw on top.

public class SemicircularProgressBar extends View {
private int mProgress;
private RectF mOval;
private RectF mOvalInner;
private Paint mPaintProgress;
private Paint mPaintClip;
private float ovalsDiff;
private Path clipPath;

public SemicircularProgressBar(Context context) {
    super(context);
    init();
}

public SemicircularProgressBar(Context context, AttributeSet attrs) {
    super(context, attrs);
    init();
}

public SemicircularProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init();
}

private void init() {
    mProgress = 0;
    ovalsDiff = 20;
    mOval = new RectF();
    mOvalInner = new RectF();
    clipPath = new Path();
    mPaintProgress = new Paint();
    mPaintProgress.setColor(Color.GREEN);
    mPaintProgress.setAntiAlias(true);
    mPaintClip = new Paint();
    mPaintClip.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
    mPaintClip.setAlpha(0);
    mPaintClip.setAntiAlias(true);
}


// call this from the code to change the progress displayed
public void setProgress(int progress) {
    this.mProgress = progress;
    invalidate();
}

// sets the width of the progress arc
public void setProgressBarWidth(float width) {
    this.ovalsDiff = width;
    invalidate();
}

// sets the color of the bar (#FF00FF00 - Green by default)
public void setProgressBarColor(int color){
    this.mPaintProgress.setColor(color);
}

@Override
public void onDraw(Canvas c) {
    super.onDraw(c);
    mOval.set(0, 0, this.getWidth(), this.getHeight()*2);
    mOvalInner.set(0+ovalsDiff, 0+ovalsDiff, this.getWidth()-ovalsDiff, this.getHeight()*2);
    clipPath.addArc(mOvalInner, 180, 180);
    c.clipPath(clipPath, Op.DIFFERENCE);
    c.drawArc(mOval, 180, 180f * ((float) mProgress / 100), true, mPaintProgress);
}

// Setting the view to be always a rectangle with height equal to half of its width
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
    int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
    this.setMeasuredDimension(parentWidth/2, parentHeight);
    ViewGroup.LayoutParams params = this.getLayoutParams();
    params.width = parentWidth;
    params.height = parentWidth/2;
    this.setLayoutParams(params);
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}

You can use this library :

 compile 'com.github.lzyzsd:circleprogress:1.1.1'

enter image description here

for example :

   <com.github.lzyzsd.circleprogress.DonutProgress
        android:layout_marginLeft="50dp"
        android:id="@+id/donut_progress"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        custom:donut_progress="30"/>

enter image description here

<com.github.lzyzsd.circleprogress.ArcProgress
        android:id="@+id/arc_progress"
        android:background="#214193"
        android:layout_marginLeft="50dp"
        android:layout_width="100dp"
        android:layout_height="100dp"
        custom:arc_progress="55"
        custom:arc_bottom_text="MEMORY"/>

For more information see the following website :

https://github.com/lzyzsd/CircleProgress

You may be able to use this github library - circularseekbar. To achieve the half circle, you will need to manipulate the following attributes: "app:start_angle" & "app:end_angle"

More Options:

  1. The Holo Seekbar library
  2. Tutorial showing semi-circular seekbar link to tutorial
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top