Android自定义覆盖层控件 悬浮窗控件
在我们移动应用开发过程中,偶尔有可能会接到这种需求:
1、在手机桌面创建一个窗口,类似于360的悬浮窗口,点击这个窗口可以响应(至于窗口拖动我们可以后面再扩展)。
2、自己开发的应用去启动一个非本应用b,在b应用的某个界面增加一个引导窗口。
3、在应用的页面上触发启动这个窗口,该窗口悬浮在这个页面上,但又不会影响界面的其他操作。即不像popupwindow那样要么窗口消失要么页面不可响应
以上需求都有几个共同特点,1、窗口的承载页面不一定不是本应用页面(activity),即不是类似dialog, popupwindow之类的页面。2、窗口的显示不会影响用户对其他界面的操作。
根据以上特点,我们发现这类的窗口其不影响其他界面操作特点有点像toast,但又不完全是,因为toast是自己消失的。其界面可以恒显示又有点像popupwindow,只当调用了消失方法才会消失。所以我们在做这样的控件的时候可以去参考一下toast和popupwindow如何实现。最主要的时候toast。好了说了这么多大概的思路我们已经明白了。
透过toast,popupwindow源码我们发现,toast,popup的实现都是通过windowmanager的addview和removeview以及通过设置layoutparams实现的。因此后面设计就该从这里入手,废话不说了----去实现。
第一步设计类似toast的类floatwindow
package com.floatwindowtest.john.floatwindowtest.wiget; import android.app.activity; import android.content.context; import android.graphics.pixelformat; import android.view.gravity; import android.view.keyevent; import android.view.motionevent; import android.view.view; import android.view.viewgroup; import android.view.windowmanager; import android.widget.framelayout; import android.widget.linearlayout; import static android.view.viewgroup.layoutparams.match_parent; import static android.view.viewgroup.layoutparams.wrap_content; import static android.view.windowmanager.layoutparams.flag_not_focusable; import static android.view.windowmanager.layoutparams.flag_watch_outside_touch; /** * created by john on 2017/3/10. */ class floatwindow { private final context mcontext; private windowmanager windowmanager; private view floatview; private windowmanager.layoutparams params; public floatwindow(context mcontext) { this.mcontext = mcontext; this.params = new windowmanager.layoutparams(); } /** * 显示浮动窗口 * @param view * @param x view距离左上角的x距离 * @param y view距离左上角的y距离 */ void show(view view, int x, int y) { this.windowmanager = (windowmanager) this.mcontext.getsystemservice(context.window_service); params.height = windowmanager.layoutparams.wrap_content; params.width = windowmanager.layoutparams.wrap_content; params.gravity = gravity.top | gravity.left; params.format = pixelformat.translucent; params.x = x; params.y = y; params.type = windowmanager.layoutparams.type_toast; params.flags = windowmanager.layoutparams.flag_keep_screen_on | flag_not_focusable | flag_watch_outside_touch | windowmanager.layoutparams.flag_alt_focusable_im; floatview = view; windowmanager.addview(floatview, params); } /** * 显示浮动窗口 * @param view * @param x * @param y * @param listener 窗体之外的监听 * @param backlistener 返回键盘监听 */ void show(view view, int x, int y, outsidetouchlistener listener, keybacklistener backlistener) { this.windowmanager = (windowmanager) this.mcontext.getsystemservice(context.window_service); final floatwindowcontainerview containerview = new floatwindowcontainerview(this.mcontext, listener, backlistener); containerview.addview(view, wrap_content, wrap_content); params.height = windowmanager.layoutparams.wrap_content; params.width = windowmanager.layoutparams.wrap_content; params.gravity = gravity.top | gravity.left; params.format = pixelformat.translucent; params.x = x; params.y = y; params.type = windowmanager.layoutparams.type_toast; // // params.flags = windowmanager.layoutparams.flag_keep_screen_on // | windowmanager.layoutparams.flag_alt_focusable_im | windowmanager.layoutparams.flag_watch_outside_touch // | windowmanager.layoutparams. flag_not_focusable ; params.flags = windowmanager.layoutparams.flag_keep_screen_on | windowmanager.layoutparams.flag_alt_focusable_im | windowmanager.layoutparams.flag_watch_outside_touch | windowmanager.layoutparams.flag_not_touch_modal; floatview = containerview; windowmanager.addview(floatview, params); } /** * 更新view对象文职 * * @param offset_x x偏移量 * @param offset_y y偏移量 */ public void updatewindowlayout(float offset_x, float offset_y) { params.x += offset_x; params.y += offset_y; windowmanager.updateviewlayout(floatview, params); } /** * 关闭界面 */ void dismiss() { if (this.windowmanager == null) { this.windowmanager = (windowmanager) this.mcontext.getsystemservice(context.window_service); } if (floatview != null) { windowmanager.removeview(floatview); } floatview = null; } public void justhidewindow() { this.floatview.setvisibility(view.gone); } private class floatwindowcontainerview extends framelayout { private outsidetouchlistener listener; private keybacklistener backlistener; public floatwindowcontainerview(context context, outsidetouchlistener listener, keybacklistener backlistener) { super(context); this.listener = listener; this.backlistener = backlistener; } @override public boolean dispatchkeyevent(keyevent event) { if (event.getkeycode() == keyevent.keycode_back) { if (getkeydispatcherstate() == null) { if (backlistener != null) { backlistener.onkeybackpressed(); } return super.dispatchkeyevent(event); } if (event.getaction() == keyevent.action_down && event.getrepeatcount() == 0) { keyevent.dispatcherstate state = getkeydispatcherstate(); if (state != null) { state.starttracking(event, this); } return true; } else if (event.getaction() == keyevent.action_up) { keyevent.dispatcherstate state = getkeydispatcherstate(); if (state != null && state.istracking(event) && !event.iscanceled()) { system.out.println("dsfdfdsfds"); if (backlistener != null) { backlistener.onkeybackpressed(); } return super.dispatchkeyevent(event); } } return super.dispatchkeyevent(event); } else { return super.dispatchkeyevent(event); } } @override public boolean ontouchevent(motionevent event) { final int x = (int) event.getx(); final int y = (int) event.gety(); if ((event.getaction() == motionevent.action_down) && ((x < 0) || (x >= getwidth()) || (y < 0) || (y >= getheight()))) { return true; } else if (event.getaction() == motionevent.action_outside) { if (listener != null) { listener.onoutsidetouch(); } system.out.println("dfdf"); return true; } else { return super.ontouchevent(event); } } } }
大家可能会注意到
// params.flags = windowmanager.layoutparams.flag_keep_screen_on // | windowmanager.layoutparams.flag_alt_focusable_im | windowmanager.layoutparams.flag_watch_outside_touch // | windowmanager.layoutparams. flag_not_focusable ; params.flags = windowmanager.layoutparams.flag_keep_screen_on | windowmanager.layoutparams.flag_alt_focusable_im | windowmanager.layoutparams.flag_watch_outside_touch | windowmanager.layoutparams.flag_not_touch_modal;
这些设置有所不同,这就是我们要实现既能够监听窗口之外的触目事件,又不会影响他们自己的操作的关键地方 ,同时| windowmanager.layoutparams. flag_not_focusable ;和| windowmanager.layoutparams.flag_not_touch_modal; 窗体是否监听到返回键的关键设置 需要指出的是一旦窗体监听到返回键事件,则当前activity不会再监听到返回按钮事件了,所以大家可根据自己的实际情况出发做出选择。
为了方便管理这些浮动窗口的显示和消失,还写了一个管理窗口显示的类floatwindowmanager。这是一个单例模式 对应的显示窗口也是只显示一个。大家可以根据自己的需求是改变 这里不再明细。
package com.floatwindowtest.john.floatwindowtest.wiget; import android.content.context; import android.view.view; /** * * created by john on 2017/3/10. */ public class floatwindowmanager { private static floatwindowmanager manager; private floatwindow floatwindow; private floatwindowmanager(){ } public static synchronized floatwindowmanager getinstance(){ if(manager==null){ manager=new floatwindowmanager(); } return manager; } public void showfloatwindow(context context, view view,int x,int y){ if(floatwindow!=null){ floatwindow.dismiss(); } floatwindow=new floatwindow(context); floatwindow.show(view,x,y); } public void showfloatwindow(context context, view view, int x, int y, outsidetouchlistener listener,keybacklistener backlistener){ if(floatwindow!=null){ floatwindow.dismiss(); } floatwindow=new floatwindow(context); floatwindow.show(view,0,0,listener,backlistener); } public void dismissfloatwindow(){ if(floatwindow!=null){ floatwindow.dismiss(); } } public void justhidewindow(){ floatwindow.justhidewindow(); } /** * 更新位置 * @param offsetx * @param offsety */ public void updatewindowlayout(float offsetx, float offsety){ floatwindow.updatewindowlayout(offsetx,offsety); }; }
还有设计类似悬浮球的窗口等 大家可以自己运行一遍比这里看千遍更有用。
附件:android浮动窗口
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。