Android pattern loading progress bar (2) - simple circular progress bar

background

The Android pattern loading progress bar series of articles mainly explain how to customize the required progress bar, including horizontal, circular, annular, arc-shaped, irregular shapes, etc.
In this article, we will start with the circular progress bar, and talk about the simple form of the circular progress bar, which only has progress color and no progress text. It mainly uses Canvas to draw circles and arcs.

Effect

First look at the effect in the picture above. There are 6 progress bars, and there are subtle differences in style. They basically belong to the same category of progress bars.
progress bar effect
The 6 progress bars are basically divided into 3 categories:

  1. The background bar is the same width as the progress bar;
  2. If the background bar and the progress bar have different widths, the progress bar is slightly smaller than the background bar, showing a layered effect;
  3. The effect of the ring type or the pie type is different.

We use the first type as the basic form to describe, and the knowledge points that need to be prepared are:

  • Customize the coordinate axis of the control;
  • Canvas ring, arc drawing method;
  • custom properties;
  • Handler message processing mechanism.

Each of the knowledge points involved has a lot of content, and we only mention the knowledge content used this time.

Knowledge point

1. The coordinate axis of the custom control The
schematic diagram of the coordinate axis of the Android system is as follows, the origin of the coordinate axis O point, which can be understood as the upper left corner of the screen. (The picture comes from the Internet)
write picture description here
During the drawing process of the custom control View, the O point is equivalent to the upper left corner of the custom control View area. It can be understood that there is a coordinate system in the View, and the origin of the coordinate system O is at the location of the View. upper left corner.

2, Canvas ring, arc drawing method

1) Canvas ring drawing method

android.graphics.Canvas#drawCircle(float cx, float cy, float radius, Paint paint)

This method draws a circle with parameters:

  • cx: the x-axis coordinate of the center O of the circle;
  • cy: the y-axis coordinate of the center O of the circle;
  • radius: the radius of the circle;
  • paint: The brush used to draw graphics.

If the brush uses the hollow mode, it draws a ring, as in Figure 1 in the effect animation; if the brush uses the solid mode, it draws a pie, as in Figure 3 in the effect animation.

2) Canvas arc drawing method

android.graphics.Canvas#drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)

This method draws an arc where:

  • oval: is a rectangular area that defines the outer edge of the arc;
  • sweepAngle: is the angle swept by the arc;
  • userCenter: It is used to control whether the arc passes through the center of the circle when drawing;
  • paint: The brush used to draw graphics.

3. Custom properties
In custom controls, we inevitably use various parameters such as color values ​​and sizes. If these values ​​are hard-coded in the code, the user cannot control them flexibly. We need to put the value definition of the attribute on the user side to realize the decoupling of the code.

At this point, some custom properties are needed to support us in defining and using the properties of the control. For example, the width of the ring, we can define it as roundWidth, and the value uses the dimension type.

Custom attributes need to create or modify the attrs.xml file in the res-values ​​directory of the project. The specific usage method can be found in Baidu. The example is given below.
attrs.xml file
4. Handler message processing mechanism
Android does not allow sub-threads (we write a Thread to run a sub-thread) to update the UI interface in the main thread (which can be understood as Activity), such as setting the text value, changing the size, etc. of. So we need the message mechanism in Handler to update the interface in the thread.
Because the progress bar control is constantly changing with the progress change when it is used, usually the progress change is done in the child thread. When the progress value changes, by sending a message to the Handler, carrying the progress value, the Handler is receiving the message The progress can be updated to the UI interface during post-processing.
The technical principle and specific use points of Handler can be found on Baidu, and will not be expanded here. Use examples below.

custom properties

With the knowledge points explained in the previous paragraph, we can actually draw custom controls. Before drawing, we need to understand which properties of this control need to be defined separately for users to flexibly modify. Initial considerations include:

  • List content
  • the color of the ring;
  • the width of the ring;
  • progress color on the ring;
  • Progress width on the torus, etc.

To this end, we define the content of the attrs.xml configuration file as:

<?xml version="1.0" encoding="UTF-8"?>
<resources>
    <!--简单环形进度-->
    <declare-styleable name="SimpleRoundProgress">
        <!--圆环颜色-->
        <attr name="srp_roundColor" format="color" />
        <!--圆环的宽度-->
        <attr name="srp_roundWidth" format="dimension" />
        <!--圆环上的进度颜色-->
        <attr name="srp_progressColor" format="color" />
        <!--圆环上的进度宽度-->
        <attr name="srp_progressWidth" format="dimension" />
        <!--进度值的最大值,一般为100-->
        <attr name="srp_max" format="integer" />
        <!--开始角度,指定进度初始点的绘制位置-->
        <attr name="srp_startAngle" format="integer" />
        <!--样式,空心还是实心-->
        <attr name="srp_style">
            <enum name="STROKE" value="0" />
            <enum name="FILL" value="1" />
        </attr>
    </declare-styleable>
</resources>

With the attribute definitions, we can draw according to the set values ​​by combining these attributes when drawing.

Progress bar graphic drawing

Android custom graphics need to inherit the View class, so the progress bar SimpleRoundProgress we define after inheriting View looks like:

/**
 * 简单环形进度条
 */
public class SimpleRoundProgress extends View {
...
}

1. Read custom properties for subsequent use

Define some member variables in the SimpleRoundProgress class to load the value of the custom attribute, and then load the attribute value through the attribute loading class. The definitions of member variables are:

public class SimpleRoundProgress extends View {
    private Paint paint; // 画笔对象的引用
    private int roundColor; // 圆环的颜色
    private float roundWidth; // 圆环的宽度
    private int progressColor; // 圆环进度的颜色
    private float progressWidth; // 圆环进度的宽度
    private int max; // 最大进度
    private int style; // 进度的风格,实心或者空心
    private int startAngle; // 进度条起始角度
    public static final int STROKE = 0; // 样式:空心
    public static final int FILL = 1; // 样式:实心
    private int progress; // 当前进度
    ...
}

Read the value assigned to the user-defined property in the constructor.

public class SimpleRoundProgress extends View {
    ...
    public SimpleRoundProgress(Context context) {
        this(context, null);
    }

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

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

        paint = new Paint();

        // 读取自定义属性的值
        TypedArray mTypedArray = context.obtainStyledAttributes(attrs, R.styleable.SimpleRoundProgress);

        // 获取自定义属性和默认值
        roundColor = mTypedArray.getColor(R.styleable.SimpleRoundProgress_srp_roundColor, Color.RED);
        roundWidth = mTypedArray.getDimension(R.styleable.SimpleRoundProgress_srp_roundWidth, 5);
        progressColor = mTypedArray.getColor(R.styleable.SimpleRoundProgress_srp_progressColor, Color.GREEN);
        progressWidth = mTypedArray.getDimension(R.styleable.SimpleRoundProgress_srp_progressWidth, roundWidth);
        max = mTypedArray.getInteger(R.styleable.SimpleRoundProgress_srp_max, 100);
        style = mTypedArray.getInt(R.styleable.SimpleRoundProgress_srp_style, 0);
        startAngle = mTypedArray.getInt(R.styleable.SimpleRoundProgress_srp_startAngle, 90);

        mTypedArray.recycle();
    }
    ...
}

2. Draw the progress ring

There are two main steps to draw the progress ring: draw the background ring and draw the foreground progress ring. The code is written in the View's onDraw method body:

public class SimpleRoundProgress extends View {
    ...
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        ...
    }
    ...
}

1) Draw the background ring

Because it is a circular progress bar, the center of the circle is in the center of the entire control when the circle is drawn, so the drawing code of the background ring:

int centerX = getWidth() / 2; // 获取圆心的x坐标
int radius = (int) (centerX - roundWidth / 2); // 圆环的半径

// step1 画最外层的大圆环
paint.setStrokeWidth(roundWidth); // 设置圆环的宽度
paint.setColor(roundColor); // 设置圆环的颜色
paint.setAntiAlias(true); // 消除锯齿
// 设置画笔样式
switch (style) {
    case STROKE:
        paint.setStyle(Paint.Style.STROKE);
        break;
    case FILL:
        paint.setStyle(Paint.Style.FILL_AND_STROKE);
        break;
}
canvas.drawCircle(centerX, centerX, radius, paint); // 画出圆环

Make two points:

  • The x/y axis coordinates of the center of the circle are getWidth() / 2 (generally the same width and height);
  • paint.setStyle can set whether the brush is hollow or solid, and the corresponding effect is a ring or a pie.

2) Draw the foreground progress ring

There are calculations involving progress in the foreground progress ring. Take the total progress value of 100 as an example. When the progress is 50, if it is a horizontal progress bar, it should draw half the progress length of the entire control. If it is a circular progress bar, it should be drawn. 180 degree circle. By analogy, the angle of the progress loop is calculated as:

int sweepAngle = 360 * progress / max; // 计算进度值在圆环所占的角度

The drawing code of the foreground progress ring is:

// step2 画圆弧-画圆环的进度
paint.setStrokeWidth(progressWidth); // 设置画笔的宽度使用进度条的宽度
paint.setColor(progressColor); // 设置进度的颜色
RectF oval = new RectF(centerX - radius , centerX - radius , centerX + radius , centerX + radius ); // 用于定义的圆弧的形状和大小的界限

int sweepAngle = 360 * progress / max; // 计算进度值在圆环所占的角度
// 根据进度画圆弧
switch (style) {
    case STROKE:
        // 空心
        canvas.drawArc(oval, startAngle, sweepAngle, false, paint);
        break;
    case FILL:
        // 实心
        canvas.drawArc(oval, startAngle, sweepAngle, true, paint);
        break;
}

At this point, the drawing of the progress bar interface is completed, and the code of the entire SimpleRoundProgress is:

package com.dommy.loading.widget;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;

import com.dommy.loading.R;

/**
 * 简单环形进度条
 */
public class SimpleRoundProgress extends View {
    private Paint paint; // 画笔对象的引用
    private int roundColor; // 圆环的颜色
    private float roundWidth; // 圆环的宽度
    private int progressColor; // 圆环进度的颜色
    private float progressWidth; // 圆环进度的宽度
    private int max; // 最大进度
    private int style; // 进度的风格,实心或者空心
    private int startAngle; // 进度条起始角度
    public static final int STROKE = 0; // 样式:空心
    public static final int FILL = 1; // 样式:实心
    private int progress; // 当前进度

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

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

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

        paint = new Paint();

        // 读取自定义属性的值
        TypedArray mTypedArray = context.obtainStyledAttributes(attrs, R.styleable.SimpleRoundProgress);

        // 获取自定义属性和默认值
        roundColor = mTypedArray.getColor(R.styleable.SimpleRoundProgress_srp_roundColor, Color.RED);
        roundWidth = mTypedArray.getDimension(R.styleable.SimpleRoundProgress_srp_roundWidth, 5);
        progressColor = mTypedArray.getColor(R.styleable.SimpleRoundProgress_srp_progressColor, Color.GREEN);
        progressWidth = mTypedArray.getDimension(R.styleable.SimpleRoundProgress_srp_progressWidth, roundWidth);
        max = mTypedArray.getInteger(R.styleable.SimpleRoundProgress_srp_max, 100);
        style = mTypedArray.getInt(R.styleable.SimpleRoundProgress_srp_style, 0);
        startAngle = mTypedArray.getInt(R.styleable.SimpleRoundProgress_srp_startAngle, 90);

        mTypedArray.recycle();
    }

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

        int centerX = getWidth() / 2; // 获取圆心的x坐标
        int radius = (int) (centerX - roundWidth / 2); // 圆环的半径

        // step1 画最外层的大圆环
        paint.setStrokeWidth(roundWidth); // 设置圆环的宽度
        paint.setColor(roundColor); // 设置圆环的颜色
        paint.setAntiAlias(true); // 消除锯齿
        // 设置画笔样式
        switch (style) {
            case STROKE:
                paint.setStyle(Paint.Style.STROKE);
                break;
            case FILL:
                paint.setStyle(Paint.Style.FILL_AND_STROKE);
                break;
        }
        canvas.drawCircle(centerX, centerX, radius, paint); // 画出圆环

        // step2 画圆弧-画圆环的进度
        paint.setStrokeWidth(progressWidth); // 设置画笔的宽度使用进度条的宽度
        paint.setColor(progressColor); // 设置进度的颜色
        RectF oval = new RectF(centerX - radius , centerX - radius , centerX + radius , centerX + radius ); // 用于定义的圆弧的形状和大小的界限

        int sweepAngle = 360 * progress / max; // 计算进度值在圆环所占的角度
        // 根据进度画圆弧
        switch (style) {
            case STROKE:
                // 空心
                canvas.drawArc(oval, startAngle, sweepAngle, false, paint);
                break;
            case FILL:
                // 实心
                canvas.drawArc(oval, startAngle, sweepAngle, true, paint);
                break;
        }
    }

    /**
     * 设置进度的最大值
     * <p>根据需要,最大值一般设置为100,也可以设置为1000、10000等</p>
     *
     * @param max int最大值
     */
    public synchronized void setMax(int max) {
        if (max < 0) {
            throw new IllegalArgumentException("max not less than 0");
        }
        this.max = max;
    }

    /**
     * 获取进度
     *
     * @return int 当前进度值
     */
    public synchronized int getProgress() {
        return progress;
    }

    /**
     * 设置进度,此为线程安全控件
     *
     * @param progress 进度值
     */
    public synchronized void setProgress(int progress) {
        if (progress < 0) {
            throw new IllegalArgumentException("progress not less than 0");
        }
        if (progress > max) {
            progress = max;
        }
        this.progress = progress;
        // 刷新界面调用postInvalidate()能在非UI线程刷新
        postInvalidate();
    }
}

Use of circular progress bar

1. Page control configuration

When a custom control is used in a page, the class name should be used as the label, and the custom attribute should be introduced through the namespace, such as:

<?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"
    ...
    tools:context="com.dommy.loading.SimpleRoundActivity">

    ...

    <com.dommy.loading.widget.SimpleRoundProgress
        android:id="@+id/srp_stroke_0"
        android:layout_width="100dip"
        android:layout_height="100dip"
        android:layout_gravity="center"
        app:srp_max="100"
        app:srp_progressColor="@color/red_web"
        app:srp_roundColor="@color/pro_bg"
        app:srp_roundWidth="6dip"
        app:srp_startAngle="0"
        app:srp_style="STROKE" />

     ...
</LinearLayout>

The preview effect when the interface is written:
Preview effect
Because the default progress is 0, there is no foreground progress color.

2. Activity code control

Through the method com.dommy.loading.widget.SimpleRoundProgress#setProgress, you can set the control to display the specified progress, such as

srpStroke0.setProgress(39);

The effect is:
The effect of a progress value of 39
3. Make the progress bar move.
When the progress value is 39, if you want to make the progress bar move, you can make the progress value gradually increase from 0-39 to display, and control the progress value through a thread to gradually increase from 0-39 the code:

private void refresh() {
    final int percent = RandomUtil.getRandomPercent();
    new Thread(new Runnable() {
        Message msg = null;

        @Override
        public void run() {
            int start = 0;
            while (start <= percent) {
                msg = new Message();
                msg.what = MSG_REFRESH_PROGRESS;
                msg.arg1 = start;
                handler.sendMessage(msg);
                start++;
                try {
                    Thread.sleep(25);
                } catch (InterruptedException e) {
                }
            }
        }
    }).start();
}

The percent here is a random number from 0 to 100. When the percent is 39, it is the above display effect.
When the thread updates the value of the start variable, it notifies the handler to update the interface state. The definition of the handler is:

private Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        switch (msg.what) {
            case MSG_REFRESH_PROGRESS:
                srpStroke0.setProgress(msg.arg1);
                break;
        }
    }

};

Here is the effect:
Dynamic change effect

change effect

Since the value of the custom attribute is read when the graph is drawn, if you need to change the style of the circular progress, you can modify it directly through the attribute value, which is more convenient. The 6 circular progress in the effect animation originally posted in the article is changed by the style, which is essentially the SimpleRoundProgress control.

If you feel that the custom properties of the control are not enough, you can add and use it yourself, and then expand on this basis to make the progress effect you want.

Source code download

It will be added after uploading.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324480847&siteId=291194637