安卓可拖拽懸浮按鈕二

幾個(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)思路

  1. 通過(guò)重寫控件的onTouchEvent方法監(jiān)聽(tīng)觸摸效果。
  2. 通過(guò)ViewsetX()setY()方法實(shí)現(xiàn)移動(dòng)。
  3. 使用屬性動(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,326評(píng)論 25 708
  • 林小刀159閱讀 302評(píng)論 0 3
  • 今天有去鏈接到,我覺(jué)得自我管理做的很好的朋友,是通過(guò)重生星球 今晚上去上零起點(diǎn)課程,感覺(jué)好快樂(lè),自己大聲講了英語(yǔ),...
    思思培閱讀 202評(píng)論 0 0
  • 在火車上完成了新概念的作業(yè),不在乎別人的眼光,做自己,感覺(jué)棒棒的。思維導(dǎo)圖如果深入鉆研下去,我覺(jué)得肯定會(huì)對(duì)英語(yǔ)學(xué)習(xí)...
    小花貓和多多閱讀 297評(píng)論 5 1

友情鏈接更多精彩內(nèi)容