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

不想閱讀本文,可以直接到這里獲取源碼
閱讀本文你需要掌握
自定義屬性
ValueAnimator動畫
Viwe的測量、繪制
Paint和Path的用法
動手實現
拆解
在動手編碼之前,要靜下心來分析一下,這款View是怎樣組成的,也就是要把這個View拆解一下。分析后,不難發現主要有一下部分組成
知道這個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
|
//初始化畫筆及路徑 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