幾個(gè)月前,我寫了一篇文章《Android 可拖拽懸浮吸附按鈕》這篇文章的實(shí)現(xiàn)方式有點(diǎn)影響性能,介于當(dāng)時(shí)的能力不足也是有一定原因的。這幾天重新實(shí)現(xiàn)了一種效果更好的方式,這種方式的優(yōu)點(diǎn)是,你可以就像使用普通的控件的一樣使用它(實(shí)際上它就是普通的控件)并且滿足按鈕點(diǎn)擊效果,代碼上也大大的比之前簡(jiǎn)化了。記得之前的方式 應(yīng)為事件被改寫了還得單獨(dú)寫一個(gè)接口來(lái)用來(lái)判斷點(diǎn)擊事件。
實(shí)現(xiàn)思路
- 通過(guò)重寫控件的
onTouchEvent方法監(jiān)聽(tīng)觸摸效果。 - 通過(guò)
View的setX()和setY()方法實(shí)現(xiàn)移動(dòng)。 - 使用屬性動(dòng)畫實(shí)現(xiàn)邊緣吸附效果。
源代碼沒(méi)多少行,這里先把代碼線上。此處我是繼承了FloatingActionButton,使它擁有了拖拽移動(dòng)的功能。
public class DragFloatActionButton extends FloatingActionButton{
private int parentHeight;
private int parentWidth;
private int slop;
public DragFloatActionButton(Context context) {
this(context,null);
}
public DragFloatActionButton(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public DragFloatActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
slop=ViewConfigurationCompat.getScaledPagingTouchSlop(ViewConfiguration.get(context));
}
private int lastX;
private int lastY;
private boolean isDrag;
@Override
public boolean onTouchEvent(MotionEvent event) {
int rawX = (int) event.getRawX();
int rawY = (int) event.getRawY();
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
setPressed(true);
isDrag=false;
getParent().requestDisallowInterceptTouchEvent(true);
lastX=rawX;
lastY=rawY;
ViewGroup parent;
if(getParent()!=null){
parent= (ViewGroup) getParent();
parentHeight=parent.getHeight();
parentWidth=parent.getWidth();
}
break;
case MotionEvent.ACTION_MOVE:
if(parentHeight<=0||parentWidth<=0){
//如果不存在父類的寬高則無(wú)法拖動(dòng),默認(rèn)直接返回false
isDrag=false;
break;
}
int dx=rawX-lastX;
int dy=rawY-lastY;
//這里修復(fù)一些華為手機(jī)無(wú)法觸發(fā)點(diǎn)擊事件
int distance= (int) Math.sqrt(dx*dx+dy*dy);
//此處稍微增加一些移動(dòng)的偏移量,防止手指抖動(dòng),誤判為移動(dòng)無(wú)法觸發(fā)點(diǎn)擊時(shí)間
if(distance==0||distance<=slop){
isDrag=false;
break;
}
//程序到達(dá)此處一定是正在拖動(dòng)了
isDrag=true;
float x=getX()+dx;
float y=getY()+dy;
//檢測(cè)是否到達(dá)邊緣 左上右下
x=x<0?0:x>parentWidth-getWidth()?parentWidth-getWidth():x;
y=getY()<0?0:getY()+getHeight()>parentHeight?parentHeight-getHeight():y;
setX(x);
setY(y);
lastX=rawX;
lastY=rawY;
break;
case MotionEvent.ACTION_UP:
if(isDrag()){
//恢復(fù)按壓效果
setPressed(false);
}
welt(rawX);
break;
}
//如果是拖拽則消耗事件,否則正常傳遞即可。
return isDrag() || super.onTouchEvent(event);
}
private boolean isDrag(){
return isDrag;
}
private boolean isLeftSide(){
return getX()==0;
}
private boolean isRightSide(){
return getX()==parentWidth-getWidth();
}
private void welt(int currentX){
if(!isLeftSide()||!isRightSide()){
if(currentX>=parentWidth/2){
//靠右吸附
animate().setInterpolator(new DecelerateInterpolator())
.setDuration(500)
.xBy(parentWidth-getWidth()-getX())
.start();
}else {
//靠左吸附
ObjectAnimator oa=ObjectAnimator.ofFloat(this,"x",getX(),0);
oa.setInterpolator(new DecelerateInterpolator());
oa.setDuration(500);
oa.start();
}
}
}
}
代碼很簡(jiǎn)單,
手指按下
首先是處理手指按壓下的事件,這里我們把拖拽標(biāo)識(shí)符設(shè)置為false并記錄當(dāng)前點(diǎn)擊的屏幕坐標(biāo)。然后我們?cè)谔幚硪苿?dòng)事件。
手指移動(dòng)
這里我們把拖拽標(biāo)識(shí)符設(shè)置為true,因?yàn)槭种敢苿?dòng)了。然后我們需要計(jì)算手指移動(dòng)了多少偏移量
//計(jì)算手指移動(dòng)了多少
int dx=rawX-lastX;
int dy=rawY-lastY;
而后的兩行代碼表示控件需要移動(dòng)的具體距離,后面有一個(gè)簡(jiǎn)單的邊緣檢測(cè)計(jì)算。最終通過(guò)調(diào)用setX以及setY方法實(shí)現(xiàn)控件的移動(dòng)。
手指松開(kāi)
這里如果是拖拽動(dòng)作我們才需要處理自己的邏輯否則直接跳過(guò)即可。在這里我們首先恢復(fù)了按鈕的按壓效果,在源代碼中找到setPressed(boolean)方法,這是處理按鈕點(diǎn)擊效果用的,在這里當(dāng)手指松開(kāi)后我們需要恢復(fù)按鈕原來(lái)的效果。然后在判斷控件需要往哪邊吸附,吸附的過(guò)程就是做屬性動(dòng)畫而已,原理還是不斷的改變setX方法讓按鈕靠邊移動(dòng)
總結(jié)
這種實(shí)現(xiàn)方式,我們能正常的使用控件的單擊時(shí)間和長(zhǎng)按事件,因?yàn)橹挥挟?dāng)控件拖拽的時(shí)候我們才自己消耗事件否則全部交給系統(tǒng)處理。這是一種比較好的實(shí)現(xiàn)方式,通過(guò)這個(gè)例子其實(shí)我們還能實(shí)現(xiàn)更多的控件移動(dòng)效果。事實(shí)上只需要改變所繼承的控件類型就可以了
PS1:
最近發(fā)現(xiàn)在部分華為手機(jī)上無(wú)法觸發(fā)點(diǎn)擊事件,調(diào)試發(fā)現(xiàn)當(dāng)我手指按壓的時(shí)候會(huì)一直觸發(fā)MotionEvent.ACTION_MOVE事件而事實(shí)上我手指一點(diǎn)都沒(méi)有動(dòng),且Log出現(xiàn)的數(shù)據(jù)顯示移動(dòng)距離一直是0.坑爹。只能加一個(gè)距離判斷了。上面的代碼已經(jīng)修復(fù)了這個(gè)問(wèn)題。
PS2:
修復(fù)拖拽到中心點(diǎn)的時(shí)候長(zhǎng)按不會(huì)吸附到邊緣的問(wèn)題,
優(yōu)化實(shí)現(xiàn)定義,準(zhǔn)確的說(shuō)是讓view在父控件中任意拖拽。
歡迎共同探討更多安卓,java,c/c++相關(guān)技術(shù)QQ群:392154157