欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2024-03-02 13:00:58
现在很多安全类的软件,比如360手机助手,百度手机助手等等,都有一个悬浮窗,可以飘浮在桌面上,方便用户使用一些常用的操作。 今天这篇文章,就是介绍如何实现桌面悬浮窗效果的...

现在很多安全类的软件,比如360手机助手,百度手机助手等等,都有一个悬浮窗,可以飘浮在桌面上,方便用户使用一些常用的操作。

今天这篇文章,就是介绍如何实现桌面悬浮窗效果的。

首先,看一下效果图。

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

悬浮窗一共分为两个部分,一个是平常显示的小窗口,另外一个是点击小窗口显示出来的二级悬浮窗口。
首先,先看一下这个项目的目录结构。

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

最关键的就是红框内的四个类。

首先,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(); 
} 
} 

知道上面这些之后,我们就可以实现教学模板效果了,首先看效果图。

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

下面是代码实现

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实现桌面悬浮窗、蒙板效果实例代码,希望对大家有所帮助!