輕松自制flyme懸浮球
去年用了一整年的MX4Pro,魅族留給我最大的印象就是懸浮球了(質量問題我就不說了),左右滑動切換應用、上拉返回桌面、下拉打開通知欄、輕觸返回…,一切都那么絲滑。然而自從上半年換成了s7dege,我感覺怎么也習慣不了沒有懸浮球的生活了。
三星自己也有一個類似于懸浮球的功能,不過太過復雜,不易用,懸浮球本來就該是一個一步操作的產品,看來三星在軟件設計方面還是任重而道遠。于是乎我便在各大應用市場上找懸浮球,把所有排名靠前的懸浮球應用都安裝試了一下,最后終于讓我找到了一款幾乎和flyme懸浮球相仿的app。
這款app在我手機里呆了好幾個月,是我手機里除了微信之外,唯一允許自啟動的應用了。很感謝這款app的開發者,不僅沒有任何廣告,還非常好用,完美移植了flyme自帶的懸浮球功能。
然而漸漸的,我便感覺到了一絲不舒服,那就是我每次安裝了一個新app,打開后提示要賦予權限(存儲、拍照)的時候,6.0的系統總會溫馨的彈出一個框:
然后我就必須到設置頁面,花半天找到懸浮球,關掉它的 “可出現在頂部的應用程” 權限,然后才能回到app,授予權限。最后,我還得再次跑到設置頁面,再花半天找到懸浮球,打開它的 “可出現在頂部的應用程” 權限。朋友啊朋友,這種體驗,一次就夠了,然而硬是讓我體驗了N次啊!
然而有什么能難得倒程序員的呢?剛好這個周末在家無事,我決定按照自己的習慣,打造一個心目中最易用的懸浮球。
設計
1.UI
UI很簡單,直接用sketch切了三個圓,一個是作為背景的灰色半透明的圓,一個是中心的小圓,另外還有一個默認隱藏的大圓。
2.功能
因為自己的操作習慣是固定的,所以也就不需要給懸浮球添加自定義操作的功能了,直接將操作對應的功能寫死即可。
(1)單擊:返回
(2)長按:移動懸浮球
(3)左滑右滑:打開最近應用程序
(4)上拉:返回桌面
(5)下拉:
這塊我最先開始定義的很簡單,就是下拉通知欄,但是經過一天的使用,我又給它加了一個功能,就是保持下拉狀態1.5秒,將移除懸浮球。這樣你便可以很簡單的移除掉懸浮球了。
實現
1.如何添加懸浮球到桌面
這里首先要感謝郭霖大神的 《 Android桌面懸浮窗效果實現,仿360手機衛士懸浮窗效果》 ,這部分我參考了這篇文章,成功的將懸浮球添加到了桌面。
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.手勢判斷
這是最重要的部分了,承擔著懸浮球的主要功能。
(1)手指按下時
按下時,隱藏小球,展現大球,并記錄按下位置和按下時間。
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 (!mIsLongTouch && mIsTouching && mCurrentMode == MODE_NONE) {
mIsLongTouch = isLongClick(event);
}
}
}, LONG_CLICK_LIMIT);
break;
代碼最后的postDealy時干嘛使的呢?就是通過延遲300毫秒,判斷是否是長按模式。其中判斷長按的方法代碼如下:
private boolean isLongClick(MotionEvent event) {
float offsetX = Math.abs(event.getX() - mLastDownX);
float offsetY = Math.abs(event.getY() - mLastDownY);
long time = System.currentTimeMillis() - mLastDownTime;
if (offsetX < mTouchSlop && offsetY < mTouchSlop && time >= LONG_CLICK_LIMIT) {
//震動提醒
mVibrator.vibrate(mPattern, -1);
return true;
} else {
return false;
}
}
(2)手指移動時
這時要判斷是否是處于長按狀態,如果是,那么進入MOVE模式,移動懸浮球,如果不是,則判斷操作手勢,即下拉還是上拉等其他手勢。
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;
進行手勢操作的代碼如下,主要是根據當前坐標與按下時記錄的坐標進行計算,判斷手勢,并更新大球位置。
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);
//如果長時間保持下拉狀態,將會觸發移除懸浮球功能
postDelayed(new Runnable() {
@Override
public void run() {
if (mCurrentMode == MODE_DOWN && mIsTouching) {
toRemove();
mCurrentMode = MODE_GONE;
}
}
}, TO_APP_INDEX_LIMIT);
} else {
if (mCurrentMode == MODE_UP) {
return;
}
mCurrentMode = MODE_UP;
mImgBigBall.setX(mBigBallX);
mImgBigBall.setY(mBigBallY - OFFSET);
}
}
}
(3)手指抬起時
手指抬起后,先要判斷是否是長按模式,不是的話再判斷是否是單擊,都不是的話就根據當前狀態觸發對應功能。
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;
效果
到目前為止,懸浮球的功能就實現了,來看看使用效果如何。
最后再說兩句
花了大半天,總算是大功告成了,程序員,最大的好處就是自己可以定制應用:joy:,