Ring progress bar of Android custom View

foreword

  Many times we will use the progress bar, and the Android default progress bar is long, from left to right. In daily development, sometimes the UI needs to use a circular progress bar in order to make the page more beautiful, so this article is to write a circular progress bar by customizing, first look at the rendering:

insert image description here

text

  I won’t explain too much about the basics of custom View, let’s go straight to the topic, this time we don’t need to create a project anymore, just use the EasyView I created before.

1. XML style

  According to the rendering above, we first determine the attribute style in XML, and modify the code of attrs.xml as follows:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!--文字颜色-->
    <attr name="textColor" format="color|reference" />
    <!--文字大小-->
    <attr name="textSize" format="dimension" />
    <!--蓝牙地址输入控件-->
    <declare-styleable name="MacAddressEditText">
        <!-- 方框大小,宽高一致 -->
        <attr name="boxWidth" format="dimension" />
        <!-- 方框背景颜色 -->
        <attr name="boxBackgroundColor" format="color|reference" />
        <!-- 方框描边颜色 -->
        <attr name="boxStrokeColor" format="color|reference" />
        <!-- 方框描边宽度 -->
        <attr name="boxStrokeWidth" format="dimension" />
        <!--文字颜色-->
        <attr name="textColor" />
        <!--文字大小-->
        <attr name="textSize" />
        <!--分隔符,: 、- -->
        <attr name="separator" format="string|reference" />
    </declare-styleable>
    <!--圆形进度条控件-->
    <declare-styleable name="CircularProgressBar">
        <!--半径-->
        <attr name="radius" format="dimension" />
        <!--进度条宽度-->
        <attr name="strokeWidth" format="dimension" />
        <!--进度条背景颜色-->
        <attr name="progressbarBackgroundColor" format="color|reference" />
        <!--进度条进度颜色-->
        <attr name="progressbarColor" format="color|reference" />
        <!--最大进度-->
        <attr name="maxProgress" format="integer" />
        <!--当前进度-->
        <attr name="progress" format="integer" />
        <!--文字-->
        <attr name="text" format="string" />
        <!--文字颜色-->
        <attr name="textColor" />
        <!--文字大小-->
        <attr name="textSize" />
    </declare-styleable>
</resources>

declare-styleableHere you will find a change, that is, the properties of text color and text size have been extracted from the   previous ones , because we may use the same property in multiple custom controls, so according to the principle that properties cannot be renamed, we need Pull it out, and then declare-styleablequote it.

insert image description here

2. Construction method

  Now that the attribute style is available, the next step is to write the construction method of the custom View. com.llw.easyviewCreate a new CircularProgressBarclass under the package, and the code inside is as follows:

public class CircularProgressBar extends View {
    
    

    /**
     * 半径
     */
    private int mRadius;
    /**
     * 进度条宽度
     */
    private int mStrokeWidth;

    /**
     * 进度条背景颜色
     */
    private int mProgressbarBgColor;

    /**
     * 进度条进度颜色
     */
    private int mProgressColor;

    /**
     * 开始角度
     */
    private int mStartAngle = 0;

    /**
     * 当前角度
     */
    private float mCurrentAngle = 0;

    /**
     * 结束角度
     */
    private int mEndAngle = 360;
    /**
     * 最大进度
     */
    private float mMaxProgress;
    /**
     * 当前进度
     */
    private float mCurrentProgress;
    /**
     * 文字
     */
    private String mText;
    /**
     * 文字颜色
     */
    private int mTextColor;
    /**
     * 文字大小
     */
    private float mTextSize;
    /**
     * 动画的执行时长
     */
    private long mDuration = 1000;
    /**
     * 是否执行动画
     */
    private boolean isAnimation = false;
    
    public CircularProgressBar(Context context) {
    
    
        this(context, null);
    }

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

    public CircularProgressBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    
    
        super(context, attrs, defStyleAttr);
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CircularProgressBar);
        mRadius = array.getDimensionPixelSize(R.styleable.CircularProgressBar_radius, 80);
        mStrokeWidth = array.getDimensionPixelSize(R.styleable.CircularProgressBar_strokeWidth, 8);
        mProgressbarBgColor = array.getColor(R.styleable.CircularProgressBar_progressbarBackgroundColor, ContextCompat.getColor(context, R.color.teal_700));
        mProgressColor = array.getColor(R.styleable.CircularProgressBar_progressbarColor, ContextCompat.getColor(context, R.color.teal_200));
        mMaxProgress = array.getInt(R.styleable.CircularProgressBar_maxProgress, 100);
        mCurrentProgress = array.getInt(R.styleable.CircularProgressBar_progress, 0);
        String text = array.getString(R.styleable.CircularProgressBar_text);
        mText = text == null ? "" : text;
        mTextColor = array.getColor(R.styleable.CircularProgressBar_textColor, ContextCompat.getColor(context, R.color.black));
        mTextSize = array.getDimensionPixelSize(R.styleable.CircularProgressBar_textSize, (int) TypedValue
                .applyDimension(TypedValue.COMPLEX_UNIT_SP, 14, getResources().getDisplayMetrics()));
        array.recycle();
    }
}

  Some variables are declared here, and then three construction methods are written, and attributes are assigned in the third construction method.

3. Measurement

  The measurement here is relatively simple. Of course, this is relative to the previous Mac address input box. The code is as follows:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    
    
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = 0;
        switch (MeasureSpec.getMode(widthMeasureSpec)) {
    
    
            case MeasureSpec.UNSPECIFIED:
            case MeasureSpec.AT_MOST:   //wrap_content
                width = mRadius * 2;
                break;
            case MeasureSpec.EXACTLY:   //match_parent
                width = MeasureSpec.getSize(widthMeasureSpec);
                break;
        }
        //Set the measured width and height
        setMeasuredDimension(width, width);
    }

  Because there is no need to process sub-controls, we only need a ring, and the text is drawn in the middle of the ring. Let's look at the drawing method below.

4. Drawing

  There is a little more code here for drawing, because there are three things to be drawn: the background of the progress bar, the progress bar, and the middle text. The code for drawing is as follows:

    @Override
    protected void onDraw(Canvas canvas) {
    
    
        int centerX = getWidth() / 2;
        RectF rectF = new RectF();
        rectF.left = mStrokeWidth;
        rectF.top = mStrokeWidth;
        rectF.right = centerX * 2 - mStrokeWidth;
        rectF.bottom = centerX * 2 - mStrokeWidth;

        //绘制进度条背景
        drawProgressbarBg(canvas, rectF);
        //绘制进度
        drawProgress(canvas, rectF);
        //绘制中心文本
        drawCenterText(canvas, centerX);
    }

  Before drawing, we must first determine the center point, because we are a ring, which is actually a circle. The width and height of the circle are the same, so the positions of the x and y axes of the center point are the same, and then determine the upper left and The coordinate points of the two lower right positions, a rectangle can be drawn through these two points, and the next step is to draw the background of the progress bar.

① Draw the progress bar background

    /**
     * 绘制进度条背景
     */
    private void drawProgressbarBg(Canvas canvas, RectF rectF) {
    
    
        Paint mPaint = new Paint();
        //画笔的填充样式,Paint.Style.STROKE 描边
        mPaint.setStyle(Paint.Style.STROKE);
        //圆弧的宽度
        mPaint.setStrokeWidth(mStrokeWidth);
        //抗锯齿
        mPaint.setAntiAlias(true);
        //画笔的颜色
        mPaint.setColor(mProgressbarBgColor);
        //画笔的样式 Paint.Cap.Round 圆形
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        //开始画圆弧
        canvas.drawArc(rectF, mStartAngle, mEndAngle, false, mPaint);
    }

  Because the background is a ring, so pay more attention to the brush settings here, just take a look at it. The most important thing here is drawArcto draw a circular arc. Like the picture below, a 4/1 background is drawn.

insert image description here
The progress is plotted below

② Drawing progress

    /**
     * 绘制进度
     */
    private void drawProgress(Canvas canvas, RectF rectF) {
    
    
        Paint paint = new Paint();
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(mStrokeWidth);
        paint.setColor(mProgressColor);
        paint.setAntiAlias(true);
        paint.setStrokeCap(Paint.Cap.ROUND);
        if (!isAnimation) {
    
    
            mCurrentAngle = 360 * (mCurrentProgress / mMaxProgress);
        }
        canvas.drawArc(rectF, mStartAngle, mCurrentAngle, false, paint);
    }

  The progress value here will be processed according to the current parameters. There is a variable here to judge and process. The main function is whether to perform animation drawing.

③ Draw text

    /**
     * 绘制中心文字
     */
    private void drawCenterText(Canvas canvas, int centerX) {
    
    
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        paint.setColor(mTextColor);
        paint.setTextAlign(Paint.Align.CENTER);
        paint.setTextSize(mTextSize);
        Rect textBounds = new Rect();
        paint.getTextBounds(mText, 0, mText.length(), textBounds);
        canvas.drawText(mText, centerX, textBounds.height() / 2 + getHeight() / 2, paint);
    }

The rules for drawing text are still the same as before.

5. API method

  You also need to provide some methods to call in the code, the following is the code of these methods:

    /**
     * 设置当前进度
     */
    public void setProgress(float progress) {
    
    
        if (progress < 0) {
    
    
            throw new IllegalArgumentException("Progress value can not be less than 0");
        }
        if (progress > mMaxProgress) {
    
    
            progress = mMaxProgress;
        }
        mCurrentProgress = progress;
        mCurrentAngle = 360 * (mCurrentProgress / mMaxProgress);
        setAnimator(0, mCurrentAngle);
    }

    /**
     * 设置文本
     */
    public void setText(String text) {
    
    
        mText = text;
    }

    /**
     * 设置文本的颜色
     */
    public void setTextColor(int color) {
    
    
        if (color <= 0) {
    
    
            throw new IllegalArgumentException("Color value can not be less than 0");
        }
        mTextColor = color;
    }

    /**
     * 设置文本的大小
     */
    public void setTextSize(float textSize) {
    
    
        if (textSize <= 0) {
    
    
            throw new IllegalArgumentException("textSize can not be less than 0");
        }
        mTextSize = textSize;
    }


    /**
     * 设置动画
     *
     * @param start  开始位置
     * @param target 结束位置
     */
    private void setAnimator(float start, float target) {
    
    
        isAnimation = true;
        ValueAnimator animator = ValueAnimator.ofFloat(start, target);
        animator.setDuration(mDuration);
        animator.setTarget(mCurrentAngle);
        //动画更新监听
        animator.addUpdateListener(valueAnimator -> {
    
    
            mCurrentAngle = (float) valueAnimator.getAnimatedValue();
            invalidate();
        });
        animator.start();
    }

  So far this custom View is completed, and we can use it in MainActivity.

6. Use

  The first modified activity_main.xmlcode is as follows:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical"
    android:padding="16dp"
    tools:context=".MainActivity">

    <com.easy.view.MacAddressEditText
        android:id="@+id/mac_et"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:boxBackgroundColor="@color/white"
        app:boxStrokeColor="@color/black"
        app:boxStrokeWidth="2dp"
        app:boxWidth="48dp"
        app:separator=":"
        app:textColor="@color/black"
        app:textSize="16sp" />

    <Button
        android:id="@+id/btn_mac"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="30dp"
        android:text="获取地址" />

    <com.easy.view.CircularProgressBar
        android:id="@+id/cpb_test"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="30dp"
        app:maxProgress="100"
        app:progress="10"
        app:progressbarBackgroundColor="@color/purple_500"
        app:progressbarColor="@color/purple_200"
        app:radius="80dp"
        app:strokeWidth="16dp"
        app:text="10%"
        app:textColor="@color/teal_200"
        app:textSize="28sp" />

    <Button
        android:id="@+id/btn_set_progress"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="30dp"
        android:text="随机设置进度" />
</LinearLayout>

First of all, pay attention to see if you can preview, I can preview here, as shown in the following figure:

insert image description here

Used in MainActivity, modify onCreate()the code in the method as follows:

        //圆形进度条操作
        CircularProgressBar cpbTest = findViewById(R.id.cpb_test);
        Button btnSetProgress = findViewById(R.id.btn_set_progress);
        btnSetProgress.setOnClickListener(v -> {
    
    
            int progress = Math.abs(new Random().nextInt() % 100);
            Toast.makeText(this, "" + progress, Toast.LENGTH_SHORT).show();
            cpbTest.setText(progress + "%");
            cpbTest.setProgress(progress);
        });

The running effect is shown in the figure below:

insert image description here

Seven, source code

  By the way, I plan to make this project an open source warehouse and submit it to China. If it is mavenCentral()used later, it can be called through this way of introducing dependencies. It will be very convenient. A separate article will be published later to describe this warehouse.

If it is helpful to you, you may wish to Star or Fork, the mountains are high and the waters are long, and there will be a period later~

Source address: EasyView

Guess you like

Origin blog.csdn.net/qq_38436214/article/details/130078072