現(xiàn)在的App絕大多數(shù)都帶有底部導(dǎo)航欄择膝,Google Material Design也給出了Bottom navigation的設(shè)計(jì)規(guī)范。點(diǎn)擊查看
為了提高開發(fā)效率油湖,我寫了一個(gè)底部開源控件晴股,本文主要講關(guān)于向下兼容點(diǎn)擊水紋效果和類似QQ粘性拖拽效果的實(shí)現(xiàn)方法。
首先看效果圖:
關(guān)于點(diǎn)擊水紋效果其實(shí)實(shí)現(xiàn)方法很簡(jiǎn)單肺魁。就是畫半透明圓,增大半徑隔节,不停的重繪直到圓的半徑達(dá)到你設(shè)定的最大值鹅经。可以在父容器中捕捉點(diǎn)擊的位置然后在viewgroup中繪制圓環(huán)實(shí)現(xiàn)水紋效果怎诫,這樣做的好處是每一個(gè)view在外面包一層的viewgroup則即可以實(shí)現(xiàn)瘾晃,不用每個(gè)view都去實(shí)現(xiàn)重繪方法。
核心方法:
/**
*rippleRate:速率
*rippleTime:時(shí)間
*rippleDistance:距離=時(shí)間*速度幻妓,本方法通過(guò)rippleTime++蹦误,來(lái)擴(kuò)大圓的半徑
/**
//點(diǎn)擊才開始繪制圓
if (isRippleDrawing) {
//已經(jīng)繪制最大圓環(huán) 停止繼續(xù)繪制
if (rippleDistance<= rippleTime * rippleRate) {
isRippleDrawing = false;
rippleTime = 0;
//刷新視圖
refreshView();
}
else {
mHandler.postDelayed(runnable, 10);
}
//從點(diǎn)擊的位置繪制圓
canvas.drawCircle(downX, downY, radiusMax * (((float) rippleTime *rippleRate) / rippleDistance), ripplePaint);
rippleTime++;
}
private final Runnable runnable = new Runnable() {
@Override
public void run() {
refreshView();
}};
private void refreshView(){
if(Looper.getMainLooper()==Looper.myLooper()) invalidate();
else postInvalidate();
}
關(guān)于小紅點(diǎn)的點(diǎn)擊拖拽效果就是繪制兩個(gè)圓加一個(gè)貝塞爾曲線連接,復(fù)雜的是實(shí)現(xiàn)全屏的拖拽肉津,因?yàn)橐坏┏隽说撞縯ab控件就無(wú)法繪制小紅點(diǎn)了强胰。這里先分析如何畫出效果再分析如何實(shí)現(xiàn)全屏拖動(dòng)。
畫貝塞爾曲線
如圖妹沙,其實(shí)關(guān)鍵點(diǎn)就是在于獲得C1 C2這兩個(gè)點(diǎn)的坐標(biāo)偶洋,然后可以通過(guò)C1 C2兩個(gè)點(diǎn)求得M N E F C的坐標(biāo),然后通過(guò)quadTo()
(畫貝塞爾曲線)距糖,lineTo()
將幾個(gè)點(diǎn)連接起來(lái)最后通過(guò)drawPath()
得到預(yù)期圖形玄窝。
而C1不動(dòng)圓的圓心顯然可以知道,C2拖拽圓的圓心即是你點(diǎn)擊的downX,downY悍引。
核心代碼
private void drawBPath(Canvas canvas) {
Path path=new Path();
path.moveTo(stillPoint[0].x,stillPoint[0].y);
// middleBase.x,middleBase.y,是圖中C點(diǎn)的橫縱坐標(biāo)恩脂,作為貝塞爾曲線的基準(zhǔn)點(diǎn)。
path.quadTo(middleBase.x,middleBase.y,dragPoint[0].x,dragPoint[0].y);
path.lineTo(dragPoint[1].x,dragPoint[1].y);
path.quadTo(middleBase.x,middleBase.y,stillPoint[1].x,stillPoint[1].y);
path.close();
canvas.drawPath(path,mDragPaint);
}
關(guān)于全屏拖動(dòng)
這里我的策略是先繪制一個(gè)DotView(帶數(shù)字的小紅點(diǎn))趣斤,當(dāng)DotView創(chuàng)建的時(shí)候創(chuàng)建一個(gè)DragView(拖拽效果),同時(shí)將這個(gè)DragView通過(guò)activity.addContentView()
作為activity的子view并設(shè)置全屏以及設(shè)置背景顏色為全透明俩块。通過(guò)在DragView中設(shè)置DotView的onTouch()
監(jiān)聽事件,在move過(guò)程中不斷更新繪制兩個(gè)圓和貝塞爾曲線。在小紅點(diǎn)被消費(fèi)后隱藏并銷毀DragView典阵,這樣就能實(shí)現(xiàn)小紅點(diǎn)的全屏拖動(dòng)奋渔。
dotView.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction())
{ //點(diǎn)擊時(shí)候畫帶消息數(shù)的圓圈,隱藏DotView
case MotionEvent.ACTION_DOWN:
//獲取DotView的相關(guān)信息
getDotViewInfo(((DotView) v));
updataPointF(stillCirclePoint,((DotView) v).getCircleCenterOnRaw()[0]+radius,((DotView) v).getCircleCenterOnRaw()[1]+radius);
isCanDraw=true;
v.setVisibility(GONE);
isDrawText=true;
refreshView();
break;
//移動(dòng)時(shí)候畫不帶消息靜止圓壮啊,以及貝塞爾曲線和拖拽圓
case MotionEvent.ACTION_MOVE:
isDrawText=false;
isShowDragCircle=true;
dragCirclePoint.set(event.getRawX(),event.getRawY());
//拖動(dòng)時(shí)候更新靜止圓的邊緣數(shù)組坐標(biāo)
slope = ((event.getRawY() - stillCirclePoint.y)/(event.getRawX() -stillCirclePoint.x));
calculatePath();
refreshView();
break;
case MotionEvent.ACTION_UP:
if(isDisconnet)
{
isCanDraw=false;
isShowDragCircle=false;
refreshView();
v.setVisibility(GONE);
//顯示爆炸效果
showBoomAnim();
}
//顯示回彈效果
else showReBoundAnim(v);
break;
}
return true;
}});
全部代碼在https://github.com/WakeHao/EasyBottomTab中的DotView以及DragView中嫉鲸,注釋都有寫。
最后我想縮的是歹啼,其實(shí)我寫的這個(gè)控件真的挺好用的,具體的使用方法點(diǎn)擊玄渗。不用你去寫那些fragment的add()
hide()
切換邏輯,不用去自己按照MD設(shè)計(jì)規(guī)范寫布局,也不要自己去實(shí)現(xiàn)一些水紋切換拖拽效果狸眼。幾行代碼就能幫你實(shí)現(xiàn)藤树,歡迎大家star and fork。