悬浮窗是可以在不同软件最上面,默认的效果,不需要过多设置,通常放在服务里面,因为需要长时间存在
思路
写一个悬浮窗大概是以下几个步骤
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;
}
}
效果图
最后
如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。
如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。
全套视频资料:
一、面试合集
二、源码解析合集
三、开源框架合集
欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓