首先,我们需要知道,悬浮窗分为两种:Activity级别的悬浮窗,系统级别的悬浮窗
Activity级别的悬浮窗跟随所属Activity的生命周期而变化,而系统级别的悬浮窗则可以脱离Activity而存在。
由此可知,要实现360手机卫士那样的悬浮窗效果,就需要使用系统级别的悬浮窗
下面学习实现桌面悬浮窗效果的代码步骤:
Demo描述,悬浮窗为一个ImageView ,可以在桌面 ,任意应用,锁屏上方任意移动
1、配置清单文件AndroidManifest.xml 中 添加系统悬浮窗的权限
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
2、开始Activity代码的编写
先看成员变量:
private WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
private static WindowManager windowManager;
private static ImageView imageView;
onCreate()方法:
获取WindwoManager对象,该对象是系统级别的
windowManager = (WindowManager) getApplication().getSystemService(WINDOW_SERVICE);
使用WindowManager可以显示在其他应用最上层,甚至手机桌面最上层显示窗口。
3、添加一个UI空间,作为悬浮窗的内容 ,当然Demo是一个ImageView作为悬浮窗内容,实际项目中就需要用复杂View,ViewGroup来扩展功能了
//注意,悬浮窗只有一个,而当打开应用的时候才会产生悬浮窗,所以要判断悬浮窗是否已经存在,
if (imageView != null){
windowManager.removeView(imageView);
}
// 使用Application context 创建UI控件,避免Activity销毁导致上下文出现问题,因为现在的悬浮窗是系统级别的,不依赖与Activity存在
imageView = new ImageView(getApplicationContext());
imageView.setImageResource(R.mipmap.normal);
4、设置系统级别的悬浮窗的参数,保证悬浮窗悬在手机桌面上
lp.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
|WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
lp.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
|WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
//TYPE_SYSTEM_ALERT 系统提示,它总是出现在应用程序窗口之上
//TYPE_SYSTEM_OVERLAY 系统顶层窗口。显示在其他一切内容之上。此窗口不能获得输入焦点,否则影响锁屏
// FLAG_NOT_FOCUSABLE 悬浮窗口较小时,后面的应用图标由不可长按变为可长按,不设置这个flag的话,home页的划屏会有问题
// FLAG_NOT_TOUCH_MODAL不阻塞事件传递到后面的窗口
关于 WindowManager.LayoutParams 的详解 请参考:Android中WindowManager.LayoutParams类详解
5、悬浮窗默认显示的位置
lp.gravity = Gravity.LEFT|Gravity.TOP; //显示在屏幕左上角
6、悬浮窗相对5默认位置的位置差和悬浮窗宽高设置
//显示位置与指定位置的相对位置差
lp.x = 0;
lp.y = 0;
//悬浮窗的宽高
lp.width = WindowManager.LayoutParams.WRAP_CONTENT;
lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
7、设置悬浮窗背景透明
lp.format = PixelFormat.TRANSPARENT;
8、将悬浮窗添加到WindowManager对象中
windowManager.addView(imageView,lp);
9.设置悬浮窗的响应事件
这里为移动悬浮窗操作,可以自己扩展添加点击等响应事件
imageView.setOnTouchListener(new View.OnTouchListener() {
private float lastX; //上一次位置的X.Y坐标
private float lastY;
private float nowX; //当前移动位置的X.Y坐标
private float nowY;
private float tranX; //悬浮窗移动位置的相对值
private float tranY;
@Override
public boolean onTouch(View v, MotionEvent event) {
boolean ret = false;
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
// 获取按下时的X,Y坐标
lastX = event.getRawX();
lastY = event.getRawY();
ret = true;
break;
case MotionEvent.ACTION_MOVE:
// 获取移动时的X,Y坐标
nowX = event.getRawX();
nowY = event.getRawY();
// 计算XY坐标偏移量
tranX = nowX - lastX;
tranY = nowY - lastY;
// 移动悬浮窗
lp.x += tranX;
lp.y += tranY;
//更新悬浮窗位置
windowManager.updateViewLayout(imageView,lp);
//记录当前坐标作为下一次计算的上一次移动的位置坐标
lastX = nowX;
lastY = nowY;
break;
case MotionEvent.ACTION_UP:
break;
}
return ret;
}
});
10、扩展移除悬浮窗功能
11、效果图:
完整代码:
注意添加权限!!!
1 package com.xqx.window.app;
2
3 import android.app.Activity;
4 import android.graphics.PixelFormat;
5 import android.os.Bundle;
6 import android.view.*;
7 import android.widget.ImageView;
8
9 /**
10 * 系统级别悬浮窗,可以在手机桌面上显示的悬浮窗
11 */
12 public class FloatWindowActivity extends Activity {
13
14 private WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
15 private static WindowManager windowManager;
16 private static ImageView imageView;
17
18 @Override
19 protected void onCreate(Bundle savedInstanceState) {
20 super.onCreate(savedInstanceState);
21 setContentView(R.layout.activity_float_window);
22
23 // 1、获取系统级别的WindowManager
24 windowManager = (WindowManager) getApplication().getSystemService(WINDOW_SERVICE);
25
26 // 判断UI控件是否存在,存在则移除,确保开启任意次应用都只有一个悬浮窗
27 if (imageView != null){
28 windowManager.removeView(imageView);
29 }
30 // 2、使用Application context 创建UI控件,避免Activity销毁导致上下文出现问题
31 imageView = new ImageView(getApplicationContext());
32 imageView.setImageResource(R.mipmap.normal);
33
34
35 // 3、设置系统级别的悬浮窗的参数,保证悬浮窗悬在手机桌面上
36 // 系统级别需要指定type 属性
37 // TYPE_SYSTEM_ALERT 允许接收事件
38 // TYPE_SYSTEM_OVERLAY 悬浮在系统上
39 // 注意清单文件添加权限
40
41 //系统提示。它总是出现在应用程序窗口之上。
42 lp.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
43 |WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
44
45 // FLAG_NOT_TOUCH_MODAL不阻塞事件传递到后面的窗口
46 // FLAG_NOT_FOCUSABLE 悬浮窗口较小时,后面的应用图标由不可长按变为可长按,不设置这个flag的话,home页的划屏会有问题
47 lp.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
48 |WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
49
50 //悬浮窗默认显示的位置
51 lp.gravity = Gravity.LEFT|Gravity.TOP;
52 //显示位置与指定位置的相对位置差
53 lp.x = 0;
54 lp.y = 0;
55 //悬浮窗的宽高
56 lp.width = WindowManager.LayoutParams.WRAP_CONTENT;
57 lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
58
59 lp.format = PixelFormat.TRANSPARENT;
60 windowManager.addView(imageView,lp);
61
62 //设置悬浮窗监听事件
63 imageView.setOnTouchListener(new View.OnTouchListener() {
64 private float lastX; //上一次位置的X.Y坐标
65 private float lastY;
66 private float nowX; //当前移动位置的X.Y坐标
67 private float nowY;
68 private float tranX; //悬浮窗移动位置的相对值
69 private float tranY;
70
71 @Override
72 public boolean onTouch(View v, MotionEvent event) {
73 boolean ret = false;
74 switch (event.getAction()){
75 case MotionEvent.ACTION_DOWN:
76 // 获取按下时的X,Y坐标
77 lastX = event.getRawX();
78 lastY = event.getRawY();
79 ret = true;
80 break;
81 case MotionEvent.ACTION_MOVE:
82 // 获取移动时的X,Y坐标
83 nowX = event.getRawX();
84 nowY = event.getRawY();
85 // 计算XY坐标偏移量
86 tranX = nowX - lastX;
87 tranY = nowY - lastY;
88 // 移动悬浮窗
89 lp.x += tranX;
90 lp.y += tranY;
91 //更新悬浮窗位置
92 windowManager.updateViewLayout(imageView,lp);
93 //记录当前坐标作为下一次计算的上一次移动的位置坐标
94 lastX = nowX;
95 lastY = nowY;
96 break;
97 case MotionEvent.ACTION_UP:
98 break;
99 }
100 return ret;
101 }
102 });
103 }
104
105 }
FloatWindowActivity.java