<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Android實(shí)現(xiàn)炫酷的拖拽浮動(dòng)按鈕

          共 6365字,需瀏覽 13分鐘

           ·

          2021-10-16 02:51

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


          一、效果圖



          效果還是不錯(cuò)的。上圖看出雖然沒(méi)有像IOS一樣彈出菜單欄,僅僅以Toast和旋轉(zhuǎn)動(dòng)畫(huà)的效果代替了(因?yàn)樘珣辛耍趴岬男Ч唤o你們的想象了)。但是確實(shí)支持點(diǎn)擊事件,并且和拖拽事件不沖突。


          二、實(shí)現(xiàn)原理


          1、拖拽實(shí)現(xiàn)


          很簡(jiǎn)單,設(shè)置TouchListener監(jiān)聽(tīng),實(shí)現(xiàn)onTouch方法,在ACTION_MOVE的過(guò)程中隨著x,y坐標(biāo)的移動(dòng)更新浮動(dòng)按鈕的位置。

          下面具體介紹重寫(xiě)onTouch方法的具體實(shí)現(xiàn)

          監(jiān)聽(tīng)ACTION_DOWN事件
          case MotionEvent.ACTION_DOWN:{                mDownPointerId = MotionEventCompat.getPointerId(event, 0);                mPreviousX = event.getRawX();                mPreviousY = event.getRawY();                break;            }

          記錄初始的坐標(biāo)以及觸摸點(diǎn)。

          監(jiān)聽(tīng)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)方法,來(lái)更新浮動(dòng)按鈕的相對(duì)位置。
          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代表浮動(dòng)按鈕可移動(dòng)的矩形范圍。

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


          監(jiān)聽(tīng)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;                    //抬起時(shí) 根據(jù)位置強(qiáng)制把浮動(dòng)按鈕歸于左邊或右邊                    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直接移動(dòng) 否則開(kāi)啟動(dòng)畫(huà)                    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;            }        }

          實(shí)現(xiàn)當(dāng)抬起的瞬間,根據(jù)當(dāng)前所處坐標(biāo)靠左還是靠右,把浮動(dòng)按鈕置于左邊緣或者右邊緣。同時(shí),調(diào)用回調(diào),把移動(dòng)相對(duì)位置傳給回調(diào)函數(shù),實(shí)現(xiàn)拖拽監(jiān)聽(tīng)。

          當(dāng)從當(dāng)前位置移動(dòng)到左/右邊緣的距離小于100時(shí),直接移動(dòng),否則實(shí)現(xiàn)動(dòng)畫(huà)減速移動(dòng)效果。

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


          2、點(diǎn)擊實(shí)現(xiàn)


          也許有人會(huì)認(rèn)為點(diǎn)擊事件很好實(shí)現(xiàn)啊,setOnClickListener()設(shè)置個(gè)監(jiān)聽(tīng)就可以實(shí)現(xiàn)了。不信你去試試,沒(méi)用。

          其實(shí)點(diǎn)擊實(shí)現(xiàn)才是本篇文章的精髓,因?yàn)殪`活應(yīng)用到了事件分發(fā)機(jī)制。

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

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

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

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


          因?yàn)閛nTouch方法中返回false,確實(shí)onClickListenr接收到了事件,然后消費(fèi)掉。可是因?yàn)閛nTouch方法中返回false,所以接下來(lái)的一切事件不能接受,沒(méi)辦法響應(yīng)拖拽效果了。

          通過(guò)上面的分析,最終的解決辦法就是:onTouch方法中,在接收到ACTION_DOWN后,返回false,交給onClickListenr處理。剩下的ACTION_MOVE/ACTION_UP等事件,返回true,交給onTouchListener處理,這樣自然就可以既實(shí)現(xiàn)拖拽效果又實(shí)現(xiàn),點(diǎn)擊效果了。

          具體實(shí)現(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;    }

          原理就是這么簡(jiǎn)單,更加炫酷的效果可自定義實(shí)現(xiàn)

          源碼地址:
          https://github.com/LRH1993/FloatingDragButton

          到這里就結(jié)束啦。
          瀏覽 114
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  麻豆天天爱天天 | 美女黄片| 欧美色图亚洲色图视频 | 成人午夜无码 | 操B网址 操屄网址 |