TicktockMusic 音樂播放器項目相關文章匯總:
- Clean Architecture 架構:http://www.lxweimin.com/p/15ea0fecb61d
- 開源庫封裝:http://www.lxweimin.com/p/1645b81dc994
- 自定義播放暫停按鈕:http://www.lxweimin.com/p/74f38e9b16fc
- 自定義歌詞控件:http://www.lxweimin.com/p/ab735509cc74
最近想寫個音樂播放器,偶然看到輕聽這款播放器的播放和暫停按鈕,在切換過程中的動畫很是吸引我。本著造輪子(其實是 github 上邊沒找到)的想法,就花了點時間擼出來了這個效果。
效果就是下邊這個樣子:
下邊說下實現方法,中間也踩了一些坑。
測量及初始化
首先要確實View的寬高,在這里由于是圓形按鈕,所以設置寬高相等,onMeasure()方法中設置下即可:
mWidth = MeasureSpec.getSize(widthMeasureSpec);
mHeight = MeasureSpec.getSize(heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
switch (widthMode) {
case MeasureSpec.EXACTLY:
mWidth = mHeight = Math.min(mWidth, mHeight);
setMeasuredDimension(mWidth, mHeight);
break;
case MeasureSpec.AT_MOST:
float density = getResources().getDisplayMetrics().density;
mWidth = mHeight = (int) (50 * density); //默認50dp
setMeasuredDimension(mWidth, mHeight);
break;
}
然后畫出底部的圓形
canvas.drawCircle(mWidth / 2, mHeight / 2, mRadius, mPaint);
計算Path
1、初始化完畢后,怎么實現兩個豎條到一個三角形的過渡呢?這里首先想到的就是自定義 View 常用的 drawPath 方法,拋開動畫不談,整個 View 變化過程其實就是兩個矩形變成兩個直角三角形的過程。
就是這個樣子。知道大體的思路,怎么搞呢,當然是開車了。
就是 canvas.drawPath();
首先計算暫停時兩個矩形的各個坐標位置:
float distance = mGapWidth; //暫停時左右兩邊矩形距離
float barWidth = mRectWidth / 2 - distance / 2; //一個矩形的寬度
float leftLeftTop = barWidth; //左邊矩形左上角
float rightLeftTop = barWidth + distance; //右邊矩形左上角
float rightRightTop = 2 * barWidth + distance; //右邊矩形右上角
float rightRightBottom = rightRightTop; //右邊矩形右下角
bottom 的話直接加上矩形的高度即可。
mLeftPath.moveTo(0, 0);
mLeftPath.lineTo(leftLeftTop, mRectHeight);
mLeftPath.lineTo(barWidth, mRectHeight);
mLeftPath.lineTo(barWidth, 0);
mLeftPath.close();
mRightPath.moveTo(rightLeftTop, 0);
mRightPath.lineTo(rightLeftTop, mRectHeight);
mRightPath.lineTo(rightRightBottom, mRectHeight);
mRightPath.lineTo(rightRightTop, 0);
mRightPath.close();
這樣兩個豎條就出來了。
2、在一開始寫的時候就寫了這么多計算的方法,但是這時候矩形的邊角會超出 View 的范圍,所以后來計算了一波位置:
如上圖所示,這樣就需要再更改一些參數:
首先定義出來這個矩形,計算下寬高:
float space = (float) (mRadius / Math.sqrt(2));
mRectLT = (int) (mRadius - space);
int rectRB = (int) (mRadius + space);
mRect.top = mRectLT;
mRect.bottom = rectRB;
mRect.left = mRectLT;
mRect.right = rectRB;
然后只用在 確定 path 的路線時更改下坐標就可以了:
mLeftPath.moveTo(mRectLT, mRectLT);
mLeftPath.lineTo(leftLeftTop + mRectLT, mRectHeight + mRectLT);
mLeftPath.lineTo(barWidth + mRectLT, mRectHeight + mRectLT);
mLeftPath.lineTo(barWidth + mRectLT, mRectLT);
mLeftPath.close();
mRightPath.moveTo(rightLeftTop + mRectLT, mRectLT);
mRightPath.lineTo(rightLeftTop + mRectLT, mRectHeight + mRectLT);
mRightPath.lineTo(rightRightBottom + mRectLT, mRectHeight + mRectLT);
mRightPath.lineTo(rightRightTop + mRectLT, mRectLT);
mRightPath.close();
這時候畫出來兩個 Path,暫停按鈕就完美的呈現了:
canvas.drawPath(mLeftPath, mPaint);
canvas.drawPath(mRightPath, mPaint);
如下圖這樣:
動畫實現
畫完暫停按鈕后,怎么讓他動畫變成三角形呢?一開始我想根據一些寬高的屬性來指定動畫的變化值,然后更新過程中再畫出來,但是計算過程中發現涉及動畫的矩形寬度都是從原始的大小到0過渡的,那統一的使用一個參數確定會不會更好點呢?當然會了,從1倍到0變化即可。
這時候就可以設置動畫屬性了:
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0 , 1);
valueAnimator.setDuration(200);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mProgress = (float) animation.getAnimatedValue();
invalidate();
}
});
然后根據 progress 在更新View的過程中來更改矩形的寬高值:
float distance = mGapWidth * (1 - mProgress); //暫停時左右兩邊矩形距離
float barWidth = mRectWidth / 2 - distance / 2; //一個矩形的寬度
float leftLeftTop = barWidth * mProgress; //左邊矩形左上角
float rightLeftTop = barWidth + distance; //右邊矩形左上角
float rightRightTop = 2 * barWidth + distance; //右邊矩形右上角
float rightRightBottom = rightRightTop - barWidth * mProgress; //右邊矩形右下角
這樣便可以實現兩個矩形到三角形的過渡了,執行動畫結束后便是這個樣子:
兩個矩形變成三角形之后,只需要畫布旋轉一下,兩個暫停按鈕到播放按鈕的動畫已經可以執行了:
canvas.rotate(rotation, mWidth / 2f, mHeight / 2f);
到這里基本上已經結束了,但是寫完使用的時候總覺得位置有點不對勁,后來發現確實有問題:
如圖所示,旋轉過后 A 和 C 本來是緊靠著圓周的,而 B 距離圓周還有一定的距離。所以需要將其位移 x 的距離,讓 OC 的長度等于 BO 的長度。此時圓心O也是三角形的外心。那么此時可以計算出OF的距離,公式如下:
√(( r / √2 ) ^ 2 + OF ^ 2) = √2 * r - OF
得出 OF 的長度為: 3 * √2 * r / 8
那么原矩形寬度的一半減去 OF 的值即為右移的距離,計算可得,右移的距離為 √2 * r / 8 用 Java 表示即
radius * Math.sqrt(2) / 8f
換算為矩形的高度即
mRectHeight / 8f
然后在畫布位移一下即可:
canvas.translate((float) (mRectHeight / 8f * mProgress), 0);
總結
上邊幾個步驟寫完,整體效果已經實現了。后來又設置了一系列自定義的參數方便使用:
<declare-styleable name="PlayPauseView">
<attr name="bg_color" format="color"/>
<attr name="btn_color" format="color"/>
<attr name="gap_width" format="float"/>
<attr name="space_padding" format="float"/>
<attr name="anim_duration" format="integer"/>
<attr name="anim_direction">
<enum name="positive" value="1"/>
<enum name="negative" value="2"/>
</attr>
</declare-styleable>
所有代碼都已經上傳到 我的Github 上邊了,點擊可查看,希望提出問題相互討論,隨便給個 Star 再好不過了。
有問題交流可加QQ群 661614986 ,歡迎討論。