网络科技

    今日:3| 主题:301759
收藏本版
互联网、科技极客的综合动态。

[其他] 轻松自制flyme悬浮球

[复制链接]
纠结星人 发表于 2016-11-27 22:40:16
179 1
去年用了一整年的MX4Pro,魅族留给我最大的印象就是悬浮球了(质量问题我就不说了),左右滑动切换应用、上拉返回桌面、下拉打开通知栏、轻触返回…,一切都那么丝滑。然而自从上半年换成了s7dege,我感觉怎么也习惯不了没有悬浮球的生活了。
  三星自己也有一个类似于悬浮球的功能,不过太过复杂,不易用,悬浮球本来就该是一个一步操作的产品,看来三星在软件设计方面还是任重而道远。于是乎我便在各大应用市场上找悬浮球,把所有排名靠前的悬浮球应用都安装试了一下,最后终于让我找到了一款几乎和flyme悬浮球相仿的app。
   

轻松自制flyme悬浮球

轻松自制flyme悬浮球

  这款app在我手机里呆了好几个月,是我手机里除了微信之外,唯一允许自启动的应用了。很感谢这款app的开发者,不仅没有任何广告,还非常好用,完美移植了flyme自带的悬浮球功能。
  然而渐渐的,我便感觉到了一丝不舒服,那就是我每次安装了一个新app,打开后提示要赋予权限(存储、拍照)的时候,6.0的系统总会温馨的弹出一个框:
  

轻松自制flyme悬浮球

轻松自制flyme悬浮球

   然后我就必须到设置页面,花半天找到悬浮球,关掉它的 “可出现在顶部的应用程” 权限,然后才能回到app,授予权限。最后,我还得再次跑到设置页面,再花半天找到悬浮球,打开它的 “可出现在顶部的应用程” 权限。朋友啊朋友,这种体验,一次就够了,然而硬是让我体验了N次啊!
  然而有什么能难得倒程序员的呢?刚好这个周末在家无事,我决定按照自己的习惯,打造一个心目中最易用的悬浮球。
  设计

  1.UI

  UI很简单,直接用sketch切了三个圆,一个是作为背景的灰色半透明的圆,一个是中心的小圆,另外还有一个默认隐藏的大圆。
  2.功能

  因为自己的操作习惯是固定的,所以也就不需要给悬浮球添加自定义操作的功能了,直接将操作对应的功能写死即可。
  (1)单击:返回

   (2)长按:移动悬浮球
   (3)左滑右滑:打开最近应用程序
  (4)上拉:返回桌面

   (5)下拉:
  这块我最先开始定义的很简单,就是下拉通知栏,但是经过一天的使用,我又给它加了一个功能,就是保持下拉状态1.5秒,将移除悬浮球。这样你便可以很简单的移除掉悬浮球了。
  实现

  1.如何添加悬浮球到桌面

   这里首先要感谢郭霖大神的 《 Android桌面悬浮窗效果实现,仿360手机卫士悬浮窗效果》 ,这部分我参考了这篇文章,成功的将悬浮球添加到了桌面。
  1. public static void addBallView(Context context) {
  2.     if (mBallView == null) {
  3.         WindowManager windowManager = getWindowManager(context);
  4.         int screenWidth = windowManager.getDefaultDisplay().getWidth();
  5.         int screenHeight = windowManager.getDefaultDisplay().getHeight();
  6.         mBallView = new FloatBallView(context);
  7.         LayoutParams params = new LayoutParams();
  8.         params.x = screenWidth;
  9.         params.y = screenHeight / 2;
  10.         params.width = WindowManager.LayoutParams.WRAP_CONTENT;
  11.         params.height = WindowManager.LayoutParams.WRAP_CONTENT;
  12.         params.gravity = Gravity.LEFT | Gravity.TOP;
  13.         params.type = LayoutParams.TYPE_PHONE;
  14.         params.format = PixelFormat.RGBA_8888;
  15.         params.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL
  16.                 | LayoutParams.FLAG_NOT_FOCUSABLE;
  17.         mBallView.setLayoutParams(params);
  18.         windowManager.addView(mBallView, params);
  19.     }
  20. }
复制代码
2.手势判断

  这是最重要的部分了,承担着悬浮球的主要功能。
  (1)手指按下时

  按下时,隐藏小球,展现大球,并记录按下位置和按下时间。
  1. case MotionEvent.ACTION_DOWN:
  2.        mIsTouching = true;
  3.        mImgBall.setVisibility(INVISIBLE);
  4.        mImgBigBall.setVisibility(VISIBLE);
  5.        mLastDownTime = System.currentTimeMillis();
  6.        mLastDownX = event.getX();
  7.        mLastDownY = event.getY();
  8.        postDelayed(new Runnable() {
  9.           @Override
  10.           public void run() {
  11.             if (!mIsLongTouch && mIsTouching && mCurrentMode == MODE_NONE) {
  12.                     mIsLongTouch = isLongClick(event);
  13.                }
  14.           }
  15.        }, LONG_CLICK_LIMIT);
  16.        break;
复制代码
代码最后的postDealy时干嘛使的呢?就是通过延迟300毫秒,判断是否是长按模式。其中判断长按的方法代码如下:
  1. private boolean isLongClick(MotionEvent event) {
  2.     float offsetX = Math.abs(event.getX() - mLastDownX);
  3.     float offsetY = Math.abs(event.getY() - mLastDownY);
  4.     long time = System.currentTimeMillis() - mLastDownTime;
  5.     if (offsetX < mTouchSlop && offsetY < mTouchSlop && time >= LONG_CLICK_LIMIT) {
  6.         //震动提醒
  7.         mVibrator.vibrate(mPattern, -1);
  8.         return true;
  9.     } else {
  10.         return false;
  11.     }
  12. }
复制代码
(2)手指移动时

  这时要判断是否是处于长按状态,如果是,那么进入MOVE模式,移动悬浮球,如果不是,则判断操作手势,即下拉还是上拉等其他手势。
  1. case MotionEvent.ACTION_MOVE:
  2.       if (!mIsLongTouch && isTouchSlop(event)) {
  3.               return true;
  4.       }
  5.       if (mIsLongTouch && (mCurrentMode == MODE_NONE || mCurrentMode == MODE_MOVE)) {
  6.               mLayoutParams.x = (int) (event.getRawX() - mOffsetToParent);
  7.               mLayoutParams.y = (int) (event.getRawY() - mOffsetToParentY);
  8.               mWindowManager.updateViewLayout(FloatBallView.this, mLayoutParams);
  9.               mBigBallX = mImgBigBall.getX();
  10.               mBigBallY = mImgBigBall.getY();
  11.               mCurrentMode = MODE_MOVE;
  12.       } else {
  13.               doGesture(event);
  14.       }
  15.       break;
复制代码
进行手势操作的代码如下,主要是根据当前坐标与按下时记录的坐标进行计算,判断手势,并更新大球位置。
  1. private void doGesture(MotionEvent event) {
  2.     float offsetX = event.getX() - mLastDownX;
  3.     float offsetY = event.getY() - mLastDownY;
  4.     if (Math.abs(offsetX) < mTouchSlop && Math.abs(offsetY) < mTouchSlop) {
  5.         return;
  6.     }
  7.     if (Math.abs(offsetX) > Math.abs(offsetY)) {
  8.         if (offsetX > 0) {
  9.             if (mCurrentMode == MODE_RIGHT) {
  10.                 return;
  11.             }
  12.             mCurrentMode = MODE_RIGHT;
  13.             mImgBigBall.setX(mBigBallX + OFFSET);
  14.             mImgBigBall.setY(mBigBallY);
  15.         } else {
  16.             if (mCurrentMode == MODE_LEFT) {
  17.                 return;
  18.             }
  19.             mCurrentMode = MODE_LEFT;
  20.             mImgBigBall.setX(mBigBallX - OFFSET);
  21.             mImgBigBall.setY(mBigBallY);
  22.         }
  23.     } else {
  24.         if (offsetY > 0) {
  25.             if (mCurrentMode == MODE_DOWN || mCurrentMode == MODE_GONE) {
  26.                 return;
  27.             }
  28.             mCurrentMode = MODE_DOWN;
  29.             mImgBigBall.setX(mBigBallX);
  30.             mImgBigBall.setY(mBigBallY + OFFSET);
  31.             //如果长时间保持下拉状态,将会触发移除悬浮球功能
  32.             postDelayed(new Runnable() {
  33.                 @Override
  34.                 public void run() {
  35.                     if (mCurrentMode == MODE_DOWN && mIsTouching) {
  36.                         toRemove();
  37.                         mCurrentMode = MODE_GONE;
  38.                     }
  39.                 }
  40.             }, TO_APP_INDEX_LIMIT);
  41.         } else {
  42.             if (mCurrentMode == MODE_UP) {
  43.                 return;
  44.             }
  45.             mCurrentMode = MODE_UP;
  46.             mImgBigBall.setX(mBigBallX);
  47.             mImgBigBall.setY(mBigBallY - OFFSET);
  48.         }
  49.     }
  50. }
复制代码
(3)手指抬起时

  手指抬起后,先要判断是否是长按模式,不是的话再判断是否是单击,都不是的话就根据当前状态触发对应功能。
  1. case MotionEvent.ACTION_CANCEL:
  2. case MotionEvent.ACTION_UP:
  3.        mIsTouching = false;
  4.        if (mIsLongTouch) {
  5.            mIsLongTouch = false;
  6.        } else if (isClick(event)) {
  7.            AccessibilityUtil.doBack(mService);
  8.        } else {
  9.            doUp();
  10.        }
  11.        mImgBall.setVisibility(VISIBLE);
  12.        mImgBigBall.setVisibility(INVISIBLE);
  13.        mCurrentMode = MODE_NONE;
  14.        break;
复制代码
效果

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

轻松自制flyme悬浮球

轻松自制flyme悬浮球

  最后再说两句

  花了大半天,总算是大功告成了,程序员,最大的好处就是自己可以定制应用:joy:,
   apk下载地址在这: http://od35ecbnl.bkt.clouddn.com/app-debug.apk ,欢迎大家体验。  项目我也提交到github上了: https://github.com/HalfStackDeveloper/FloatBall  ,感兴趣可以看看,如果再能顺便给个 star 是最好不过了:grimacing:。
元柳 发表于 2016-11-27 23:46:27
撸过...
回复 支持 反对

使用道具 举报

我要投稿

回页顶回复上一篇下一篇回列表
手机版/c.CoLaBug.com ( 粤ICP备05003221号 | 粤公网安备 44010402000842号 )

© 2001-2017 Comsenz Inc.

返回顶部 返回列表