文章出處

  Android中自定義View并沒有什么可怕的,拿到一個需要自定義的View,首先要做的就是把它肢解,然后思考每一步是怎樣實現的,按分析的步驟一步一步的編碼實現,最后你就會發現達到了你想要的效果。本文就按這樣的步驟帶你打造一款精美的按鈕。


效果預覽

  在開始本文之前,照例,先看下實現后的效果,如下圖


不想閱讀本文,可以直接到這里獲取源碼

閱讀本文你需要掌握

自定義屬性
ValueAnimator動畫
Viwe的測量、繪制
Paint和Path的用法

動手實現

拆解

  在動手編碼之前,要靜下心來分析一下,這款View是怎樣組成的,也就是要把這個View拆解一下。分析后,不難發現主要有一下部分組成

  • 圓形背景
  • 圓環的背景
  • 圓環
  • 文字

知道這個View是怎樣組成的,然后完成相應部分的編碼,最后將這些部分按時間順序進行拼裝展示,就能達到文章開頭那樣的效果了。

分析原理

  經過拆解,知道了這個View都有那幾部分組成了,下面就來分析一下是怎樣將以上部分進行整合的

  1. 在沒點擊之前,是一個中間帶有文字的圓形。
  2. 點擊之后圓形縮小,當縮小到一定程度后,圓環背景出現,同時,圓環進度條開始加載。
  3. 如果進度條加載完成,則改變文字(回調接口),抬起手后恢復原來的形狀;如果沒有加載完成,抬起手后,恢復原裝,下次點擊從新執行此步驟。

為了理解清楚,可以自己畫一下流程圖。

編碼實現

  相信,經過分析拆解之后,你腦子里應該有一個實現的流程了,下面就動手開始實現吧!

先將需要的畫筆和路徑進行初始化

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);
}

自定義View需要經過三個重要的步驟,測量布局繪制,分別對應onMeasure(),onLayout(),onDraw()方法。這里的onLayout()主要是對自定義ViewGroup的,自定義View只要重寫onMeasure()onDraw()方法就行了,按照自定義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
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//獲得父View傳遞過來的寬度的大小和類型
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
//獲得父View傳遞過來的高度的大小和類型
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//初始化最終的寬高
int resultWidth = widthSize;
int resultHeight = heightSize;
//為了讓文字可以在背景(圓形)中完全顯示
if (mRadius * 2 < textPaint.measureText(contentText)) {
mRadius = (int) textPaint.measureText(contentText);
}
if (widthMode == MeasureSpec.AT_MOST) {
//獲取我們需要的寬度
int contentWidth = (mRadius + space + ringSize)*2+ getPaddingLeft() + getPaddingRight();
//得到最終的寬度
resultWidth = (contentWidth < widthSize) ? resultWidth : contentWidth;
}
if (heightMode == MeasureSpec.AT_MOST) {
//獲取我們需要的高度
int contentHeight = (mRadius + space + ringSize)*2 + getPaddingTop() + getPaddingBottom();
//得到最終的高度
resultHeight = (contentHeight < heightSize) ? resultHeight : contentHeight;
}
//設置測量后的寬高
setMeasuredDimension(resultWidth,resultHeight);
}

代碼中都有注釋,相信你可以看的懂。下面就開始畫我們需要的圓形,圓環背景,圓環和文字了,我們需要在onDraw()方法中進行作畫,代碼如下

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


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()