記得以前有一個(gè)需求
做一個(gè)播放界面,左邊的是需要這種效果可以隨著拖動(dòng)調(diào)節(jié)音量,然后點(diǎn)擊速圖標(biāo)還可以收縮伸展開這個(gè)音量欄,尼瑪當(dāng)初的我做的那個(gè)累啊,整整弄了一個(gè)星期,大概寫了3,4個(gè)組合控件有3000多行代碼以及用了各種切圖,勉強(qiáng)弄好后還有點(diǎn)Bug,還依稀記得我們技術(shù)總監(jiān)最后看我的眼神~~~,當(dāng)然現(xiàn)在翻過頭來看這種需求也不是那么特別難了,前段時(shí)間正好沒事把它擼了出來。
我們看下怎么實(shí)現(xiàn)的。
public class VoiceView extends View
首先定義VoiceView
private void initView() {
//音量大小默認(rèn)為1
mSpeedLength = 5;
//矩形弧度minimum
mXRound = 30;
mYRound = 30;
//每個(gè)item需要的寬度
mVoiceItemWidth = 10;
mVoiceItemHeight = 20;
mMinimumVoiceHeight = mVoiceItemHeight+mVoiceItemHeight*1/3;
mPointBitmap = Bitmap.createBitmap(BitmapFactory.decodeResource(getResources(), R.mipmap.point));
//初始化音量控件初始狂寬高大小
mMinWidth=100;
mMinHeight=30;
mPointWidth = mVoiceItemWidth*2;
mCurrVolum = 4;
//右側(cè)圖片寬高
}
我在創(chuàng)建控件的時(shí)候定義一個(gè)init方法進(jìn)行一些初始化,這里有初始速度,矩形弧度,每個(gè)音量塊的寬度以及每個(gè)音量塊的高度,音量指示器圖片等,這里我把指示器的寬度設(shè)置為音量塊寬度的二倍。
好我們主要看一下draw方法里面怎么實(shí)現(xiàn)的。主要的邏輯都在這里
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int count=0;
mMeasuredWidth = getMeasuredWidth();
mMeasuredHeight = getMeasuredHeight();
Paint backgroundPaint = new Paint();
backgroundPaint.setColor(Color.parseColor("#a4c2f4"));
canvas.drawRoundRect(0, 0, mMeasuredWidth, mMeasuredHeight, mXRound, mYRound, backgroundPaint);
//音量欄所需要的上下左右寬距離
if(mRightImageBitmap!=null){
mRightBitmapWidthAndHeight=mMeasuredHeight;
//mVoiceItemMarginLeft = (mMeasuredWidth - mMeasuredHeight - (mVoiceItemWidth * mSpeedLength))/(mSpeedLength+1);
RectF rectF = new RectF();
rectF.set(mMeasuredWidth-mRightBitmapWidthAndHeight,0,mMeasuredWidth,mRightBitmapWidthAndHeight);
canvas.drawBitmap(mRightImageBitmap,null,rectF,null);
Log.e("wwww","isnull");
}else{
mRightBitmapWidthAndHeight=0;
//mVoiceItemMarginLeft=(mMeasuredWidth - (mVoiceItemWidth * mSpeedLength))/(mSpeedLength+1);
Log.e("wwww","isnullNO");
}
mVoiceItemMarginLeft = (mMeasuredWidth - mRightBitmapWidthAndHeight - (mVoiceItemWidth * mSpeedLength))/(mSpeedLength+1);
int voiceLeft;
int voiceRight;
backgroundPaint.setColor(Color.WHITE);
//設(shè)置初始的7個(gè)音量調(diào)節(jié)位置和大小 上下都是不變的變得是左右位置
for (int i = 0; i < mCurrVolum; i++) {
voiceLeft = (i + 1) * mVoiceItemMarginLeft + i * mVoiceItemWidth;
voiceRight = (i + 1) * mVoiceItemMarginLeft + (i +1)* mVoiceItemWidth ;
canvas.drawRect(voiceLeft, mVoiceItemHeight, voiceRight, mMeasuredHeight - mVoiceItemHeight, backgroundPaint);
//繪制point
if(i==(mCurrVolum-1)){
int pointLeft = voiceLeft - (mPointWidth-mVoiceItemWidth) / 2;
RectF rectF = new RectF();
rectF.set(pointLeft,0,pointLeft+mPointWidth,mPointWidth);
canvas.drawBitmap(mPointBitmap,null,rectF,null);
Log.e("TAG","mCurrVolum:"+mCurrVolum);
}
}
backgroundPaint.setColor(Color.parseColor("#a4c2f4"));
for (int i = mCurrVolum; i < mSpeedLength; i++) {
//定義每個(gè)Item音量的寬度
voiceLeft = (i + 1) * mVoiceItemMarginLeft + i * mVoiceItemWidth;
voiceRight = (i + 1) * mVoiceItemMarginLeft + i * mVoiceItemWidth + mVoiceItemWidth;
canvas.drawRect(voiceLeft, mVoiceItemHeight, voiceRight, mMeasuredHeight - mVoiceItemHeight, backgroundPaint);
}
}
由于這個(gè)控件實(shí)現(xiàn)的比較單一所以實(shí)現(xiàn)細(xì)節(jié)直接寫里面了沒有抽出來,
首先繪制矩形,然后我們判斷右邊的bitmap是否為null,因?yàn)檫@是提供的一個(gè)開放方法這個(gè)圖片是由我們設(shè)置進(jìn)去的如果是null,那么mRightBitmapWidthAndHeight設(shè)置為0,否則我們把它的寬和高都設(shè)置為整個(gè)控件的高。
ok右邊的圖片繪制完后,正式開始擼我們的音量啦~一開始我默認(rèn)設(shè)置音量len=7我們分別繪制7個(gè)音塊
for (int i = 0; i < mCurrVolum; i++) {
voiceLeft = (i + 1) * mVoiceItemMarginLeft + i * mVoiceItemWidth;
voiceRight = (i + 1) * mVoiceItemMarginLeft + (i +1)* mVoiceItemWidth ;
canvas.drawRect(voiceLeft, mVoiceItemHeight, voiceRight, mMeasuredHeight - mVoiceItemHeight, backgroundPaint);
}
每個(gè)音塊的左邊距離等我們設(shè)置的marginleft+音塊的寬度
然后開始繪制指示器point
//繪制point
if(i==(mCurrVolum-1)){
int pointLeft = voiceLeft - (mPointWidth-mVoiceItemWidth) / 2;
RectF rectF = new RectF();
rectF.set(pointLeft,0,pointLeft+mPointWidth,mPointWidth);
canvas.drawBitmap(mPointBitmap,null,rectF,null);
Log.e("TAG","mCurrVolum:"+mCurrVolum);
}
這里我們把指示器默認(rèn)繪制在第四檔的位置,這里需要注意下,由于指示器的寬度是音塊寬度的二倍所以指示器的
左側(cè)距離=第四個(gè)音塊的左側(cè)距離-(指示器寬度-音塊寬度)/2
好了這里我們指示器和音塊全部繪制完畢了
if(i==(mCurrVolum-1))
這里為啥要去-1呢你想啊我們繪制音塊的時(shí)候是從0開始的,假如
mCurrVolum值是4當(dāng)然要在index=3處繪制這個(gè)時(shí)候音塊長度為4
但是這個(gè)控件不能是靜態(tài)的啊,我們要滑動(dòng)控制它啊,我們重寫一下
onTouchEvent方法
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
return true;
case ACTION_MOVE:
getScrollLength(event);
invalidate();
return true;
case MotionEvent.ACTION_UP:
getScrollLength(event);
invalidate();
return true;
}
return super.onTouchEvent(event);
}
可以看到代碼并不復(fù)雜,在滑動(dòng)和停止滑動(dòng)時(shí)候都調(diào)用了getscrolllenth()這個(gè)方法是個(gè)什么鬼我們貼上代碼看看
private void getScrollLength(MotionEvent event) {
int eventX= (int) (event.getX()+0.5f);
/*
if(eventX>0&&eventY>0&&eventX<mMeasuredWidth-mMeasuredHeight&&eventY<mMeasuredHeight){*/
//方案1:
int marginLeftAndVoiceWidth= (mMeasuredWidth - mRightBitmapWidthAndHeight - mVoiceItemMarginLeft) / mSpeedLength;
//方案2:
//int marginLeftAndVoiceWidth= mVoiceItemMarginLeft;
mCurrVolum=eventX/marginLeftAndVoiceWidth;
if(mCurrVolum>mSpeedLength){
mCurrVolum=mSpeedLength;
}
Log.e("TAG","volum:"+mCurrVolum);
if(mOnSpeedClickListener!=null){
mOnSpeedClickListener.onSpeedChange(mCurrVolum);
}
}
這里采用的方案1是當(dāng)我們觸摸到每個(gè)音塊最右邊的時(shí)候,這個(gè)音塊顯示或者消失,而方案2是觸摸到音塊左邊的時(shí)候顯示消失,這個(gè)根據(jù)個(gè)人習(xí)慣選擇了。
我們用控件寬度-最右側(cè)圖標(biāo)的寬度-音塊的左邊距/音量長度就可以得到每個(gè)音塊+左邊距長度
我們根據(jù)這個(gè)值和我們手指滑動(dòng)的距離跟新我們的音量大小,拿我們滑動(dòng)的當(dāng)前位置eventX/剛才計(jì)算的邊距 =當(dāng)前音量位置然后把結(jié)果賦值給我們mCurrVolum重新繪制一下,音量就更新好啦。
最終我們實(shí)現(xiàn)的效果如下
3000多行代碼擼出來的效果現(xiàn)在200行左右就全部搞定省時(shí)省力,哈
這個(gè)控件雖然不是很復(fù)雜但是還是有應(yīng)用場景的的,貼上git地址:
老鐵們隨手給個(gè)Star支持下我這小小的進(jìn)步哈 ~
https://github.com/boboyuwu/voice-view