轻松自制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 (isLongTouch()) {    
                  mIsLongTouch = true;    
                  mVibrator.vibrate(mPattern, -1);
            }
          }
       }, LONG_CLICK_LIMIT);
       break;

代码最后的postDealy时干嘛使的呢?就是通过延迟300毫秒,判断是否是长按模式。如果目前还没有处于其他模式,则可判断为长按,并震动提醒。

(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;
                    }
                }
            }, REMOVE_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;

效果

到目前为止,悬浮球的功能就实现了,来看看使用效果如何。

最后再说两句

花了大半天,总算是大功告成了,程序员,最大的好处就是自己可以定制应用😂,
apk下载地址在这:https://pan.baidu.com/s/1slAhPDF,欢迎大家体验。项目我也提交到github上了:https://github.com/HalfStackDeveloper/FloatBall,感兴趣可以看看,如果再能顺便给个star是最好不过了😬。

魅族小米请注意!试了魅族pro5,先点击start->进入辅助功能界面->点击无障碍->开启FloatBall辅助功能。接着还要干一件事,就是魅族自己给悬浮窗加了权限,必须进入设置->应用管理->已安装中找到floatball->权限管理->开启悬浮窗权限,小米应该也是。此处不想吐槽国产ROM

(转载请标明ID:半栈工程师,个人博客:https://halfstackdeveloper.github.io)

欢迎关注我的知乎专栏:https://zhuanlan.zhihu.com/halfstack

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容