前言
去年用了一整年的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