轉載請注明出處:http://blog.csdn.net/zhaokaiqiang1992
現在很多安全類的軟體,比如360手機助手,百度手機助手等等,都有一個懸浮窗,可以飄浮在桌面上,友善使用者使用一些常用的操作。今天這篇文章,就是介紹如何實作桌面懸浮窗效果的。
首先,看一下效果圖。
懸浮窗一共分為兩個部分,一個是平常顯示的小視窗,另外一個是點選小視窗顯示出來的二級懸浮視窗。
首先,先看一下這個項目的目錄結構。
最關鍵的就是紅框内的四個類。
首先,floatwindowservice是一個背景的服務類,主要負責在背景不斷的重新整理桌面上的小懸浮視窗,否則會導緻更換界面之後,懸浮視窗也會随之消失,是以需要不斷的重新整理。下面是實作代碼。
[java] view
plaincopy
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);
}
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);
}
});
}
public ibinder onbind(intent intent) {
return null;
}
除了背景服務之外,我們還需要兩個自定義的布局,分别是floatwindowsmallview和floatwindowbigview,這兩個自定義的布局,主要負責懸浮窗的前台顯示,我們分别看一下代碼實作。
首先是floatwindowsmallview類的實作。
import java.lang.reflect.field;
import android.annotation.suppresslint;
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;
* 小懸浮窗,用于初始顯示
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")
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();
case motionevent.action_up:
// 如果手指離開螢幕時,按下坐标與目前坐标相等,則視為觸發了單擊事件
if (xdowninscreen == event.getrawx()
&& ydowninscreen == (event.getrawy() - getstatusbarheight())) {
if (listener != null) {
listener.click();
}
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類的一些方法和屬性,下一篇會詳細說明,這篇隻說實作。
除了小懸浮窗之外,點選之後彈出的二級懸浮窗也是類似的方式添加到桌面上,下面是二級懸浮窗的代碼。
public class floatwindowbigview extends linearlayout {
// 記錄大懸浮窗的寬
// 記錄大懸浮窗的高
public windowmanager.layoutparams bigwindowparams;
public floatwindowbigview(context context) {
this.context = context;
layoutinflater.from(context).inflate(r.layout.float_window_big, this);
view view = findviewbyid(r.id.big_window_layout);
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
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的實作。這個類實作的就是對懸浮窗的操作。
* 懸浮窗管理器
public class floatwindowmanager {
// 小懸浮窗對象
private floatwindowsmallview smallwindow;
// 大懸浮窗對象
private floatwindowbigview bigwindow;
// 用于控制在螢幕上添加或移除懸浮窗
private windowmanager mwindowmanager;
// floatwindowmanager的單例
private static floatwindowmanager floatwindowmanager;
// 上下文對象
private floatwindowmanager(context context) {
public static floatwindowmanager getinstance(context context) {
if (floatwindowmanager == null) {
floatwindowmanager = new floatwindowmanager(context);
return floatwindowmanager;
* 建立小懸浮窗
* 必須為應用程式的context.
public void createsmallwindow(context context, int layoutresid,
windowmanager windowmanager = getwindowmanager();
if (smallwindow == null) {
smallwindow = new floatwindowsmallview(context, layoutresid,
rootlayoutid);
windowmanager.addview(smallwindow, smallwindow.smallwindowparams);
* 将小懸浮窗從螢幕上移除
public void removesmallwindow() {
if (smallwindow != null) {
windowmanager windowmanager = getwindowmanager();
windowmanager.removeview(smallwindow);
smallwindow = null;
public void setonclicklistener(floatwindowsmallview.onclicklistener listener) {
smallwindow.setonclicklistener(listener);
* 建立大懸浮窗
public void createbigwindow(context context) {
if (bigwindow == null) {
bigwindow = new floatwindowbigview(context);
windowmanager.addview(bigwindow, bigwindow.bigwindowparams);
* 将大懸浮窗從螢幕上移除
public void removebigwindow() {
if (bigwindow != null) {
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
private windowmanager getwindowmanager() {
if (mwindowmanager == null) {
mwindowmanager = (windowmanager) context
.getsystemservice(context.window_service);
return mwindowmanager;
還有個擷取螢幕寬高的幫助類。
package com.qust.demo;
* 螢幕幫助類
public class screenutils {
* 擷取螢幕寬度
@suppresswarnings("deprecation")
public static int getscreenwidth(context context) {
return ((windowmanager) context
.getsystemservice(context.window_service)).getdefaultdisplay()
.getwidth();
public static int getscreenheight(context context) {
.getheight();
完成這些,我們就可以直接用了。
import android.app.activity;
import android.os.bundle;
import android.view.keyevent;
import com.qust.floatwindow.floatwindowmanager;
import com.qust.floatwindow.floatwindowservice;
import com.qust.floatwindow.floatwindowsmallview.onclicklistener;
* 示例
* @classname: com.qust.demo.mainactivity
* @description:
* @date 2014-10-23 下午11:30:13
public class mainactivity extends activity {
private floatwindowmanager floatwindowmanager;
protected void oncreate(bundle savedinstancestate) {
super.oncreate(savedinstancestate);
setcontentview(r.layout.activity_main);
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);
* 顯示二級懸浮窗
public void showbig(view view) {
// 設定小懸浮窗的單擊事件
floatwindowmanager.setonclicklistener(new onclicklistener() {
public void click() {
floatwindowmanager.createbigwindow(context);
* 移除所有的懸浮窗
public void remove(view view) {
floatwindowmanager.removeall();
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.view.view.onclicklistener;
import android.view.windowmanager.layoutparams;
import android.widget.imageview;
import android.widget.imageview.scaletype;
private imageview img;
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參數
// 設定顯示的類型,type_phone指的是來電話的時候會被覆寫,其他時候會在最前端,顯示位置在statebar下面,其他更多的值請查閱文檔
// 設定顯示格式
// 設定對齊方式
// 設定寬高
// 添加到目前的視窗上
windowmanager.addview(img, params);
// 點選圖層之後,将圖層移除
img.setonclicklistener(new onclicklistener() {
public void onclick(view arg0) {
windowmanager.removeview(img);
github項目位址:https://github.com/zhaokaiqiang/teachmask