轻松自制flyme悬浮球

作者: 半栈工程师 | 来源:发表于2016-11-27 21:07 被阅读7981次

前言

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

相关文章

  • 轻松自制flyme悬浮球

    前言 去年用了一整年的MX4Pro,魅族留给我最大的印象就是悬浮球了(质量问题我就不说了),左右滑动切换应用、上拉...

  • 悬浮球

    需求:(1)可移动(2)可点击

  • 将ViewGroup裁切成圆形

    最近收到一个开发悬浮球的需求,悬浮球上显示一张图片,一个TextView文本。这个悬浮球被裁切成圆形。 该如何实现...

  • 移动端touch拖动事件和click事件冲突问题解决

    通过一个悬浮球交互功能的案例来阐述问题,以及解决办法。 实现效果 类似微信里的悬浮窗效果,苹果手机的悬浮球功能效果...

  • Android悬浮球

    FloatBall

  • 悬浮球SuspendedBall

    一个类似于苹果AssistiveTouch的悬浮球 拖动停止后,会自动停靠在距离较近的那一侧的边框上,可以自定义图...

  • Android 悬浮球

    闲来无事,搞一波悬浮球,此球: 无需权限 主要代码只有一个类,简简单单放进自己的工程 悬浮球可以用来干啥:打开侧滑...

  • 吹气悬浮球

    今天,我和童童一起逛万达商场。逛到四楼,我们路过了一个名叫“小时候”的商店,我们进去逛了逛。 我...

  • 无需权限的悬浮球,快收到你碗里去吧!

    闲来无事,搞一波悬浮球,此球: 无需权限 无需权限 无需权限 主要代码只有一个类,简简单单放进自己的工程 悬浮球可...

  • 悬浮球——悬浮文件夹

    在锤子T3发布会最后,介绍了一项名为onestep的功能,由此让我想到了另一种实现这种效果的办法以此来打破应用...

网友评论

  • 可爱到被草:最在做悬浮助手这个项目
    想问下怎么用Seekbar动态改变LayoutParams宽高
  • 3de9b51e2dd2:要是左右滑直接能切换应用就完美了 最喜欢魅族悬浮球的就是这个
  • 培根_b1a0:楼主您好,我是魅族应用中心返回键的作者,看了您的应用和我的很像,想请问下楼主有研究过怎么在输入法弹出的时候悬浮球自动上移的功能?
    半栈工程师:@培根_b1a0 哈哈,你是我文中提到那个《悬浮球》的作者吗?我之前一直用那个悬浮球,输入法这个倒是没有研究过,感觉也不太好弄啊,没办法全局监听输入法弹出。
  • 耿直boy:lz,你在弄这个的时候遇到什么问题么
    半栈工程师:好像没有什么问题啊,你有遇到吗?
  • 三千千千:想要自启动,但是360n4的应用启动管理里找不到球球,请问版主要怎么做呀?
    三千千千: @半栈工程师 想回复截图,好像没有办法。在应用权限管理那里,球球的自启动是灰色的,无法点亮。在自启动管理里面,找不到球球呢。😔
    半栈工程师:@三千千千 没有360手机,但是想要实现你这个需求估计不太可能吧。。。(你在应用启动管理里再找找FloatBall,应该会有啊)
    三千千千: @96f91ae5ec73 还有他自己带了一个魔球,在通知快捷栏中有一个图标,有办法点那个图标启动这个球球吗?
  • sendtion:小米5,7.0开不了
  • c5651a28be45:1、我的手机是魅族Pro5,Android 5.1;下载安装后发现左右滑动不能用,上下滑动、单机、拖动都正常,这是为什么啊
    2、另外,我是个新手,有个地方想不通想咨询下楼主:
    postDelayed(new Runnable() {
    @Override
    public void run() {
    if (!mIsLongTouch && mIsTouching && mCurrentMode == MODE_NONE) {
    mIsLongTouch = isLongClick(event);
    }
    }
    }, LONG_CLICK_LIMIT);
    传给 isLongClick(event)的event应该是ACTION_DOWN的MotionEvent,然后isLongClick方法的if语句判断感觉好像是一定是true啊,因为ACTION_DOWN的MotionEvent的offsetX/offSetY应该是0(只有按下还没有移动,难道移动时的MotionEvent会传递进来?),time的话因为是postDelayed延迟执行,所以肯定也大于等于LONG_CLICK_LIMIT了,
    if (offsetX < mTouchSlop && offsetY < mTouchSlop && time >= LONG_CLICK_LIMIT)
    不知道我这样的理解有什么问题……
    半栈工程师:@独朗二八 没想到你这么仔细 :smile: ,你说的是对的,是我写的有问题,
    正确的写法是:postDelayed(new Runnable() {
    @Override
    public void run() {
    if (mIsTouching && mCurrentMode == MODE_NONE) {
    mIsLongTouch=true;
    mVibrator.vibrate(mPattern, -1);
    }
    }
    }, LONG_CLICK_LIMIT);
    :smile:
  • 70abbc2c9596:大神快快更新
  • 70abbc2c9596: 楼主你这个软件真的做的很好,一百个赞!!
    我能给点建议么
    1.增加双击锁屏功能
    2.增加锁定悬浮球位置功能
    3.增加隐藏悬浮球应用列表(例如打电话时可以自动隐藏悬浮球,输入法时自动隐藏悬浮球)
    这都是我想做的,只因基础不扎实,做不了
    半栈工程师:@前行123 我觉得第二条不太实用,因为本身悬浮球就是固定的,除非长按。第三条目前不知道能不能实现监听全局的键盘事件。
    70abbc2c9596:@前行123第二三条不考虑?
    半栈工程师:@前行123 谢谢支持,锁屏功能本来是准备添加的,但是由于安全问题,在带指纹解锁的手机上进行锁屏,唤醒时不能使用指纹解锁了,只能密码解锁,所以才没做锁屏。
  • smartapple:下载apk 安装到模拟器上 4.4的 直接崩溃
    java.lang.RuntimeException: Unable to instantiate application com.android.tools.fd.runtime.BootstrapApplication: java.lang.ClassNotFoundException: Didn't find class "com.android.tools.fd.runtime.BootstrapApplication" on path: DexPathList[[zip file "/data/app/com.wangxiandeng.floatball-1.apk"],nativeLibraryDirectories=[/data/app-lib/com.wangxiandeng.floatball-1, /system/lib]]
    半栈工程师:@四条眼的小胖子 哈哈,我家里有一个魅族pro5,回去试试
    smartapple:目前又测试了一次 我是直接下载你的apk 测试的 apk 可以安装 模拟器和手机均可以 但是一运行就崩了 代码我也下载了 参考写了一遍 还没有运行成功 正在慢慢研究中呢 模拟器是4.4的 真机mx5 flyme最新系统 真的无法运行 :joy:
    半栈工程师:@四条眼的小胖子 应该不会啊,我刚刚又测了两个4.4的模拟器,都是ok的啊。不过你可以看一看这篇文章,看看是不是gradle的问题:http://blog.csdn.net/axuanqq/article/details/50667041
  • 系射鸡狮啊:楼主给力哦~收藏了,以后换其他系统手机就可以用这个了~不过flyme的MBack 和悬浮球 确实好用~ :smile:
    半栈工程师:@湿乎文化总编辑 当初悬浮球俘获了多少魅粉啊!
  • 河里的枇杷树:代码很整齐
  • 赖斯皮革:正琢磨怎么做弹窗流氓软件呢,作者真是雪中送炭呀
    半栈工程师: @赖斯皮革 我擦😅
  • 6d2a611d9a96:小米手机不行
  • 耿直boy:LZ,我在做你的项目。请见谅。因为我才自学那么一点点四大组jian
    半栈工程师:@半栈工程师 哈哈,加油哦
    耿直boy:@半栈工程师 做你的这悬浮球
    半栈工程师: @耿直boy 做我的项目?
  • df899ce6a6ab: 楼上是闪退,我的是打开floatball权限后没反应。
    半栈工程师: @df899ce6a6ab 你是android几点几的系统?我只测了5.1和6.0😅

本文标题:轻松自制flyme悬浮球

本文链接:https://www.haomeiwen.com/subject/lmpnpttx.html