這次給大家?guī)淼氖且豢羁捎糜谝魳凡シ牌鞯膽腋〔藛伟粹o,它是基于 FloatingActionButton
上完成兆览,能夠聯(lián)動(dòng)音樂播放器顯示歌曲的進(jìn)度,設(shè)置歌曲的封面和通過封面的旋轉(zhuǎn)來展示播放的狀態(tài)(停止或者播放)。
除此之外,它可以設(shè)置一組按鈕作為菜單展示顽聂,支持上下左右四個(gè)方位顯示,更方便的是可以在代碼中動(dòng)態(tài)的添加按鈕或者移除按鈕盯仪。
制作這個(gè)自定義控件初衷是想要一個(gè)小型的音樂播放器能夠在應(yīng)用的首頁顯示紊搪,它能夠展示當(dāng)前音樂的播放狀態(tài),也能夠?qū)σ魳凡シ牌鬟M(jìn)行一些操作全景,比如播放暫停耀石,上下曲切換等。
具體的代碼和如何引用請(qǐng)移步到 Github 上的項(xiàng)目地址上:FloatingMusicMenu
如何實(shí)現(xiàn)
說完作品爸黄,我們來講講是怎么用代碼實(shí)現(xiàn)這些效果的吧滞伟。
利用反射使 FloatingActionButton 填充整個(gè)按鈕
在 FloatingActionButton
中是沒有開放的方法來調(diào)整圖片大小的揭鳞,所以這時(shí)候就想到了使用反射的方法來調(diào)整。
/**
* 利用反射重新定義fab圖片的大小梆奈,使其充滿整個(gè)fab
*/
public void setMaxImageSize() {
try {
Class clazz = getClass().getSuperclass();
Method sizeMethod = clazz.getDeclaredMethod("getSizeDimension");
sizeMethod.setAccessible(true);
int size = (Integer) sizeMethod.invoke(this);
Field field = clazz.getDeclaredField("mMaxImageSize");
field.setAccessible(true);
field.set(this, size);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
postInvalidate();
}
其中通過 getSizeDimension
可以獲得 FloatingActionButton
的實(shí)際大小野崇,而 mMaxImageSize
則是圖片的指定大小,所以可以重新賦值 mMaxImageSize
來調(diào)整圖片的大小亩钟。
具體代碼則是在 FloatingMusicButton
中乓梨。
一個(gè)可以旋轉(zhuǎn)的 Drawable 圖片
這里要實(shí)現(xiàn)的效果有點(diǎn)多,首先要將圖片的形狀變成圓形径荔,其次要在圖片的周圍留出一定的邊距以便顯示進(jìn)度條督禽,最后讓圖片轉(zhuǎn)起來。
圓形圖片有多種方式可以實(shí)現(xiàn)总处,這里我們通過 BitmapShader
來進(jìn)行變換狈惫。
/**
* 圓形
*/
private void circleBitmap() {
BitmapShader bitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setShader(bitmapShader);
}
BitmapShader 通過設(shè)置給mPaint,然后用這個(gè)mPaint繪圖時(shí)鹦马,就會(huì)根據(jù)你設(shè)置的TileMode胧谈,對(duì)繪制區(qū)域進(jìn)行著色。
mBitmapShader = new BitmapShader(bitmap, TileMode.CLAMP, TileMode.CLAMP);
繪制進(jìn)度條可以通過 canvas.drawArc()
來實(shí)現(xiàn)荸频,記得要先將之前的圖片縮小菱肖,防止進(jìn)度條覆蓋在圖片之上。
@Override
public void draw(Canvas canvas) {
float progressWidth = mWidth * progressPercent / 100f;
float halfWidth = progressWidth / 2;
// 畫背景圖
canvas.save();
canvas.rotate(mRotation, getBounds().centerX(), getBounds().centerY());
float scale = 1 - progressWidth * 2.0f / mWidth;
canvas.scale(scale, scale, mWidth / 2.0f, mWidth / 2.0f);
canvas.drawCircle(mWidth / 2, mWidth / 2, mWidth / 2, mPaint);
canvas.restore();
// 畫進(jìn)度條
rectF.set(halfWidth, halfWidth, mWidth - halfWidth, mWidth - halfWidth);
canvas.drawArc(rectF, -90, progress, false, progressPaint);
}
讓圖片旋轉(zhuǎn)則是很簡(jiǎn)單的旋轉(zhuǎn) canvas 畫布旭从,利用 Handler 不斷循環(huán)發(fā)送消息來更新 mRotation
保持旋轉(zhuǎn)的動(dòng)畫稳强。
canvas.rotate(mRotation, getBounds().centerX(), getBounds().centerY());
具體代碼在 RotatingProgressDrawable
中。
可展開和收縮的 ViewGroup
自定義 ViewGroup 首先要做的兩件事是測(cè)量控件的寬高和子控件的擺放和悦,由于控件可以四個(gè)方向自由擺放退疫,所以需要針對(duì)不同的方向進(jìn)行不同的計(jì)算和擺放。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
measureChildren(widthMeasureSpec, heightMeasureSpec);
switch (floatingDirection) {
case FLOATING_DIRECTION_UP:
case FLOATING_DIRECTION_DOWN:
onMeasureVerticalDirection();
break;
case FLOATING_DIRECTION_LEFT:
case FLOATING_DIRECTION_RIGHT:
onMeasureHorizontalDirection();
break;
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
switch (floatingDirection) {
case FLOATING_DIRECTION_UP:
onUpDirectionLayout(l, t, r, b);
break;
case FLOATING_DIRECTION_DOWN:
onDownDirectionLayout(l, t, r, b);
break;
case FLOATING_DIRECTION_LEFT:
onLeftDirectionLayout(l, t, r, b);
break;
case FLOATING_DIRECTION_RIGHT:
onRightDirectionLayout(l, t, r, b);
break;
}
}
控件的大小和位置確定好后就需要為每個(gè)子視圖添加動(dòng)畫鸽素,比較簡(jiǎn)單的做法是將動(dòng)畫相關(guān)的變量放入 LayoutParams 中褒繁,方便管理。
private class MenuLayoutParams extends LayoutParams {
private ObjectAnimator expandDirAnim = new ObjectAnimator();
private ObjectAnimator expandAlphaAnim = new ObjectAnimator();
private ObjectAnimator collapseDirAnim = new ObjectAnimator();
private ObjectAnimator collapseAlphaAnim = new ObjectAnimator();
...
}
同時(shí)要重寫 generateLayoutParams()
方法馍忽,使其在 addView()
時(shí)能夠?qū)?MenuLayoutParams
賦給每個(gè)子視圖棒坏。
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new MenuLayoutParams(super.generateDefaultLayoutParams());
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MenuLayoutParams(super.generateLayoutParams(attrs));
}
@Override
protected LayoutParams generateLayoutParams(LayoutParams p) {
return new MenuLayoutParams(super.generateLayoutParams(p));
}
這樣當(dāng)在 onLayout()
的方法期間就可以為每個(gè)子視圖確定動(dòng)畫的距離,方向等屬性遭笋。
具體的細(xì)節(jié)可以查看類 —— FloatingMusicMenu
總結(jié)
這個(gè)控件是我在寫 MoeMusic
項(xiàng)目時(shí)就已經(jīng)存在的坝冕,但只有一個(gè)向上展開的功能,沒有現(xiàn)在這么完整瓦呼。想到自己好久沒有寫開源項(xiàng)目了徽诲,就把這個(gè)控件拎出來完善了一下開源給大家,希望能夠喜歡吵血。如果使用過程中有任何問題或者建議可以直接聯(lián)系谎替!