Android customizes a beautiful button with a progress bar

 There is nothing terrible about customizing View in Android. To get a View that needs to be customized, the first thing to do is to dismember it, then think about how each step is implemented, and implement the coding step by step according to the analysis steps, and finally you You will This article will take you to create a beautiful button according to such steps.


Effect preview

  Before starting this article, as usual, let's take a look at the effect after implementation, as shown below


If you don't want to read this article, you can get the source code directly here

To read this article you need to master

Custom property
ValueAnimator animation
Viwe's measurement, drawing
Paint and Path usage

hands-on

dismantling

  Before you start coding, you must calm down and analyze how this View is composed, that is, you need to disassemble this View. After analysis, it is not difficult to find that there are mainly the following components

  • round background
  • ring background
  • ring
  • Word

Know how this View is composed, then complete the coding of the corresponding parts, and finally assemble and display these parts in chronological order, you can achieve the effect at the beginning of the article.

Analysis principle

  After dismantling, I know that this View is composed of several parts. Let's analyze how to integrate the above parts.

  1. Before clicking, it is a circle with text in the middle.
  2. After clicking, the circle shrinks. When it shrinks to a certain extent, the circle background appears, and at the same time, the circle progress bar starts to load.
  3. If the progress bar is loaded, change the text (callback interface), and restore the original shape after lifting your hand; if the loading is not completed, lift your hand, restore the original shape, and perform this step again next time you click.

In order to understand clearly, you can draw a flowchart yourself.

Coding implementation

  I believe that after the analysis and disassembly, you should have a realization process in your mind. Let’s start to realize it!

First initialize the required brushes and paths

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//初始化画笔及路径
private void initPaintOrPath() {
circleBgPaint = new Paint();
circleBgPaint.setAntiAlias(true);
circleBgPaint.setStyle(Paint.Style.FILL_AND_STROKE);
ringBgPaint = new Paint();
ringBgPaint.setColor(ringBgColor.getColorForState(getDrawableState(),0));
ringBgPaint.setAntiAlias(true);
ringBgPaint.setStrokeWidth(ringSize);
ringBgPaint.setStyle(Paint.Style.STROKE);
ringPaint = new Paint();
ringPaint.setColor(ringColor.getColorForState(getDrawableState(),0));
ringPaint.setAntiAlias(true);
ringPaint.setStrokeWidth(ringSize);
ringPaint.setStyle(Paint.Style.STROKE);
path = new Path();
textPaint = new Paint();
textPaint.setAntiAlias(true);
textPaint.setTextAlign(Paint.Align.CENTER);
textPaint.setColor(textColor.getColorForState(getDrawableState(),0));
textPaint.setTextSize(textSize);
}

Custom View needs to go through three important steps, measurement , layout , and drawing , which correspond to onMeasure() , onLayout() , and onDraw() methods respectively. The onLayout() here is mainly for the custom ViewGroup. The custom View only needs to rewrite the onMeasure() and onDraw() methods. Follow the custom View routine, measure first, and look at the code directly

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Override 
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//Get the size and type of the width passed from the parent View
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec. getSize(widthMeasureSpec);
//Get the size and type of the height passed by the parent View
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//Initialize the final width and height
int resultWidth = widthSize;
int resultHeight = heightSize;
//In order for the text to be fully displayed in the background (circle)
if (mRadius * 2 < textPaint.measureText(contentText)) {
mRadius = (int) textPaint.measureText(contentText);
}
if (widthMode == MeasureSpec.AT_MOST) {
//Get the width we need
int contentWidth = (mRadius + space + ringSize)*2+ getPaddingLeft() + getPaddingRight() ;
//Get the final width
resultWidth = (contentWidth < widthSize) ? resultWidth : contentWidth;
}
if (heightMode == MeasureSpec.AT_MOST) {
//Get the height we need
int contentHeight = (mRadius + space + ringSize)*2 + getPaddingTop() + getPaddingBottom();
//Get the final height
resultHeight = (contentHeight < heightSize) ? resultHeight : contentHeight;
}
//Set the measured width and height
setMeasuredDimension(resultWidth,resultHeight);
}

There are comments in the code, I believe you can understand. Let's start to draw the circle, ring background, ring and text we need. We need to draw in the onDraw() method. The code is as follows

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//画圆,改变ringRadius就可以改变圆形背景的大小,主要控制value值的改变
ringRadius = mRadius - DPUtils.dip2px(getContext(),value/2);
circleBgPaint.setColor(circleColor.getColorForState(getDrawableState(),0));
canvas.drawCircle(getWidth() / 2, getHeight() / 2, ringRadius, circleBgPaint);
//用户按键时开始画圆环
if (startDrawLine){
//计算外环的半径,记得要减去外环的宽度的一半
result = ringRadius + space +ringSize/2;
//画完整的进度条
canvas.drawCircle(getWidth() / 2, getHeight() / 2, result, ringBgPaint);
//画进度条路径
path.reset();//重置路径,否则下次精度条不会从开始位置,可以注释掉此代码,看下效果
//计算画路径的矩形
float left = getWidth()/2-result;
float top = getHeight()/2-result;
float right = getWidth()/2+result;
float bottom = getHeight()/2+result;
RectF rect = new RectF(left,top, right ,bottom);
path.addArc(rect, -90, angle);//通过改变angle就可以动态的改变进度条
//画圆环的路径
canvas.drawPath(path, ringPaint);
}
canvas.drawText(contentText,getWidth()/2,getHeight()/2,textPaint);//文字
}

完成以上几步,点击view时并没有反应,因为还没有为View添加触摸事件,也没有添加动画,进过分析原理那步,可以知道,手指按下时改变圆形背景的大小,既改变半径的大小……,这里就不在重复说了,直接看代码,代码中会有讲解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
@Override
public boolean onTouchEvent(final MotionEvent event) {
//控制加载完成时候是否还可以相应点击事件,可以通过setEnable()方法来控制
if (!enable && event.getAction()!=MotionEvent.ACTION_UP) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
//当手指按下时,移除手指抬起时的监听
if (animator != null) {
animator.removeAllUpdateListeners();
}
//改变narrowDown的值
animatorValue = ValueAnimator.ofInt(0, narrowDown);
animatorValue.setDuration(50);
animatorValue.setInterpolator(new LinearInterpolator());
animatorValue.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
value = (int) valueAnimator.getAnimatedValue();//改变value的值也就是按下手指让圆形背景缩小
if (value == narrowDown) {
//当缩小到一定值时开始画圆环和精度条
startDrawLine = true;//控制什么时候开始画圆环和进度条
animatorValue.removeAllUpdateListeners();//当开始画进度条时移除改变背景大小的动画,既停止改变
}
postInvalidate();//刷新画布
}
});
animatorValue.start();//开始缩小

angleAnimator = ValueAnimator.ofFloat(0, 360f);
angleAnimator.setDuration(2000);
angleAnimator.setInterpolator(new LinearInterpolator());
angleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
angle = (float) valueAnimator.getAnimatedValue();//angle用来画进度条,动态改变进度条加载的进度
if (angle == 360) {
//加载完成移除动画,既进度条停止加载
angleAnimator.removeAllUpdateListeners();
//进度条加载完成后的回调
onViewClick.onFinish(ImitateKeepButton.this);
}
postInvalidate();
}
});
angleAnimator.start();//开始加载
}
break;
case MotionEvent.ACTION_UP: {
angleAnimator.removeAllUpdateListeners();
animatorValue.removeAllUpdateListeners();
animator = ValueAnimator.ofInt(value,0);
animator.setDuration(300);
animator.setInterpolator(new LinearInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
value = (int) valueAnimator.getAnimatedValue();
postInvalidate();
}
});
animator.start();//开始恢复背景原来的大小
}
startDrawLine = false;
break;
}
return true;
}

好了,到这里已经达到了文章开始时的效果,可以结束本文了。

结束语

  文中代码,只是粘贴部分比较重要的,不完整,完整代码可以到这里获取源码

转载请注明出处:www.wizardev.com

Guess you like

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