Android实现桌面悬浮窗、蒙板效果实例代码
现在很多安全类的软件,比如360手机助手,百度手机助手等等,都有一个悬浮窗,可以飘浮在桌面上,方便用户使用一些常用的操作。
今天这篇文章,就是介绍如何实现桌面悬浮窗效果的。
首先,看一下效果图。
悬浮窗一共分为两个部分,一个是平常显示的小窗口,另外一个是点击小窗口显示出来的二级悬浮窗口。
首先,先看一下这个项目的目录结构。
最关键的就是红框内的四个类。
首先,floatwindowservice是一个后台的服务类,主要负责在后台不断的刷新桌面上的小悬浮窗口,否则会导致更换界面之后,悬浮窗口也会随之消失,因此需要不断的刷新。下面是实现代码。
package com.qust.floatwindow; import java.util.timer; import java.util.timertask; import android.app.service; import android.content.context; import android.content.intent; import android.os.handler; import android.os.ibinder; /** * 悬浮窗后台服务 * * @author zhaokaiqiang * */ public class floatwindowservice extends service { public static final string layout_res_id = "layoutresid"; public static final string root_layout_id = "rootlayoutid"; // 用于在线程中创建/移除/更新悬浮窗 private handler handler = new handler(); private context context; private timer timer; // 小窗口布局资源id private int layoutresid; // 布局根布局id private int rootlayoutid; @override public int onstartcommand(intent intent, int flags, int startid) { context = this; layoutresid = intent.getintextra(layout_res_id, 0); rootlayoutid = intent.getintextra(root_layout_id, 0); if (layoutresid == 0 || rootlayoutid == 0) { throw new illegalargumentexception( "layoutresid or rootlayoutid is illegal"); } if (timer == null) { timer = new timer(); // 每500毫秒就执行一次刷新任务 timer.scheduleatfixedrate(new refreshtask(), 0, 500); } return super.onstartcommand(intent, flags, startid); } @override public void ondestroy() { super.ondestroy(); // service被终止的同时也停止定时器继续运行 timer.cancel(); timer = null; } private class refreshtask extends timertask { @override public void run() { // 当前界面没有悬浮窗显示,则创建悬浮 if (!floatwindowmanager.getinstance(context).iswindowshowing()) { handler.post(new runnable() { @override public void run() { floatwindowmanager.getinstance(context) .createsmallwindow(context, layoutresid, rootlayoutid); } }); } } } @override public ibinder onbind(intent intent) { return null; } }
除了后台服务之外,我们还需要两个自定义的布局,分别是floatwindowsmallview和floatwindowbigview,这两个自定义的布局,主要负责悬浮窗的前台显示,我们分别看一下代码实现。
首先是floatwindowsmallview类的实现。
package com.qust.floatwindow; import java.lang.reflect.field; import android.annotation.suppresslint; import android.content.context; import android.graphics.pixelformat; import android.view.gravity; import android.view.layoutinflater; import android.view.motionevent; import android.view.view; import android.view.windowmanager; import android.widget.linearlayout; import android.widget.textview; import com.qust.demo.screenutils; import com.qust.floatingwindow.r; /** * 小悬浮窗,用于初始显示 * * @author zhaokaiqiang * */ public class floatwindowsmallview extends linearlayout { // 小悬浮窗的宽 public int viewwidth; // 小悬浮窗的高 public int viewheight; // 系统状态栏的高度 private static int statusbarheight; // 用于更新小悬浮窗的位置 private windowmanager windowmanager; // 小悬浮窗的布局参数 public windowmanager.layoutparams smallwindowparams; // 记录当前手指位置在屏幕上的横坐标 private float xinscreen; // 记录当前手指位置在屏幕上的纵坐标 private float yinscreen; // 记录手指按下时在屏幕上的横坐标,用来判断单击事件 private float xdowninscreen; // 记录手指按下时在屏幕上的纵坐标,用来判断单击事件 private float ydowninscreen; // 记录手指按下时在小悬浮窗的view上的横坐标 private float xinview; // 记录手指按下时在小悬浮窗的view上的纵坐标 private float yinview; // 单击接口 private onclicklistener listener; /** * 构造函数 * * @param context * 上下文对象 * @param layoutresid * 布局资源id * @param rootlayoutid * 根布局id */ public floatwindowsmallview(context context, int layoutresid, int rootlayoutid) { super(context); windowmanager = (windowmanager) context .getsystemservice(context.window_service); layoutinflater.from(context).inflate(layoutresid, this); view view = findviewbyid(rootlayoutid); viewwidth = view.getlayoutparams().width; viewheight = view.getlayoutparams().height; statusbarheight = getstatusbarheight(); textview percentview = (textview) findviewbyid(r.id.percent); percentview.settext("悬浮窗"); smallwindowparams = new windowmanager.layoutparams(); // 设置显示类型为phone smallwindowparams.type = windowmanager.layoutparams.type_phone; // 显示图片格式 smallwindowparams.format = pixelformat.rgba_8888; // 设置交互模式 smallwindowparams.flags = windowmanager.layoutparams.flag_not_touch_modal | windowmanager.layoutparams.flag_not_focusable; // 设置对齐方式为左上 smallwindowparams.gravity = gravity.left | gravity.top; smallwindowparams.width = viewwidth; smallwindowparams.height = viewheight; smallwindowparams.x = screenutils.getscreenwidth(context); smallwindowparams.y = screenutils.getscreenheight(context) / 2; } @suppresslint("clickableviewaccessibility") @override public boolean ontouchevent(motionevent event) { switch (event.getaction()) { // 手指按下时记录必要的数据,纵坐标的值都减去状态栏的高度 case motionevent.action_down: // 获取相对与小悬浮窗的坐标 xinview = event.getx(); yinview = event.gety(); // 按下时的坐标位置,只记录一次 xdowninscreen = event.getrawx(); ydowninscreen = event.getrawy() - statusbarheight; break; case motionevent.action_move: // 时时的更新当前手指在屏幕上的位置 xinscreen = event.getrawx(); yinscreen = event.getrawy() - statusbarheight; // 手指移动的时候更新小悬浮窗的位置 updateviewposition(); break; case motionevent.action_up: // 如果手指离开屏幕时,按下坐标与当前坐标相等,则视为触发了单击事件 if (xdowninscreen == event.getrawx() && ydowninscreen == (event.getrawy() - getstatusbarheight())) { if (listener != null) { listener.click(); } } break; } return true; } /** * 设置单击事件的回调接口 */ public void setonclicklistener(onclicklistener listener) { this.listener = listener; } /** * 更新小悬浮窗在屏幕中的位置 */ private void updateviewposition() { smallwindowparams.x = (int) (xinscreen - xinview); smallwindowparams.y = (int) (yinscreen - yinview); windowmanager.updateviewlayout(this, smallwindowparams); } /** * 获取状态栏的高度 * * @return */ private int getstatusbarheight() { try { class<?> c = class.forname("com.android.internal.r$dimen"); object o = c.newinstance(); field field = c.getfield("status_bar_height"); int x = (integer) field.get(o); return getresources().getdimensionpixelsize(x); } catch (exception e) { e.printstacktrace(); } return 0; } /** * 单击接口 * * @author zhaokaiqiang * */ public interface onclicklistener { public void click(); } }
在这个类里面,主要的工作是实现悬浮窗口在桌面前端的实现,还有就是位置的移动和单击事件的判断以及处理。这里使用的是主要是windowmanager类的一些方法和属性,下一篇会详细说明,这篇只说实现。
除了小悬浮窗之外,点击之后弹出的二级悬浮窗也是类似的方式添加到桌面上,下面是二级悬浮窗的代码。
package com.qust.floatwindow; import android.content.context; import android.graphics.pixelformat; import android.view.gravity; import android.view.layoutinflater; import android.view.view; import android.view.windowmanager; import android.widget.linearlayout; import android.widget.textview; import com.qust.demo.screenutils; import com.qust.floatingwindow.r; public class floatwindowbigview extends linearlayout { // 记录大悬浮窗的宽 public int viewwidth; // 记录大悬浮窗的高 public int viewheight; public windowmanager.layoutparams bigwindowparams; private context context; public floatwindowbigview(context context) { super(context); this.context = context; layoutinflater.from(context).inflate(r.layout.float_window_big, this); view view = findviewbyid(r.id.big_window_layout); viewwidth = view.getlayoutparams().width; viewheight = view.getlayoutparams().height; bigwindowparams = new windowmanager.layoutparams(); // 设置显示的位置,默认的是屏幕中心 bigwindowparams.x = screenutils.getscreenwidth(context) / 2 - viewwidth / 2; bigwindowparams.y = screenutils.getscreenheight(context) / 2 - viewheight / 2; bigwindowparams.type = windowmanager.layoutparams.type_phone; bigwindowparams.format = pixelformat.rgba_8888; // 设置交互模式 bigwindowparams.flags = windowmanager.layoutparams.flag_not_touch_modal | windowmanager.layoutparams.flag_not_focusable; bigwindowparams.gravity = gravity.left | gravity.top; bigwindowparams.width = viewwidth; bigwindowparams.height = viewheight; initview(); } private void initview() { textview tv_back = (textview) findviewbyid(r.id.tv_back); tv_back.setonclicklistener(new onclicklistener() { @override public void onclick(view v) { floatwindowmanager.getinstance(context).removebigwindow(); } }); } }
这些基本的类建立起来之后,剩下的就是最重要的类floatwindowmanager的实现。这个类实现的就是对悬浮窗的操作。
package com.qust.floatwindow; import android.content.context; import android.content.intent; import android.view.windowmanager; /** * 悬浮窗管理器 * * @author zhaokaiqiang * */ public class floatwindowmanager { // 小悬浮窗对象 private floatwindowsmallview smallwindow; // 大悬浮窗对象 private floatwindowbigview bigwindow; // 用于控制在屏幕上添加或移除悬浮窗 private windowmanager mwindowmanager; // floatwindowmanager的单例 private static floatwindowmanager floatwindowmanager; // 上下文对象 private context context; private floatwindowmanager(context context) { this.context = context; } public static floatwindowmanager getinstance(context context) { if (floatwindowmanager == null) { floatwindowmanager = new floatwindowmanager(context); } return floatwindowmanager; } /** * 创建小悬浮窗 * * @param context * 必须为应用程序的context. */ public void createsmallwindow(context context, int layoutresid, int rootlayoutid) { windowmanager windowmanager = getwindowmanager(); if (smallwindow == null) { smallwindow = new floatwindowsmallview(context, layoutresid, rootlayoutid); windowmanager.addview(smallwindow, smallwindow.smallwindowparams); } } /** * 将小悬浮窗从屏幕上移除 * * @param context */ public void removesmallwindow() { if (smallwindow != null) { windowmanager windowmanager = getwindowmanager(); windowmanager.removeview(smallwindow); smallwindow = null; } } public void setonclicklistener(floatwindowsmallview.onclicklistener listener) { if (smallwindow != null) { smallwindow.setonclicklistener(listener); } } /** * 创建大悬浮窗 * * @param context * 必须为应用程序的context. */ public void createbigwindow(context context) { windowmanager windowmanager = getwindowmanager(); if (bigwindow == null) { bigwindow = new floatwindowbigview(context); windowmanager.addview(bigwindow, bigwindow.bigwindowparams); } } /** * 将大悬浮窗从屏幕上移除 * * @param context */ public void removebigwindow() { if (bigwindow != null) { windowmanager windowmanager = getwindowmanager(); windowmanager.removeview(bigwindow); bigwindow = null; } } public void removeall() { context.stopservice(new intent(context, floatwindowservice.class)); removesmallwindow(); removebigwindow(); } /** * 是否有悬浮窗显示(包括小悬浮窗和大悬浮) * * @return 有悬浮窗显示在桌面上返回true,没有的话返回false */ public boolean iswindowshowing() { return smallwindow != null || bigwindow != null; } /** * 如果windowmanager还未创建,则创建新的windowmanager返回。否则返回当前已创建的windowmanager * * @param context * @return */ private windowmanager getwindowmanager() { if (mwindowmanager == null) { mwindowmanager = (windowmanager) context .getsystemservice(context.window_service); } return mwindowmanager; } }
还有个获取屏幕宽高的帮助类。
package com.qust.demo; import android.content.context; import android.view.windowmanager; /** * 屏幕帮助类 * * @author zhaokaiqiang * */ public class screenutils { /** * 获取屏幕宽度 * * @return */ @suppresswarnings("deprecation") public static int getscreenwidth(context context) { return ((windowmanager) context .getsystemservice(context.window_service)).getdefaultdisplay() .getwidth(); } /** * 获取屏幕宽度 * * @return */ @suppresswarnings("deprecation") public static int getscreenheight(context context) { return ((windowmanager) context .getsystemservice(context.window_service)).getdefaultdisplay() .getheight(); } }
完成这些,我们就可以直接用了。
package com.qust.demo; import android.app.activity; import android.content.context; import android.content.intent; import android.os.bundle; import android.view.keyevent; import android.view.view; import com.qust.floatingwindow.r; import com.qust.floatwindow.floatwindowmanager; import com.qust.floatwindow.floatwindowservice; import com.qust.floatwindow.floatwindowsmallview.onclicklistener; /** * 示例 * * @classname: com.qust.demo.mainactivity * @description: * @author zhaokaiqiang * @date 2014-10-23 下午11:30:13 * */ public class mainactivity extends activity { private floatwindowmanager floatwindowmanager; private context context; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); context = this; floatwindowmanager = floatwindowmanager.getinstance(context); } /** * 显示小窗口 * * @param view */ public void show(view view) { // 需要传递小悬浮窗布局,以及根布局的id,启动后台服务 intent intent = new intent(context, floatwindowservice.class); intent.putextra(floatwindowservice.layout_res_id, r.layout.float_window_small); intent.putextra(floatwindowservice.root_layout_id, r.id.small_window_layout); startservice(intent); } /** * 显示二级悬浮窗 * * @param view */ public void showbig(view view) { // 设置小悬浮窗的单击事件 floatwindowmanager.setonclicklistener(new onclicklistener() { @override public void click() { floatwindowmanager.createbigwindow(context); } }); } /** * 移除所有的悬浮窗 * * @param view */ public void remove(view view) { floatwindowmanager.removeall(); } @override public boolean onkeydown(int keycode, keyevent event) { // 返回键移除二级悬浮窗 if (keycode == keyevent.keycode_back && event.getaction() == keyevent.action_down) { floatwindowmanager.removebigwindow(); return true; } return super.onkeydown(keycode, event); } }
项目下载地址:https://github.com/zhaokaiqiang/floatwindow
在上面文章中,我们介绍了如何实现桌面悬浮窗口,在这个效果的实现过程中,最重要的一个类就是windowmanager,今天这篇文章,将对windowmanager的使用进行介绍,并且实现一个使用windowmanager来实现用户打开app,显示首次使用教学蒙板的效果。
windowmanager类实现了viewmanager接口,viewmanager接口允许我们在activity上添加或者是移除view,因此windowmanager也允许我们在activity上进行view的添加和移除操作。
我们可以通过下面的方法获取一个windowmanager对象
context.getsystemservice(context.window_service)
在activity之中,我们可以直接通过getwindowmanager()获取到一个windowmanager对象。
每一个windowmanager实例都被绑定到一个独有的display对象上面,如果我们想获取不同display的windowmanager对象,我们可以通过createdisplaycontext(display)获取到这个display的context对象,然后使用上面的方法,也可以获取到一个windowmanager对象。
我们在使用windowmanager类的时候,通常使用下面的几个方法:
windowmanager.addview(view,windowmanager.layoutparam); windowmanager.removeview(); windowmanager.getdefaultdisplay();
windowmanager.addview()方法用来向当前的窗口上添加view对象,需要接受两个参数,view是要添加到窗口的view对象,而windowmanager.layoutparam则是添加的窗口的参数,在上一篇添加悬浮窗的操作的时候,需要对layoutparam设置很多参数,下面我们看一下常用的设置
// 设置layoutparams参数 layoutparams params = new windowmanager.layoutparams(); //设置显示的类型,type_phone指的是来电话的时候会被覆盖,其他时候会在最前端,显示位置在statebar下面,其他更多的值请查阅文档 params.type = windowmanager.layoutparams.type_phone; //设置显示格式 params.format = pixelformat.rgba_8888; //设置对齐方式 params.gravity = gravity.left | gravity.top; //设置宽高 params.width = screenutils.getscreenwidth(this); params.height = screenutils.getscreenheight(this); //设置显示的位置 params.x; params.y;
设置好layoutparam之后,我们就可以通过windowmanager.addview(view,windowmanager.layoutparam)将view添加到窗口之上,不过,我们需要申明权限
<uses-permissionandroid:name="android.permission.system_alert_window"/>
添加完成之后,我们就可以在窗口上看到我们添加的view对象了。如果我们想将添加的view移除,我们只需要调用windowmanager.removeview()即可,参数就是我们前面使用的view对象,使用很简单。除了这个方法,还有个windowmanager.removeviewimmediate(),也可以将view移除,但是文档中说,这个方法并不是给一般程序调用的,因此需要小心使用,我们开发的都属于一般程序,建议不要使用这个方法。
除了这两个方法之外,我们最常用的另外一个方法就是windowmanager.getdefaultdisplay(),通过这个方法,我们可以获取到当前界面的display的一个对象,然后我们就可以获取到当前屏幕的一些参数,比如说宽高。
下面是我常用的一个工具类。
package com.qust.teachmask; import android.content.context; import android.view.windowmanager; /** * 屏幕帮助类 * * @author zhaokaiqiang * */ public class screenutils { /** * 获取屏幕宽度 * * @return */ @suppresswarnings("deprecation") public static int getscreenwidth(context context) { return ((windowmanager) context .getsystemservice(context.window_service)).getdefaultdisplay() .getwidth(); } /** * 获取屏幕宽度 * * @return */ @suppresswarnings("deprecation") public static int getscreenheight(context context) { return ((windowmanager) context .getsystemservice(context.window_service)).getdefaultdisplay() .getheight(); } }
知道上面这些之后,我们就可以实现教学模板效果了,首先看效果图。
下面是代码实现
package com.qust.teachmask; import android.app.activity; import android.graphics.pixelformat; import android.os.bundle; import android.view.gravity; import android.view.view; import android.view.view.onclicklistener; import android.view.windowmanager; import android.view.windowmanager.layoutparams; import android.widget.imageview; import android.widget.imageview.scaletype; public class mainactivity extends activity { private imageview img; private windowmanager windowmanager; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); windowmanager = getwindowmanager(); // 动态初始化图层 img = new imageview(this); img.setlayoutparams(new layoutparams( android.view.viewgroup.layoutparams.match_parent, android.view.viewgroup.layoutparams.match_parent)); img.setscaletype(scaletype.fit_xy); img.setimageresource(r.drawable.guide); // 设置layoutparams参数 layoutparams params = new windowmanager.layoutparams(); // 设置显示的类型,type_phone指的是来电话的时候会被覆盖,其他时候会在最前端,显示位置在statebar下面,其他更多的值请查阅文档 params.type = windowmanager.layoutparams.type_phone; // 设置显示格式 params.format = pixelformat.rgba_8888; // 设置对齐方式 params.gravity = gravity.left | gravity.top; // 设置宽高 params.width = screenutils.getscreenwidth(this); params.height = screenutils.getscreenheight(this); // 添加到当前的窗口上 windowmanager.addview(img, params); // 点击图层之后,将图层移除 img.setonclicklistener(new onclicklistener() { @override public void onclick(view arg0) { windowmanager.removeview(img); } }); } }
本文非原创,转载于:
以上所述是小编给大家介绍的android实现桌面悬浮窗、蒙板效果实例代码,希望对大家有所帮助!