悬浮窗是可以在不同软件最上面,默认的效果,不需要过多设置,通常放在服务里面,因为需要长时间存在
思路
写一个悬浮窗大概是以下几个步骤
1、写一个服务,因为悬浮窗长期存在,不依赖于界面,所有最好写在服务里面
2、在服务需要获取到WindowManager这个类,用来加载一个悬浮窗的布局和一些列点击事件
3、启动服务,悬浮窗就可以启动
难点
1、悬浮窗的穿透点击
当悬浮窗悬浮的时候,理想状态,应该是悬浮窗里面的按钮和悬浮窗底层点击触摸事件不冲突。
2、需要注意,悬浮窗的可能会出现黑色背景,需要加params.format = PixelFormat.RGBA_8888;
代码逻辑
1、写一个服务
public class BackService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
2、在服务里面写一个一个方法,去创建一个一个悬浮窗的样式
/**
* 初始化一个悬浮窗
*/
private void initWindow() {
// 获取WindowManager
mSystemService = (WindowManager) getSystemService(WINDOW_SERVICE);
// 创建布局参数
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
//这里需要进行不同的设置
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
params.type = WindowManager.LayoutParams.TYPE_PHONE;
}
//设置透明度
params.alpha = 1.0f;
//设置内部视图对齐方式
params.gravity = Gravity.RIGHT | Gravity.BOTTOM;
//窗口的右上角角坐标
params.x = 20;
params.y = 20;
//是指定窗口的像素格式为 RGBA_8888。
//使用 RGBA_8888 像素格式的窗口可以在保持高质量图像的同时实现透明度效果。
params.format = PixelFormat.RGBA_8888;
//设置窗口的宽高,这里为自动
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
//这段非常重要,是后续是否穿透点击的关键
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE //表示悬浮窗口不需要获取焦点,这样用户点击悬浮窗口以外的区域,就不需要关闭悬浮窗口。
|WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;//表示悬浮窗口不会阻塞事件传递,即用户点击悬浮窗口以外的区域时,事件会传递给后面的窗口处理。
//这里的引入布局文件的方式,也可以动态添加控件
mView = View.inflate(getApplicationContext(), R.layout.item_back, null);
Button btnBack = mView.findViewById(R.id.btn_back);
mSystemService.addView(mView,params);
}
ps:此处要注意一下,当服务销毁的时候,需要记得,把布局的view给removeView
@Override
public void onDestroy() {
super.onDestroy();
if (mSystemService != null && mView != null){
mSystemService.removeView(mView);
}
}
3、启动服务,悬浮窗就可以启动
startService(new Intent(context, BackService.class));
注意
1、需要注意在悬浮窗的点击中,需要效果是悬浮窗里面的按钮和悬浮窗底层点击触摸事件不冲突,关键代码是这儿
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE //表示悬浮窗口不需要获取焦点,这样用户点击悬浮窗口以外的区域,就不需要关闭悬浮窗口。
|WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;//表示悬浮窗口不会阻塞事件传递,即用户点击悬浮窗口以外的区域时,事件会传递给后面的窗口处理。
2、悬浮窗如果出现黑色背景,必须加这儿
//是指定窗口的像素格式为 RGBA_8888。
//使用 RGBA_8888 像素格式的窗口可以在保持高质量图像的同时实现透明度效果。
params.format = PixelFormat.RGBA_8888;
3、如果要隐藏当前的avtivity,只有悬浮窗,可以通过moveTaskToBack(true);
设置
当activity的启动模式是singleInstance的时候,在当前的activity直接调用moveTaskToBack(true),即可将activity 退到后台
参数说明:
参数为false——代表只有当前activity是task根,指应用启动的第一个activity时,才有效;
参数为true——则忽略这个限制,任何activity都可以有效。
设置avtivity启动模式在AndroidManifest里面
<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:exported="true"
android:launchMode="singleInstance">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
整体代码
服务里面,启动服务很简单
public class BackService extends Service {
private View mView;
private WindowManager mSystemService;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
initWindow();
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
if (mSystemService != null && mView != null){
mSystemService.removeView(mView);
}
}
/**
* 初始化一个悬浮窗
*/
private void initWindow() {
// 获取WindowManager
mSystemService = (WindowManager) getSystemService(WINDOW_SERVICE);
// 创建布局参数
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
params.type = WindowManager.LayoutParams.TYPE_PHONE;
}
//设置透明度
params.alpha = 1.0f;
//设置内部视图对齐方式
params.gravity = Gravity.RIGHT | Gravity.BOTTOM;
//窗口的左上角坐标
params.x = 20;
params.y = 20;
//是指定窗口的像素格式为 RGBA_8888。
//使用 RGBA_8888 像素格式的窗口可以在保持高质量图像的同时实现透明度效果。
params.format = PixelFormat.RGBA_8888;
//设置窗口的宽高,这里为自动
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE //表示悬浮窗口不需要获取焦点,这样用户点击悬浮窗口以外的区域,就不需要关闭悬浮窗口。
|WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;//表示悬浮窗口不会阻塞事件传递,即用户点击悬浮窗口以外的区域时,事件会传递给后面的窗口处理。
mView = View.inflate(getApplicationContext(), R.layout.item_back, null);
Button btnBack = mView.findViewById(R.id.btn_back);
btnBack.setOnClickListener(view1 -> {
ToastUtils.showShort("点击了");//此处是点击逻辑,可以自己完成
});
mSystemService.addView(mView,params);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}