輕松自制flyme懸浮球

前言

去年用了一整年的MX4Pro,魅族留給我最大的印象就是懸浮球了(質(zhì)量問題我就不說了),左右滑動(dòng)切換應(yīng)用、上拉返回桌面、下拉打開通知欄、輕觸返回...,一切都那么絲滑。然而自從上半年換成了s7dege,我感覺怎么也習(xí)慣不了沒有懸浮球的生活了。

三星自己也有一個(gè)類似于懸浮球的功能,不過太過復(fù)雜,不易用,懸浮球本來就該是一個(gè)一步操作的產(chǎn)品,看來三星在軟件設(shè)計(jì)方面還是任重而道遠(yuǎn)。于是乎我便在各大應(yīng)用市場上找懸浮球,把所有排名靠前的懸浮球應(yīng)用都安裝試了一下,最后終于讓我找到了一款幾乎和flyme懸浮球相仿的app。

這款app在我手機(jī)里呆了好幾個(gè)月,是我手機(jī)里除了微信之外,唯一允許自啟動(dòng)的應(yīng)用了。很感謝這款app的開發(fā)者,不僅沒有任何廣告,還非常好用,完美移植了flyme自帶的懸浮球功能。

然而漸漸的,我便感覺到了一絲不舒服,那就是我每次安裝了一個(gè)新app,打開后提示要賦予權(quán)限(存儲(chǔ)、拍照)的時(shí)候,6.0的系統(tǒng)總會(huì)溫馨的彈出一個(gè)框:

然后我就必須到設(shè)置頁面,花半天找到懸浮球,關(guān)掉它的“可出現(xiàn)在頂部的應(yīng)用程”權(quán)限,然后才能回到app,授予權(quán)限。最后,我還得再次跑到設(shè)置頁面,再花半天找到懸浮球,打開它的“可出現(xiàn)在頂部的應(yīng)用程”權(quán)限。朋友啊朋友,這種體驗(yàn),一次就夠了,然而硬是讓我體驗(yàn)了N次啊!

然而有什么能難得倒程序員的呢?剛好這個(gè)周末在家無事,我決定按照自己的習(xí)慣,打造一個(gè)心目中最易用的懸浮球。

設(shè)計(jì)

1.UI

UI很簡單,直接用sketch切了三個(gè)圓,一個(gè)是作為背景的灰色半透明的圓,一個(gè)是中心的小圓,另外還有一個(gè)默認(rèn)隱藏的大圓。

2.功能

因?yàn)樽约旱牟僮髁?xí)慣是固定的,所以也就不需要給懸浮球添加自定義操作的功能了,直接將操作對(duì)應(yīng)的功能寫死即可。

(1)單擊:返回

(2)長按:移動(dòng)懸浮球

(3)左滑右滑:打開最近應(yīng)用程序

(4)上拉:返回桌面

(5)下拉:
這塊我最先開始定義的很簡單,就是下拉通知欄,但是經(jīng)過一天的使用,我又給它加了一個(gè)功能,就是保持下拉狀態(tài)1.5秒,將移除懸浮球。這樣你便可以很簡單的移除掉懸浮球了。

實(shí)現(xiàn)

1.如何添加懸浮球到桌面

這里首先要感謝郭霖大神的 《 Android桌面懸浮窗效果實(shí)現(xiàn),仿360手機(jī)衛(wèi)士懸浮窗效果》,這部分我參考了這篇文章,成功的將懸浮球添加到了桌面。

public static void addBallView(Context context) {
    if (mBallView == null) {
        WindowManager windowManager = getWindowManager(context);
        int screenWidth = windowManager.getDefaultDisplay().getWidth();
        int screenHeight = windowManager.getDefaultDisplay().getHeight();
        mBallView = new FloatBallView(context);
        LayoutParams params = new LayoutParams();
        params.x = screenWidth;
        params.y = screenHeight / 2;
        params.width = WindowManager.LayoutParams.WRAP_CONTENT;
        params.height = WindowManager.LayoutParams.WRAP_CONTENT;
        params.gravity = Gravity.LEFT | Gravity.TOP;
        params.type = LayoutParams.TYPE_PHONE;
        params.format = PixelFormat.RGBA_8888;
        params.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL
                | LayoutParams.FLAG_NOT_FOCUSABLE;
        mBallView.setLayoutParams(params);
        windowManager.addView(mBallView, params);
    }
}

2.手勢判斷

這是最重要的部分了,承擔(dān)著懸浮球的主要功能。

(1)手指按下時(shí)

按下時(shí),隱藏小球,展現(xiàn)大球,并記錄按下位置和按下時(shí)間。

case MotionEvent.ACTION_DOWN:
       mIsTouching = true;
       mImgBall.setVisibility(INVISIBLE);
       mImgBigBall.setVisibility(VISIBLE);
       mLastDownTime = System.currentTimeMillis();
       mLastDownX = event.getX();
       mLastDownY = event.getY();
       postDelayed(new Runnable() {
          @Override
          public void run() {
           if (isLongTouch()) {    
                  mIsLongTouch = true;    
                  mVibrator.vibrate(mPattern, -1);
            }
          }
       }, LONG_CLICK_LIMIT);
       break;

代碼最后的postDealy時(shí)干嘛使的呢?就是通過延遲300毫秒,判斷是否是長按模式。如果目前還沒有處于其他模式,則可判斷為長按,并震動(dòng)提醒。

(2)手指移動(dòng)時(shí)

這時(shí)要判斷是否是處于長按狀態(tài),如果是,那么進(jìn)入MOVE模式,移動(dòng)懸浮球,如果不是,則判斷操作手勢,即下拉還是上拉等其他手勢。

 case MotionEvent.ACTION_MOVE:
       if (!mIsLongTouch && isTouchSlop(event)) {
            return true;
       }
       if (mIsLongTouch && (mCurrentMode == MODE_NONE || mCurrentMode == MODE_MOVE)) {
            mLayoutParams.x = (int) (event.getRawX() - mOffsetToParent);
            mLayoutParams.y = (int) (event.getRawY() - mOffsetToParentY);
            mWindowManager.updateViewLayout(FloatBallView.this, mLayoutParams);
            mBigBallX = mImgBigBall.getX();
            mBigBallY = mImgBigBall.getY();
            mCurrentMode = MODE_MOVE;
       } else {
            doGesture(event);
       }
       break;

進(jìn)行手勢操作的代碼如下,主要是根據(jù)當(dāng)前坐標(biāo)與按下時(shí)記錄的坐標(biāo)進(jìn)行計(jì)算,判斷手勢,并更新大球位置。

private void doGesture(MotionEvent event) {
    float offsetX = event.getX() - mLastDownX;
    float offsetY = event.getY() - mLastDownY;

    if (Math.abs(offsetX) < mTouchSlop && Math.abs(offsetY) < mTouchSlop) {
        return;
    }
    if (Math.abs(offsetX) > Math.abs(offsetY)) {
        if (offsetX > 0) {
            if (mCurrentMode == MODE_RIGHT) {
                return;
            }
            mCurrentMode = MODE_RIGHT;
            mImgBigBall.setX(mBigBallX + OFFSET);
            mImgBigBall.setY(mBigBallY);
        } else {
            if (mCurrentMode == MODE_LEFT) {
                return;
            }
            mCurrentMode = MODE_LEFT;
            mImgBigBall.setX(mBigBallX - OFFSET);
            mImgBigBall.setY(mBigBallY);
        }
    } else {
        if (offsetY > 0) {
            if (mCurrentMode == MODE_DOWN || mCurrentMode == MODE_GONE) {
                return;
            }
            mCurrentMode = MODE_DOWN;
            mImgBigBall.setX(mBigBallX);
            mImgBigBall.setY(mBigBallY + OFFSET);
            
            //如果長時(shí)間保持下拉狀態(tài),將會(huì)觸發(fā)移除懸浮球功能
            postDelayed(new Runnable() {
                @Override
                public void run() {
                    if (mCurrentMode == MODE_DOWN && mIsTouching) {
                        toRemove();
                        mCurrentMode = MODE_GONE;
                    }
                }
            }, REMOVE_LIMIT);
        } else {
            if (mCurrentMode == MODE_UP) {
                return;
            }
            mCurrentMode = MODE_UP;
            mImgBigBall.setX(mBigBallX);
            mImgBigBall.setY(mBigBallY - OFFSET);
        }
    }
}

(3)手指抬起時(shí)

手指抬起后,先要判斷是否是長按模式,不是的話再判斷是否是單擊,都不是的話就根據(jù)當(dāng)前狀態(tài)觸發(fā)對(duì)應(yīng)功能。

case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
       mIsTouching = false;
       if (mIsLongTouch) {
        mIsLongTouch = false;
       } else if (isClick(event)) {
        AccessibilityUtil.doBack(mService);
       } else {
        doUp();
       }
       mImgBall.setVisibility(VISIBLE);
       mImgBigBall.setVisibility(INVISIBLE);
       mCurrentMode = MODE_NONE;
       break;

效果

到目前為止,懸浮球的功能就實(shí)現(xiàn)了,來看看使用效果如何。

最后再說兩句

花了大半天,總算是大功告成了,程序員,最大的好處就是自己可以定制應(yīng)用??,
apk下載地址在這:https://pan.baidu.com/s/1slAhPDF,歡迎大家體驗(yàn)。項(xiàng)目我也提交到github上了:https://github.com/HalfStackDeveloper/FloatBall,感興趣可以看看,如果再能順便給個(gè)star是最好不過了??。

魅族小米請(qǐng)注意!試了魅族pro5,先點(diǎn)擊start->進(jìn)入輔助功能界面->點(diǎn)擊無障礙->開啟FloatBall輔助功能。接著還要干一件事,就是魅族自己給懸浮窗加了權(quán)限,必須進(jìn)入設(shè)置->應(yīng)用管理->已安裝中找到floatball->權(quán)限管理->開啟懸浮窗權(quán)限,小米應(yīng)該也是。此處不想吐槽國產(chǎn)ROM

(轉(zhuǎn)載請(qǐng)標(biāo)明ID:半棧工程師,個(gè)人博客:https://halfstackdeveloper.github.io)

歡迎關(guān)注我的知乎專欄:https://zhuanlan.zhihu.com/halfstack

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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