Copyright notice: This article is the blogger’s original article, shall not be reproduced without the permission of the blogger.

Android Development from Scratch series

Source: github.com/AnliaLee/Pr… , welcome to star

If you see any mistakes or have any good suggestions, please leave a comment


preface: I used to use the wheel made by others, but I did not know why. Sometimes I forgot what others wrote for several months, so I opened a pit to write down the realization and thinking step by step, and made a series. Since there is not much work time, try to masturbate once or twice a week

This article only focuses on the ideas and implementation steps, some knowledge principles used in it will not be very detailed. If there are unclear APIS or methods, you can search the corresponding information on the Internet, there must be a god to explain very clearly, I will not present the ugly. In the spirit of serious and responsible, I will post the relevant knowledge of the blog links (in fact, is lazy do not want to write so much ha ha), you can send their own

Results show


Draw an arc

Related blog links

【Android – custom View】 custom View analysis

Android custom View (1)

Paint’s functions are summarized in part 7 of the Custom Control trilogy

In line with the idea of writing after optimization, how simple how to, such as parameter definition, custom View size adaptation of what are left alone, and then slowly fill in the pit, we will simply draw an arc first (in order to better observe the arc drawing area, we will draw the rectangular area of the arc). The following code

public class CircleBarView extends View {

    private Paint rPaint;// Draw the rectangle brush
    private Paint progressPaint;// Draw the arc brush

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

    private void init(Context context,AttributeSet attrs){
        rPaint = new Paint();
        rPaint.setStyle(Paint.Style.STROKE);// Just stroke, no padding
        rPaint.setColor(Color.RED);

        progressPaint = new Paint();
        progressPaint.setStyle(Paint.Style.STROKE);// Just stroke, no padding
        progressPaint.setColor(Color.BLUE);
        progressPaint.setAntiAlias(true);// Set anti-aliasing
    }

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

        float x = 50;
        float y = 50;
        RectF rectF = new RectF(x,y,x+300,y+300);// Create a 300 by 300 square area

        canvas.drawArc(rectF,0.270.false,progressPaint);// The Angle 0 corresponds to the 3 o 'clock direction, increasing clockwisecanvas.drawRect(rectF,rPaint); }}Copy the code

Interface layout:

<?xml version="1.0" encoding="utf-8"? >
<RelativeLayout 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">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <com.anlia.progressbar.CircleBarView
            android:id="@+id/circle_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </LinearLayout>
</RelativeLayout>
Copy the code

Register in the Activity

circleBarView = (CircleBarView)findViewById(R.id.circle_view);
Copy the code

The effect is shown in figure


Animates the arc

We need to add an Animation effect to the drawing of the arc, which mainly uses Animation knowledge. Let’s change the previous CircleBarView code

private float sweepAngle;// The Angle of the arc

private void init(Context context,AttributeSet attrs){
    // omit some code...
	anim = new CircleAnim();
}

@Override
protected void onDraw(Canvas canvas) {
    // omit some code...
	canvas.drawArc(rectF,0,sweepAngle,false,progressPaint);
}

public class CircleBarAnim extends Animation{

	public CircleBarAnim(a){}@Override
	protected void applyTransformation(float interpolatedTime, Transformation t) {
		super.applyTransformation(interpolatedTime, t);
		sweepAngle = interpolatedTime * 360; postInvalidate(); }}// Write a method to an external call to set the animation time
public void setProgressNum(int time) {
	anim.setDuration(time);
	this.startAnimation(anim);
}
Copy the code

Call the setProgressNum() method in the Activity

circleBarView.setProgressNum(3000);// Set the animation time to 3000 milliseconds, i.e. 3 seconds
Copy the code

The effect is shown in figure


Add background arc

According to our final rendering, we also need to draw a complete arc as the background of the progress bar arc. At the same time, we need to modify the setProgressNum() method to pass in the value of the current progress bar as a parameter to calculate the drawing Angle of the progress bar. So let’s continue with our CircleBarView code

private Paint bgPaint;// Brush to draw the background arc

private float progressNum;// The progress bar value that can be updated
private float maxNum;// Maximum progress bar

private float progressSweepAngle;// The Angle of the progress bar arc sweep
private float startAngle;// Start Angle of the background arc
private float sweepAngle;// The Angle swept by the background arc

private void init(Context context,AttributeSet attrs){
	// omit some code...
	progressPaint = new Paint();
	progressPaint.setStyle(Paint.Style.STROKE);// Just stroke, no padding
	progressPaint.setColor(Color.GREEN);
	progressPaint.setAntiAlias(true);// Set anti-aliasing
	progressPaint.setStrokeWidth(15);// Set a random brush width to see what the effect is, and then use the attr custom property to set it

	bgPaint = new Paint();
	bgPaint.setStyle(Paint.Style.STROKE);// Just stroke, no padding
	bgPaint.setColor(Color.GRAY);
	bgPaint.setAntiAlias(true);// Set anti-aliasing
	bgPaint.setStrokeWidth(15);

	progressNum = 0;
	maxNum = 100;// Set it arbitrarily

	startAngle = 0;
	sweepAngle = 360;
}

@Override
protected void onDraw(Canvas canvas) {
	super.onDraw(canvas);
	// omit some code...
	canvas.drawArc(rectF,startAngle,sweepAngle,false,bgPaint);
	canvas.drawArc(rectF,startAngle,progressSweepAngle,false,progressPaint);
}

public class CircleBarAnim extends Animation{

	public CircleBarAnim(a){}@Override
	protected void applyTransformation(float interpolatedTime, Transformation t) {
		super.applyTransformation(interpolatedTime, t);
		progressSweepAngle = interpolatedTime * sweepAngle * progressNum / maxNum;// The progress bar ratio is calculated herepostInvalidate(); }}public void setProgressNum(float progressNum, int time) {
	// omit some code...
	this.progressNum = progressNum;
}
Copy the code

Call the setProgressNum() method in the Activity

circleBarView.setProgressNum(100.3000);
Copy the code

The effect is shown in figure


Measure and adapt the width and height of circular progress bar View

Related blog links

Talk about the width and height of custom View

Android custom View

In the above code, the width and height of the View is determined by the size of the rectangle to draw the arc, which is written in init(). The actual requirement is that the width and height of the View can be set externally, and the View should be a square regardless of the width and height. So we need to rewrite the View’s onMeasure() method

Continue to modify our CircleBarView code

private RectF mRectF;// Draw the rectangular region of the arc

private float barWidth;// Width of arc progress bar
private int defaultSize;// Customize the View's default width and height

private void init(Context context,AttributeSet attrs){
	// omit some code...
	defaultSize = DpOrPxUtils.dip2px(context,100);
	barWidth = DpOrPxUtils.dip2px(context,10);
	mRectF = new RectF();
	
	progressPaint.setStrokeWidth(barWidth);
	
	bgPaint.setStrokeWidth(barWidth);
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
	super.onMeasure(widthMeasureSpec, heightMeasureSpec);

	int height = measureSize(defaultSize, heightMeasureSpec);
	int width = measureSize(defaultSize, widthMeasureSpec);
	int min = Math.min(width, height);// Get the length of the shortest edge of the View
	setMeasuredDimension(min, min);// Force View to be a square with the shortest edge

	if(min >= barWidth*2) {// This simply limits the maximum width of the arc
		mRectF.set(barWidth/2,barWidth/2,min-barWidth/2,min-barWidth/2); }}private int measureSize(int defaultSize,int measureSpec) {
	int result = defaultSize;
	int specMode = View.MeasureSpec.getMode(measureSpec);
	int specSize = View.MeasureSpec.getSize(measureSpec);

	if (specMode == View.MeasureSpec.EXACTLY) {
		result = specSize;
	} else if (specMode == View.MeasureSpec.AT_MOST) {
		result = Math.min(result, specSize);
	}
	return result;
}
Copy the code

It uses the tool class of dp and PX to convert each other (relevant knowledge can be interested in their own Internet search), here will also post the relevant code

public class DpOrPxUtils {
    public static int dip2px(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5 f);
    }
    public static int px2dip(Context context, float pxValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (pxValue / scale + 0.5 f); }}Copy the code

Custom attR attributes

Related blog links

Android custom View (2, in-depth analysis of custom attributes)

TypedArray calls Recycle ()

We have many properties in the View of the need to be set in the layout file, such as a progress bar color, width and so on, here we choose five of the most basic attribute (the rest according to the need to further expand) : the progress bar arc color, background color, start Angle and sweep Angle (maximum length can be understood as a progress bar), the progress bar width for the custom

Start by adding attr. XML to the res\ Values folder to customize the CircleBarView properties

<?xml version="1.0" encoding="utf-8"? >
<resources>
    <! The name of the custom View must be the same as the name of the custom View. Otherwise, the layout file cannot be referenced.
    <declare-styleable name="CircleBarView">
        <attr name="progress_color" format="color"></attr>
        <attr name="bg_color" format="color"></attr>

        <attr name="bar_width" format="dimension"></attr>

        <attr name="start_angle" format="float"></attr>
        <attr name="sweep_angle" format="float"></attr>
    </declare-styleable>
</resources>
Copy the code

Modify CircleBarView to assign values to custom attributes

private int progressColor;// Progress bar arc color
private int bgColor;// Background arc color
private float startAngle;// Start Angle of the background arc
private float sweepAngle;// The Angle swept by the background arc
private float barWidth;// Width of arc progress bar

private void init(Context context,AttributeSet attrs){
	// omit some code...
	TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.CircleBarView);

	progressColor = typedArray.getColor(R.styleable.CircleBarView_progress_color,Color.GREEN);// The default is green
	bgColor = typedArray.getColor(R.styleable.CircleBarView_bg_color,Color.GRAY);// The default is grey
	startAngle = typedArray.getFloat(R.styleable.CircleBarView_start_angle,0);// Default is 0
	sweepAngle = typedArray.getFloat(R.styleable.CircleBarView_sweep_angle,360);// The default is 360
	barWidth = typedArray.getDimension(R.styleable.CircleBarView_bar_width,DpOrPxUtils.dip2px(context,10));// The default is 10dp
	typedArray.recycle();// When the typedArray is used, it needs to be reclaimed to prevent memory leaks
  

	progressPaint.setColor(progressColor);
	progressPaint.setStrokeWidth(barWidth);
	
	bgPaint.setColor(bgColor);
	bgPaint.setStrokeWidth(barWidth);
}

@Override
protected void onDraw(Canvas canvas) {
	super.onDraw(canvas);
	canvas.drawArc(mRectF,startAngle,sweepAngle,false,bgPaint);
	canvas.drawArc(mRectF,startAngle,progressSweepAngle,false, progressPaint);
}
Copy the code

Try setting custom properties in the layout file

<! -- Omit some code -->
<RelativeLayout 
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <com.anlia.progressbar.CircleBarView
            app:start_angle="135"
            app:sweep_angle="270"
            app:progress_color="@color/red"
            app:bg_color="@color/gray_light"
            app:bar_width="20dp"/>
    </LinearLayout>
</RelativeLayout>
Copy the code

The effect is shown in figure


Realize the text effect that can follow the progress bar

According to the requirement, we need to display the text that can follow the progress of the progress bar. Many online methods are to implement the corresponding text processing logic in the custom View, and then use the canvas.drawtext () method to draw the text. I personally feel that this is more troublesome and scalability is not high, the following provides another way of thinking for your reference

What I did was to separate the bar from the text display. The text display component used TextView directly in the layout file, passed TextView into CircleBarView, and then used the interface provided by CircleBarView to write the text processing logic. The advantage of this implementation is that if we want to change the font, style and position of the text in the later stage, we do not need to change the text in the CircleBarView, so that the text and the progress bar can be decoupled

The concrete implementation is as follows, modify our CircleBarView

private TextView textView;
private OnAnimationListener onAnimationListener;
public class CircleBarAnim extends Animation{
	// omit some code...
	@Override
	protected void applyTransformation(float interpolatedTime, Transformation t) {
		super.applyTransformation(interpolatedTime, t);
		// omit some code...
		if(textView ! =null&& onAnimationListener! =null){ textView.setText(onAnimationListener.howToChangeText(interpolatedTime,progressNum,maxNum)); }}}/** * Sets the TextView to display the text@param textView
 */
public void setTextView(TextView textView) {
	this.textView = textView;
}

public interface OnAnimationListener {
	/** * how to handle the text content to display *@paramInterpolatedTime interpolates from 0 to 1, ending the animation at 1 *@paramProgressNum Progress bar *@paramMaxNum Maximum value of the progress bar *@return* /
	String howToChangeText(float interpolatedTime, float progressNum, float maxNum);
}
Copy the code

The interface is then invoked in the Activity

circleBarView.setOnAnimationListener(new CircleBarView.OnAnimationListener() {
	@Override
	public String howToChangeText(float interpolatedTime, float progressNum, float maxNum) {
		DecimalFormat decimalFormat=new DecimalFormat("0.00");
		String s = decimalFormat.format(interpolatedTime * progressNum / maxNum * 100) + "%";
		returns; }}); circleBarView.setProgressNum(80.3000);
Copy the code

The layout file is also modified

<RelativeLayout
	android:layout_width="100dp"
	android:layout_height="100dp"
	android:layout_gravity="center_horizontal"
	android:layout_marginTop="10dp">
	<com.anlia.progressbar.CircleBarView
		android:id="@+id/circle_view"
		android:layout_width="match_parent"
		android:layout_height="match_parent"
		android:layout_gravity="center_horizontal"
		app:start_angle="135"
		app:sweep_angle="270"
		app:progress_color="@color/red"
		app:bg_color="@color/gray_light"
		app:bar_width="8dp"/>
	<TextView
		android:id="@+id/text_progress"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:layout_centerVertical="true"
		android:layout_centerHorizontal="true"/>
</RelativeLayout>
Copy the code

So let’s see what happens

This is just a little bit of an idea that you can extend the interface on if you have more complex requirements


Progress bar color gradient animation

Related blog links

Android gets a linear gradient to a certain point of color

On the basis of the previous point to do a little bit of extension, to achieve the progress bar color gradient animation, the color linear gradient algorithm provided by @aaron_cxx, if you are interested in color gradient algorithm can see the big blog

Let’s continue with our CircleBarView by extending our OnAnimationListener interface a bit

public interface OnAnimationListener {
	// omit some code...
	/** * How to handle the color of the progress bar *@paramPaint Progress bar brushes *@paramInterpolatedTime interpolates from 0 to 1, ending the animation at 1 *@paramProgressNum Progress bar *@paramMaxNum Maximum value of the progress bar */
	void howTiChangeProgressColor(Paint paint, float interpolatedTime, float progressNum, float maxNum);
}

Remember to call howTiChangeProgressColor in our animation
public class CircleBarAnim extends Animation{
	// omit some code...
	@Override
	protected void applyTransformation(float interpolatedTime, Transformation t) {
		super.applyTransformation(interpolatedTime, t);
		// omit some code...onAnimationListener.howTiChangeProgressColor(progressPaint,interpolatedTime,progressNum,maxNum); }}Copy the code

Similarly, write the corresponding logic in the Activity

circleBarView.setOnAnimationListener(new CircleBarView.OnAnimationListener() {
	// omit some code...
	@Override
	public void howTiChangeProgressColor(Paint paint, float interpolatedTime, float progressNum, float maxNum) {
		LinearGradientUtil linearGradientUtil = newLinearGradientUtil(Color.YELLOW,Color.RED); paint.setColor(linearGradientUtil.getColor(interpolatedTime)); }});Copy the code

The effect is shown in figure

If you feel good, please give me a thumbs up. Your support is my biggest motivation. If you want to extend some new features, you can also leave a message in the comment section