话不多说,先看效果:
自定义可拖动View
前两天碰见一个需求,效果如图。需要在当前actvity内实现一个可拖动控制的悬浮view。
这不是都快被造烂的轮子吗,我想都没想去github上一顿搜,然后很快啊就找到了。细看了一下才发现是自己大意了。github上的实现过于复杂了,我这里只需要在当前activity内生效,并不需要做一个跨多个activity页面甚至跨app能显示的悬浮view,于是决定自己造轮子。
思考:首先想到的一个方案是,把悬浮view直接添加到activity的根view中,也就是DecorView中。但是实际动手才发现在我们的项目中很难落地,因为我们主activity承担了太多业务,很多view会在某种操作后动态的被添加进来,导致很难保证悬浮view始终处于最上方(这里可以监听view树,当树结构的时候控制悬浮view始终显示在最上层,但逻辑会稍显复杂,并且可能会造成悬浮view在拖动过程中的中端或卡顿)。既然不能直接把悬浮view添加进viewGroup中,那只能借助于activity的windowManager了。一个activity可以展示多种类型的window,这里需要根据需求的具体情况而定选用什么类型,一般来说SUB_WINDOW的类型是够用了(也有例外,比如activity中包含了surfaceView,因为surfaceView在当前窗口中的类型也是SUB_WINDOW,因此会造成surfaceView覆盖悬浮view的情况)。这样的话悬浮view本身单独处在一个window中,android的事件是不能跨window传递的——至少framework层不支持这样的操作,为了不影响用户和activity主界面的交互,悬浮view所在的窗口大小只能是WRAP_CONTENT。一般来说还需要将窗口设计为透明的,核心代码如下:
mWindowManager = (WindowManager) activity.getSystemService(Context.WINDOW_SERVICE);
mLayoutParams =new WindowManager.LayoutParams();
mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS |
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
mLayoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
mLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
mLayoutParams.type = WindowManager.LayoutParams.LAST_SUB_WINDOW;
mLayoutParams.format = PixelFormat.RGBA_8888;
mLayoutParams.gravity = Gravity.TOP | Gravity.START;
这样的话拖动效果只能是移动窗口的位置了,如果只移动view的位置,你会神奇的发现view被你移出窗口之外了。
核心代码如下:
mLayoutParams.x = xPos;
mLayoutParams.y = yPos;
mWindowManager.updateViewLayout(mView, mLayoutParams);
另外在拖动的时候要注意去抖动,还要注意触发点击事件不要被拖动影响。这种代码网上比比皆是,就不贴了,开此博客的目的主要是记录解决问题的思路。
实现完毕,美滋滋。又可以去撩产品妹子了~