序言:最近空閒的時候一直在學習自定義View的相關知識,這也是LZ最近半年的學習物件,有的時候就是要給自己定下一個小目標,我們們沒有王老闆的先賺他一個億這麼豪氣,也得先有個目標不是。逛部落格的時候看到支付寶支付成功失敗的動畫效果,剛好最近在學習Path的相關知識,就想著實踐一下,也鞏固一下自己所學的知識,話不多說直接上圖。
![](https://i.iter01.com/images/b56add1602b95bfb19fa53dd60632270b855013515ec45372bffa4d9d8c9d4e3.gif)
這個也是公司專案中需要的,之前由於專案緊,直接讓UI切了個圖,就這樣上了,這不太符合我的一貫作風,但是沒辦法>_<
首先我們來分解一下這個動作,首先是一段progressDialog,可以看做是在請求資料等待過程,然後成功之後顯示成功的動畫,失敗之後顯示失敗的動畫,那麼這裡涉及到三個狀態,載入中、載入成功和載入失敗,這裡我們使用列舉來實現這三種狀態。首先呢,我們先來實現這個等待的進度條:
1、畫一個圓,確切的來說是畫一段圓弧,然後旋轉畫布,在此過程中不斷修改圓弧的大小,造成一個這樣動態的假象:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(getPaddingLeft(), getPaddingTop()); //將當前畫布的點移到getPaddingLeft,getPaddingTop,後面的操作都以該點作為參照點
if (mStatus == StatusEnum.Loading) { //正在載入
if (startAngle == minAngle) {
sweepAngle += 6;
}
if (sweepAngle >= 300 || startAngle > minAngle) {
startAngle += 6;
if (sweepAngle > 20) {
sweepAngle -= 6;
}
}
if (startAngle > minAngle + 300) {
startAngle %= 360;
minAngle = startAngle;
sweepAngle = 20;
}
canvas.rotate(curAngle += 4, progressRadius, progressRadius); //旋轉的弧長為4
canvas.drawArc(new RectF(0, 0, progressRadius * 2, progressRadius * 2), startAngle, sweepAngle, false, mPaint);
invalidate();
}
}複製程式碼
這裡
startAngle
表示圓弧的起始角度,sweepAngle
表示圓弧掃過的角度,minAngle
是一個過渡值,是為了幫助startAngle
改變值而用到的。這裡用到了畫弧度的方法,在上一篇部落格中我有細講這個方法,如果你還不知道的話請移步Android自定義view之圓形進度條,這裡還用到了rotate
方法,來看一下它的原始碼解釋:
/**
* Preconcat the current matrix with the specified rotation.
*
* @param degrees The amount to rotate, in degrees
* @param px The x-coord for the pivot point (unchanged by the rotation)
* @param py The y-coord for the pivot point (unchanged by the rotation)
*/
public final void rotate(float degrees, float px, float py) {
translate(px, py);
rotate(degrees);
translate(-px, -py);
}複製程式碼
這個方法主要是將畫布進行旋轉,我們可以看到,先是將畫布平移到某個點,然後再旋轉某個角度,最後再平移回去,這樣做的目的是為了讓需要旋轉的
View
進行中心對稱旋轉,所以後面傳的PX,PY
值需要是View
寬高的一半,不信的話你可以去做個實驗;說了這麼多我們直接來看一下效果:
![](https://i.iter01.com/images/34763769110813db41b340e09efc4629b25af2f876943141cf718cb28e08be09.gif)
2、畫成功狀態的動畫,這部分也可以分成兩個小部分,先是畫一個圓,然後再畫中間的鉤:
(1)、畫圓:上一篇部落格中講了通過進度來畫弧進而來畫整個圓,今天我們的主角是Path
,所以我們使用Path
來實現這樣一個效果,還是先上程式碼,通過程式碼來講解:
//追蹤Path的座標
private PathMeasure mPathMeasure;
//畫圓的Path
private Path mPathCircle;
//擷取PathMeasure中的path
private Path mPathCircleDst;
mPaint.setColor(loadSuccessColor);
mPathCircle.addCircle(getWidth() / 2, getWidth() / 2, progressRadius, Path.Direction.CW);
mPathMeasure.setPath(mPathCircle, false);
mPathMeasure.getSegment(0, circleValue * mPathMeasure.getLength(), mPathCircleDst, true);
canvas.drawPath(mPathCircleDst, mPaint);複製程式碼
Path
的常用方法有,add
一條路徑(任意形狀,任意線條),這裡我們在path中新增了一個圓的路徑,具體Path
常見的用法如下表所示:
Path的常見方法 | 方法含義 |
---|---|
moveTo() | 該方法移動後續操作的起點座標 |
lineTo() | 該方法是連線起始點與某一點(傳的引數)形成一條線 |
setLastPath() | 該方法是設定Path最後的座標 |
close() | 該方法是將起點座標與終點座標連線起來形成一個閉合的圖形(如果始終點左邊能連線的話) |
addRect() | 該方法是繪製一個巨型 |
addRoundRect() | 該方法是繪製一個圓角矩形 |
addOval() | 該方法是繪製一個橢圓 |
arcTo() | 該方法是繪製一段圓弧 |
addArc() | 該方法是繪製一段圓弧 |
然後呢,這裡有一個
PathMeasure
,簡單點說,這玩意就是用來實現Path
座標點的追蹤,你也可以認為是Path
座標的計算器,具體PathMeasure
的常見的用法如下表所示:
PathMeasure的常見方法 | 方法含義 |
---|---|
setPath() | 該方法將path與PathMeasure繫結起來 |
getLength() | 該方法用於獲得path路徑的長度 |
getSegment() | 該方法用於擷取整個Path的片段 |
nextContour() | 該方法用於切換到下一個路徑 |
這裡我們通過動畫從
0——1
之間的變化,來改變所畫圓的弧度:
circleAnimator = ValueAnimator.ofFloat(0, 1);
circleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
circleValue = (float) animation.getAnimatedValue();
invalidate();
}
});複製程式碼
(2)、接下來畫完圓之後,我們要開始畫對鉤了:
![對鉤](https://i.iter01.com/images/9c3f4e47ad4a991f9bc45b63aef8db0ddf6130d29cc86648e08250f6bb22d293.png)
if (circleValue == 1) { //表示圓畫完了,可以鉤了
successPath.moveTo(getWidth() / 1 * 3, getWidth() / 2);
successPath.lineTo(getWidth() / 2, getWidth() / 5 * 3);
successPath.lineTo(getWidth() / 3 * 2, getWidth() / 5 * 2);
mPathMeasure.nextContour();
mPathMeasure.setPath(successPath, false);
mPathMeasure.getSegment(0, successValue * mPathMeasure.getLength(), mPathCircleDst, true);
canvas.drawPath(mPathCircleDst, mPaint);
}複製程式碼
這裡的座標我是根據UI給的圖大致算出來的,可以參考下面這張圖的虛線和實現,對鉤的起始座標在座標軸中大致是
getWidth() / 8 * 3
,你也可以根據你的需求來畫出這個對鉤,其實就是兩段路徑,分別用path
的lineTo
方法來實現:
![成功畫對勾](https://i.iter01.com/images/20b420dd0969bf28ace9e02663d389d23e208d0ef7f143bca634ea3ccc9ea28c.gif)
同理,畫叉叉也是一樣的,只要你算出叉在座標軸中的座標就ok了,這裡我也給出一張參考圖:
![叉叉](https://i.iter01.com/images/0e099ae5eeda5785cf3cc5f5a77c7e5417ded90d9fb37267f0cbde673cf5e3c3.png)
mPaint.setColor(loadFailureColor);
mPathCircle.addCircle(getWidth() / 2, getWidth() / 2, progressRadius, Path.Direction.CW);
mPathMeasure.setPath(mPathCircle, false);
mPathMeasure.getSegment(0, circleValue * mPathMeasure.getLength(), mPathCircleDst, true);
canvas.drawPath(mPathCircleDst, mPaint);
if (circleValue == 1) { //表示圓畫完了,可以畫叉叉的右邊部分
failurePathRight.moveTo(getWidth() / 3 * 2, getWidth() / 3);
failurePathRight.lineTo(getWidth() / 3, getWidth() / 3 * 2);
mPathMeasure.nextContour();
mPathMeasure.setPath(failurePathRight, false);
mPathMeasure.getSegment(0, failValueRight * mPathMeasure.getLength(), mPathCircleDst, true);
canvas.drawPath(mPathCircleDst, mPaint);
}
if (failValueRight == 1) { //表示叉叉的右邊部分畫完了,可以畫叉叉的左邊部分
failurePathLeft.moveTo(getWidth() / 3, getWidth() / 3);
failurePathLeft.lineTo(getWidth() / 3 * 2, getWidth() / 3 * 2);
mPathMeasure.nextContour();
mPathMeasure.setPath(failurePathLeft, false);
mPathMeasure.getSegment(0, failValueLeft * mPathMeasure.getLength(), mPathCircleDst, true);
canvas.drawPath(mPathCircleDst, mPaint);
}複製程式碼
![失敗畫叉叉](https://i.iter01.com/images/361832b71d9d941044c16d6b4d5ceb7574ac6b5512c71e4b912dc3be485835dd.gif)
參考:
到此就完成了自定義的原型進度條了。原始碼已上傳至Github,有需要的同學可以下載下來看看,歡迎Star,Fork
同時感謝以上引用到部落格的主人,感謝!!!
![公眾號:Android技術經驗分享](https://i.iter01.com/images/b9d0a7eafc62e08d9c3babc32d54b0d6f9c3a6c5ef9d6f35b43351e876fa1f70.jpg)