FloatingDragButton:炫酷的拖拽浮動按鈕

IOS的Assistive Touch效果很炫酷呵燕,可以任意拖拽,同時點擊后會展開菜單欄。然而,這不只是IOS的特權(quán)麸俘,Android也可以實現(xiàn)。但是由于懸浮窗需要申請權(quán)限,所以本文僅在app內(nèi)實現(xiàn),可以任意拖拽,并可以響應點擊事件。

一、效果圖


效果還是不錯的貌笨。上圖看出雖然沒有像IOS一樣彈出菜單欄剥哑,僅僅以Toast和旋轉(zhuǎn)動畫的效果代替了(因為太懶了困介,更炫酷的效果交給你們的想象了)。但是確實支持點擊事件屿良,并且和拖拽事件不沖突啥么。

Github地址:FloatingDragButton

二核芽、實現(xiàn)原理

1驰坊、拖拽實現(xiàn)

很簡單,設(shè)置TouchListener監(jiān)聽,實現(xiàn)onTouch方法,在ACTION_MOVE的過程中隨著x,y坐標的移動更新浮動按鈕的位置诚亚。
下面具體介紹重寫onTouch方法的具體實現(xiàn)。

監(jiān)聽ACTION_DOWN事件

case MotionEvent.ACTION_DOWN:{
                mDownPointerId = MotionEventCompat.getPointerId(event, 0);
                mPreviousX = event.getRawX();
                mPreviousY = event.getRawY();
                break;
            }

記錄初始的坐標以及觸摸點益愈。

監(jiān)聽ACTION_MOVE事件

case MotionEvent.ACTION_MOVE:{
                if (mDownPointerId >= 0) {
                    int index = MotionEventCompat.getActionIndex(event);
                    int id = MotionEventCompat.getPointerId(event, index);
                    if (id == mDownPointerId) {
                        boolean update = adjustMarginParams(view, event);
                        if (!update) {
                            break;
                        }
                        mFloatView.requestLayout();
                        mHasMoved = true;
                        result = true;
                    }
                }
                break;
            }

其中最重要的是adjustMarginParams(view, event)方法义屏,來更新浮動按鈕的相對位置兄墅。

private boolean adjustMarginParams(View v, MotionEvent event) {
        float x =  event.getRawX();
        float y =  event.getRawY();
        float deltaX = x - mPreviousX;
        float deltaY = y - mPreviousY;
        if (!mHasMoved) {
            if (Math.abs(deltaX) < mTouchSlop && Math.abs(deltaY) < mTouchSlop) {
                return false;
            }
        }
        //左上角位置
        int newX = (int)x - mFloatView.getWidth() / 2;
        int newY = (int)y - mFloatView.getHeight() / 2;
        newX = Math.max(newX, mBoundsInScreen.left + mEdgePaddingLeft);
        newX = Math.min(newX, mBoundsInScreen.right - mEdgePaddingRight - mFloatView.getWidth());
        newY = Math.max(newY, mBoundsInScreen.top + mEdgePaddingTop);
        newY = Math.min(newY, mBoundsInScreen.bottom - mEdgePaddingBottom - mFloatView.getHeight());
        mFloatViewWindowParam.x = newX;
        mFloatViewWindowParam.y = newY - mParentMarginTop;
        return true;
    }

其中mBoundsInScreen代表浮動按鈕可移動的矩形范圍瓶殃。

根據(jù)當前event內(nèi)的坐標與mBoundsInScreen范圍比較咖楣,選擇最終拖拽到達的位置娃肿,設(shè)置給浮動按鈕的布局參數(shù)mFloatViewWindowParam,然后調(diào)用requestLayout更新布局珠十。

監(jiān)聽ACTION_UP/ACTION_CANCEL

case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:{
                if (mDownPointerId >= 0 && mHasMoved) {
                    event.setAction(MotionEvent.ACTION_CANCEL);
                    adjustMarginParams(view, event);
                    mFloatView.requestLayout();
                    int center = (mBoundsInScreen.width() - mFloatView.getWidth()) / 2;
                    int x = mFloatViewWindowParam.x;
                    int destX = 0;
                    int posX = Gravity.LEFT;
                    //抬起時 根據(jù)位置強制把浮動按鈕歸于左邊或右邊
                    if (x < center) { // 左邊
                        destX = mBoundsInScreen.left + mEdgePaddingLeft;
                    } else {
                        posX = Gravity.RIGHT;
                        destX = mBoundsInScreen.right - mEdgePaddingRight - mFloatView.getWidth();
                    }
                    if (mFloatButtonCallback != null) {
                        float posY = 0;
                        if (mBoundsInScreen.height() - mFloatView.getHeight() != 0) {
                            posY = 1f * (mFloatViewWindowParam.y - mBoundsInScreen.top) / (mBoundsInScreen.height() - mFloatView.getHeight());
                        }
                        mFloatButtonCallback.onPositionChanged(destX, mFloatViewWindowParam.y, posX, posY);
                    }
                    int deltaHorizon = destX - x;
                    //小于100直接移動 否則開啟動畫
                    if (Math.abs(deltaHorizon) < 100) {
                        mFloatViewWindowParam.x = destX;
                        mFloatView.requestLayout();
                    } else {
                        ValueAnimator animator = ValueAnimator.ofInt(x, destX);
                        animator.setInterpolator(mInterpolator);
                        if (mUpdateListener == null) {
                            mUpdateListener = new FloatAnimatorUpdateListener();
                            mUpdateListener.setUpdateView(FloatTouchListener.this);
                        }
                        animator.addUpdateListener(mUpdateListener);
                        animator.setDuration(200);
                        animator.start();
                    }
                }
                resetStatus();
                break;
            }
        }

實現(xiàn)當抬起的瞬間料扰,根據(jù)當前所處坐標靠左還是靠右,把浮動按鈕置于左邊緣或者右邊緣焙蹭。同時晒杈,調(diào)用回調(diào),把移動相對位置傳給回調(diào)函數(shù)孔厉,實現(xiàn)拖拽監(jiān)聽拯钻。

當從當前位置移動到左/右邊緣的距離小于100時,直接移動烟馅,否則實現(xiàn)動畫減速移動效果说庭。

如此簡單便可實現(xiàn)任意拖拽的效果了,具體一些細節(jié)要細看源碼實現(xiàn)郑趁。

2.點擊實現(xiàn)

也許有人會認為點擊事件很好實現(xiàn)啊刊驴,setOnClickListener()設(shè)置個監(jiān)聽就可以實現(xiàn)了。不信你去試試,沒用捆憎。



其實點擊實現(xiàn)才是本篇文章的精髓舅柜,因為靈活應用到了事件分發(fā)機制。

從事件分發(fā)機制中我們知道躲惰,就優(yōu)先級而言:onTouchListener>onClickListenr致份。

上面的拖拽事件已經(jīng)消費了onTouchListener(即onTouch方法中返回true),那么就不會下發(fā)到onClickListenr础拨,自然就不會產(chǎn)生點擊事件氮块。

也許你想讓onTouchListener不消費,然后不就下發(fā)到onClickListenr了么诡宗?

確實這樣可以實現(xiàn)點擊事件滔蝉,但是拖拽功能又實現(xiàn)不了了。



因為onTouch方法中返回false,確實onClickListenr接收到了事件塔沃,然后消費掉蝠引。可是因為onTouch方法中返回false蛀柴,所以接下來的一切事件不能接受螃概,沒辦法響應拖拽效果了。

通過上面的分析鸽疾,最終的解決辦法就是:onTouch方法中吊洼,在接收到ACTION_DOWN后,返回false制肮,交給onClickListenr處理融蹂。剩下的ACTION_MOVE/ACTION_UP等事件,返回true弄企,交給onTouchListener處理,這樣自然就可以既實現(xiàn)拖拽效果又實現(xiàn)點擊效果了区拳。

具體實現(xiàn)以下面?zhèn)未a為例

  public boolean onTouch(View view, MotionEvent event) {
        int action = MotionEventCompat.getActionMasked(event);
        if (mFloatButtonCallback != null) {
            mFloatButtonCallback.onTouch();
        }
        boolean result = false;
        switch (action) {
            case MotionEvent.ACTION_DOWN:{
               ....................................................
                break;
            }
            case MotionEvent.ACTION_MOVE:{
               ....................................................
                result = true;
                break;
            }
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:{
                ................................................
                break;
            }
        }
        return result;
    }

原理就是這么簡單拘领,更加炫酷的效果可自定義實現(xiàn),喜歡就點個star支持下吧樱调!

Github地址:https://github.com/LRH1993/FloatingDragButton

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末约素,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子笆凌,更是在濱河造成了極大的恐慌圣猎,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件乞而,死亡現(xiàn)場離奇詭異送悔,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門欠啤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來荚藻,“玉大人,你說我怎么就攤上這事洁段∮τ” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵祠丝,是天一觀的道長疾呻。 經(jīng)常有香客問我,道長写半,這世上最難降的妖魔是什么岸蜗? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮污朽,結(jié)果婚禮上散吵,老公的妹妹穿的比我還像新娘。我一直安慰自己蟆肆,他們只是感情好矾睦,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著炎功,像睡著了一般枚冗。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蛇损,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天赁温,我揣著相機與錄音,去河邊找鬼淤齐。 笑死股囊,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的更啄。 我是一名探鬼主播稚疹,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼祭务!你這毒婦竟也來了内狗?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤义锥,失蹤者是張志新(化名)和其女友劉穎柳沙,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拌倍,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡赂鲤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年噪径,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蛤袒。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡熄云,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出妙真,到底是詐尸還是另有隱情缴允,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布珍德,位于F島的核電站练般,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏锈候。R本人自食惡果不足惜薄料,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望泵琳。 院中可真熱鬧摄职,春花似錦、人聲如沸获列。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽击孩。三九已至迫悠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間巩梢,已是汗流浹背创泄。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留括蝠,地道東北人鞠抑。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像忌警,于是被迫代替她去往敵國和親碍拆。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容