• 通过Java高仿flyme悬浮球设计


    资源下载地址:https://download.csdn.net/download/sheziqiong/85796297
    资源下载地址:https://download.csdn.net/download/sheziqiong/85796297

    前言

    去年用了一整年的 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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    代码最后的 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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    进行手势操作的代码如下,主要是根据当前坐标与按下时记录的坐标进行计算,判断手势,并更新大球位置。

    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);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    (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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    效果

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

    资源下载地址:https://download.csdn.net/download/sheziqiong/85796297
    资源下载地址:https://download.csdn.net/download/sheziqiong/85796297

  • 相关阅读:
    如何用Android Studio实现登录跳转
    软件流程和管理(八):质量管理
    JS数组遍历方式 小小总结一下
    1387. 将整数按权重排序
    ZYNQ之FPGA学习----Vivado软件使用
    预处理指令
    .9图片详解
    寺庙小程序-H5网页开发
    【MHA】MySQL高可用MHA介绍8-常见错误以及解决
    docker load and build过程的一些步骤理解
  • 原文地址:https://blog.csdn.net/newlw/article/details/125481608