当前位置: 移动技术网 > 移动技术>移动开发>Android > Android实现桌面悬浮窗、蒙板效果实例代码

Android实现桌面悬浮窗、蒙板效果实例代码

2019年07月24日  | 移动技术网移动技术  | 我要评论

现在很多安全类的软件,比如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实现桌面悬浮窗、蒙板效果实例代码,希望对大家有所帮助!

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网