序言:最近空閑的時(shí)候一直在學(xué)習(xí)自定義View的相關(guān)知識,這也是LZ最近半年的學(xué)習(xí)對象植旧,有的時(shí)候就是要給自己定下一個(gè)小目標(biāo),咱們沒有王老板的先賺他一個(gè)億這么豪氣,也得先有個(gè)目標(biāo)不是荚恶。逛博客的時(shí)候看到支付寶支付成功失敗的動畫效果,剛好最近在學(xué)習(xí)Path的相關(guān)知識磷支,就想著實(shí)踐一下谒撼,也鞏固一下自己所學(xué)的知識,話不多說直接上圖雾狈。
這個(gè)也是公司項(xiàng)目中需要的廓潜,之前由于項(xiàng)目緊,直接讓UI切了個(gè)圖善榛,就這樣上了辩蛋,這不太符合我的一貫作風(fēng),但是沒辦法>_<
首先我們來分解一下這個(gè)動作移盆,首先是一段progressDialog悼院,可以看做是在請求數(shù)據(jù)等待過程,然后成功之后顯示成功的動畫咒循,失敗之后顯示失敗的動畫据途,那么這里涉及到三個(gè)狀態(tài),加載中叙甸、加載成功和加載失敗颖医,這里我們使用枚舉來實(shí)現(xiàn)這三種狀態(tài)。首先呢裆蒸,我們先來實(shí)現(xiàn)這個(gè)等待的進(jìn)度條:
1熔萧、畫一個(gè)圓,確切的來說是畫一段圓弧僚祷,然后旋轉(zhuǎn)畫布佛致,在此過程中不斷修改圓弧的大小,造成一個(gè)這樣動態(tài)的假象:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(getPaddingLeft(), getPaddingTop()); //將當(dāng)前畫布的點(diǎn)移到getPaddingLeft,getPaddingTop,后面的操作都以該點(diǎn)作為參照點(diǎn)
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); //旋轉(zhuǎn)的弧長為4
canvas.drawArc(new RectF(0, 0, progressRadius * 2, progressRadius * 2), startAngle, sweepAngle, false, mPaint);
invalidate();
}
}
這里
startAngle
表示圓弧的起始角度辙谜,sweepAngle
表示圓弧掃過的角度俺榆,minAngle
是一個(gè)過渡值,是為了幫助startAngle
改變值而用到的筷弦。這里用到了畫弧度的方法肋演,在上一篇博客中我有細(xì)講這個(gè)方法抑诸,如果你還不知道的話請移步Android自定義view之圓形進(jìn)度條,這里還用到了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);
}
這個(gè)方法主要是將畫布進(jìn)行旋轉(zhuǎn)蜕乡,我們可以看到,先是將畫布平移到某個(gè)點(diǎn)梗夸,然后再旋轉(zhuǎn)某個(gè)角度层玲,最后再平移回去,這樣做的目的是為了讓需要旋轉(zhuǎn)的
View
進(jìn)行中心對稱旋轉(zhuǎn)反症,所以后面?zhèn)鞯?code>PX,PY值需要是View
寬高的一半辛块,不信的話你可以去做個(gè)實(shí)驗(yàn);說了這么多我們直接來看一下效果:
2铅碍、畫成功狀態(tài)的動畫润绵,這部分也可以分成兩個(gè)小部分,先是畫一個(gè)圓胞谈,然后再畫中間的鉤:
(1)尘盼、畫圓:上一篇博客中講了通過進(jìn)度來畫弧進(jìn)而來畫整個(gè)圓,今天我們的主角是Path
烦绳,所以我們使用Path
來實(shí)現(xiàn)這樣一個(gè)效果卿捎,還是先上代碼,通過代碼來講解:
//追蹤Path的坐標(biāo)
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中添加了一個(gè)圓的路徑享扔,具體Path
常見的用法如下表所示:
Path的常見方法 | 方法含義 |
---|---|
moveTo() | 該方法移動后續(xù)操作的起點(diǎn)坐標(biāo) |
lineTo() | 該方法是連接起始點(diǎn)與某一點(diǎn)(傳的參數(shù))形成一條線 |
setLastPath() | 該方法是設(shè)置Path最后的坐標(biāo) |
close() | 該方法是將起點(diǎn)坐標(biāo)與終點(diǎn)坐標(biāo)連接起來形成一個(gè)閉合的圖形(如果始終點(diǎn)左邊能連接的話) |
addRect() | 該方法是繪制一個(gè)巨型 |
addRoundRect() | 該方法是繪制一個(gè)圓角矩形 |
addOval() | 該方法是繪制一個(gè)橢圓 |
arcTo() | 該方法是繪制一段圓弧 |
addArc() | 該方法是繪制一段圓弧 |
然后呢底桂,這里有一個(gè)
PathMeasure
,簡單點(diǎn)說伪很,這玩意就是用來實(shí)現(xiàn)Path
坐標(biāo)點(diǎn)的追蹤戚啥,你也可以認(rèn)為是Path
坐標(biāo)的計(jì)算器,具體PathMeasure
的常見的用法如下表所示:
PathMeasure的常見方法 | 方法含義 |
---|---|
setPath() | 該方法將path與PathMeasure綁定起來 |
getLength() | 該方法用于獲得path路徑的長度 |
getSegment() | 該方法用于截取整個(gè)Path的片段 |
nextContour() | 該方法用于切換到下一個(gè)路徑 |
這里我們通過動畫從
0——1
之間的變化锉试,來改變所畫圓的弧度:
circleAnimator = ValueAnimator.ofFloat(0, 1);
circleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
circleValue = (float) animation.getAnimatedValue();
invalidate();
}
});
(2)、接下來畫完圓之后览濒,我們要開始畫對鉤了:
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);
}
這里的坐標(biāo)我是根據(jù)UI給的圖大致算出來的呆盖,可以參考下面這張圖的虛線和實(shí)現(xiàn),對鉤的起始坐標(biāo)在坐標(biāo)軸中大致是
getWidth() / 8 * 3
贷笛,你也可以根據(jù)你的需求來畫出這個(gè)對鉤应又,其實(shí)就是兩段路徑,分別用path
的lineTo
方法來實(shí)現(xiàn):
同理乏苦,畫叉叉也是一樣的株扛,只要你算出叉在坐標(biāo)軸中的坐標(biāo)就ok了尤筐,這里我也給出一張參考圖:
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);
}
參考:
Android自定義之仿支付寶支付成功、失敗狀態(tài)的加載進(jìn)度
到此就完成了自定義的原型進(jìn)度條了洞就。源碼已上傳至Github盆繁,有需要的同學(xué)可以下載下來看看,歡迎Star旬蟋,F(xiàn)ork