The realization of circular progress bar with progress

Today, a circular progress bar with progress is implemented by customizing the View. The final effect is shown in the following figure:

Now let's talk about the design idea: first of all, this progress bar can customize the number of small rounded rectangles, the size of small rounded rectangles, the angle of small rounded rectangles, the color of unfinished progress, the color, text size, text color and circle radius when the progress is completed, so you need to customize these parameters; then how to draw this round progress? We need to draw a small rounded rectangle first, and then rotate the canvas to draw a rectangle. As shown in the figure, there are 12 small rounded rectangles, each rotating 360 / 12 = 30 degrees, drawing a small rounded rectangle, a total of 12. Then draw the progress value in the middle, and draw the progress value in the middle. It mainly needs to calculate the position of the text, which will be introduced later. Next, the implementation process is explained in detail through the code. The code is as follows:

import android.content.Context;
import android.content.res.TypedArray;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
 * Created by Liu xinon April 27, 2018

public class LoadView extends View {
    //Color value already load ed
    private int mHasLoadColor;
    //No color value for load
    private int mNormalLoadColor;
    //The width of a small rectangle
    private int mRectangleWidth;
    //Height of small rectangle
    private int mRectangleHeight;
    //Number of small rectangles
    private int mRectangleNum;
    //Small rectangle circle angle
    private int mRectangleRadius;
    //Inner radius size
    //Inside radius refers to the size of the block not included
    private int mInnerRadius;
    //Outer radius
    private int mOuterRadius;
    //Text size
    private int mTextSize;
    //Paint brush
    private Paint mPaint;
    //A brush for writing
    private TextPaint mTextPaint;
   //Small rectangle
    private RectF mRectF;

    private Context mContext;
    //Percentage string
    private String mPercentStr;
    private float mPercent;

    public LoadView(Context context) {
        this(context, null);

    public LoadView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);

    public LoadView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs, defStyleAttr);

    private void init(Context context, AttributeSet attrs, int deStyleAttr) {
        mContext = context;
        TypedArray typedArray = mContext.obtainStyledAttributes(attrs, R.styleable.LoadView, deStyleAttr, 0);
        //Here are the default values for some custom parameters
        mHasLoadColor = mContext.getResources().getColor(R.color.color_3398ab);
        mNormalLoadColor = mContext.getResources().getColor(R.color.color_e0e0e0);
        mRectangleWidth = dipToPixels(mContext, 14);
        mRectangleHeight = dipToPixels(mContext, 28);
        mRectangleNum = 12;
        mInnerRadius = dipToPixels(mContext, 85);
        mTextSize = dipToPixels(mContext, 48);
        mRectangleRadius = dipToPixels(mContext, 3);
        mPercentStr = "0%";
        mPercent = 0f;
        if (typedArray != null) {
        //Read these parameter settings from xml
            mHasLoadColor = typedArray.getColor(R.styleable.LoadView_HasLoadColor, mHasLoadColor);
            mNormalLoadColor = typedArray.getColor(R.styleable.LoadView_NormalLoadColor, mNormalLoadColor);
            mRectangleWidth = typedArray.getDimensionPixelSize(R.styleable.LoadView_RectangleWidth, mRectangleWidth);
            mRectangleHeight = typedArray.getDimensionPixelSize(R.styleable.LoadView_RectangleHeight, mRectangleHeight);
            mRectangleNum = typedArray.getInteger(R.styleable.LoadView_RectangleNum, mRectangleNum);
            mInnerRadius = typedArray.getDimensionPixelSize(R.styleable.LoadView_InnerRadius, mInnerRadius);
            mTextSize = typedArray.getDimensionPixelSize(R.styleable.LoadView_TextSize, mTextSize);
            mRectangleRadius = typedArray.getDimensionPixelSize(R.styleable.LoadView_RectangleRadius, mRectangleRadius);
        //The outer radius is equal to the user set inner radius plus the height of the small rectangle
        mOuterRadius = mInnerRadius + mRectangleHeight;
        //Here is the RetcF needed to draw the first circular rectangle. It is determined by the coordinates of the upper left corner and the lower right corner,
       // Here, the coordinates of the upper left corner (mOuterRadius - mRectangleWidth / 2f,0),
       //The coordinates of the lower right corner (mOuterRadius + mRectangleWidth / 2f, mRectangleHeight);        
       mRectF = new RectF(mOuterRadius - mRectangleWidth / 2f, 0, mOuterRadius + mRectangleWidth / 2f, mRectangleHeight);
        mPaint = new Paint();
        mTextPaint = new TextPaint();
        //You can also set the font of the text here. I have removed it
        /*try {
            Typeface typeface = Typeface.createFromFile("fonts/MyriadPro-Bold.otf");
        } catch (Exception e) {


    private int dipToPixels(Context context, float dip) {
        final float SCALE = context.getResources().getDisplayMetrics().density;
        float valueDips = dip;
        int valuePixels = (int)(valueDips * SCALE + 0.5f);
        return valuePixels;

     *For external calls
     * Please call in the main thread to set the progress from 0 to 1
     * @param percent
    public void setPercent(@FloatRange(from=0.0, to=1.0) float percent){

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //Forced width height, invalid setting width height in xml file
        //Set the size according to the radius and small rectangle size
        int defaultSize = 2 * (mInnerRadius + mRectangleHeight);
        setMeasuredDimension(defaultSize, defaultSize);


    protected void onDraw(Canvas canvas) {
        int hasDownCount = 0;//How many loaded rectangles do you need to draw to convert the downloaded percentage
        hasDownCount = Math.round(mPercent * mRectangleNum);//Only round
        //Save the state of the canvas before rotating;
        //Required angle for each rotation
        float degress = 360.0f / mRectangleNum;
        for (int i = 0; i < mRectangleNum; i++) {

            if (hasDownCount > i) {
              //Loaded colors
            } else {
            //Color without load
            //round rectangle 
            canvas.drawRoundRect(mRectF, mRectangleRadius, mRectangleRadius, mPaint);
            //Rotate the canvas at the center. Here, imagine you draw on the paper with a pen in reality. When you draw one, you will rotate the paper, and you will know
            canvas.rotate(degress, mOuterRadius, mOuterRadius);
       //Go back to the previous canvas state
      //Text width
        float textWidth = mTextPaint.measureText(mPercentStr);
        //Pay attention to the calculation of height
        //drawText is based on the BaseLine of the text
        //So the steps to calculate the height: half of the descent ascent minus descent; the result
        // It's the offset of Baseline from the center dot, and the radius is the height
        //Not yet? Look at the picture below
        Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
        float diffBaseLine = (-(fontMetrics.ascent + fontMetrics.descent)) / 2;
        //By default, x is the position of the left side of the string on the screen. If paint.setTextAlign(Paint.Align.CENTER) is set, that is the center of the character, y is the position of the baseline on the screen
        canvas.drawText(mPercentStr, mOuterRadius - textWidth / 2, mOuterRadius + diffBaseLine, mTextPaint);


Code uploaded, link address LoadView

Keywords: Android xml

Added by reecec on Sun, 29 Mar 2020 17:45:16 +0300