写在前面:本文仅个人开发时遇到的问题及个人解决办法的记录。
国内各个手机厂商对ROM魔改的比较严重,还没有做兼容性测试,所以碰到沙雕的机子的时候,请再去寻找适配方法
最近项目开发中,需要实现一个悬浮窗,说一下实现方式,做一下记录。
首先,简单的藐视就是:实现悬浮窗是用的WindowManager。利用context.getSystemService(Context.WINDOW_SERVICE)获取到WindowManger对象,调用里面的windowManager.addView(floatView, layoutParams)方法。floatView就是要展示的悬浮窗的View layoutParams是一些参数设置。
下面介绍一下实现步骤(懒得看的可以下滑到最下面看代码):
申请权限,这是必须的一步。
在你的清单文件中添加如下权限代码
然后在代码里面使用下面的方法判断是否有悬浮窗权限:
Settings.canDrawOverlays(context)
如果没有权限,跳转到权限开启页面,打开悬浮窗权限。确切的说是跳转到开启 允许显示在其他应用上层 的权限
startActivityForResult(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())), 10086);
一切准备工作完成后开始我们的正式任务啊!!!!!!!!!!
第一步,获取到WindowManager对象;
(WindowManager) context.getSystemService(Context.WINDOW_SERVICE)
第二步,创建一个WindowManager.LayoutParams对象,用于设置一些悬浮view的参数
// 设置LayoutParam
layoutParams =new WindowManager.LayoutParams();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
}else {
layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;
}
//悬浮窗弹出的位置
layoutParams.gravity = Gravity.LEFT|Gravity.CENTER;
//注意:这一个flags的设置,之前搜索很多实现都没有设置这个,出现的情况就是在悬浮的view出现后 点击窗口其它地方没有反应,是因为不设置这个参数,悬浮窗弹出来后就占据整个窗口的焦点。
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
layoutParams.format = PixelFormat.RGBA_8888;
layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
layoutParams.x =0;
layoutParams.y =0;
第三步 获取到需要悬浮显示的view对象
LayoutInflater layoutInflater = LayoutInflater.from(context);
View floatView = layoutInflater.inflate(R.layout.floating_view, null);
第四步,将悬浮view和layoutParams调用windowmanager的方法addView显示出来
// 将悬浮窗控件添加到WindowManager
windowManager.addView(floatView, layoutParams);
如果你需要对悬浮窗里不同view设置一些点击事件
我们在上面第三步里面获取到了悬浮窗的View对象,可以使用view.findviewById方法根据id拿到各个view,针对不同的view设置不同的事件。
对悬浮窗添加拖动事件
同样上面第三步我们获取到的view对象,设置触摸事件
//设置触摸事件
floatView.setOnTouchListener(new FloatingOnTouchListener());
//因为我的悬浮窗需求比较简单,所以没有那么多复杂的操作。只是拖动后,让悬浮view靠边停着。
private class FloatingOnTouchListenerimplements View.OnTouchListener {
private int x;
private int y;
//标记是否执行move事件 如果执行了move事件 在up事件的时候判断悬浮窗的位置让悬浮窗处于屏幕左边或者右边
private boolean isScroll;
//标记悬浮窗口是否移动了 防止设置点击事件的时候 窗口移动松手后触发点击事件
private boolean isMoved;
//事件开始时和结束的时候 X和Y坐标位置
private int startX;
private int startY;
@Override
public boolean onTouch(View view, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
x = (int) event.getRawX();
y = (int) event.getRawY();
isMoved =false;
isScroll =false;
startX = (int) event.getRawX();
startY = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
int nowX = (int) event.getRawX();
int nowY = (int) event.getRawY();
int movedX = nowX -x;
int movedY = nowY -y;
x = nowX;
y = nowY;
layoutParams.x =layoutParams.x + movedX;
layoutParams.y =layoutParams.y + movedY;
// 更新悬浮窗控件布局
windowManager.updateViewLayout(view, layoutParams);
isScroll =true;
break;
case MotionEvent.ACTION_UP:
int stopX = (int) event.getRawX();
int stopY = (int) event.getRawY();
if (Math.abs(startX - stopX) >=1 || Math.abs(startY - stopY) >=1) {
isMoved =true;
}
if (isScroll){
autoView(view);
}
break;
}
return isMoved;
}
//悬浮窗view自动停靠在屏幕左边或者右边
private void autoView(View view) {
// 得到view在屏幕中的位置
int[] location =new int[2];
view.getLocationOnScreen(location);
if (location[0]
layoutParams.x =0;
}else {
layoutParams.x = DensityUtils.getScreenSize(false).x - view.getWidth();
}
windowManager.updateViewLayout(view, layoutParams);
}
}
最后,放出来一个简单处理的类,有需求的可以根据需求自己修改
代码:
public class FloatingWindowUtils {
private Contextcontext;
private int screenWidth;
private WindowManager.LayoutParamslayoutParams;
private WindowManagerwindowManager;
private ViewfloatView;
private FloatingWindowUtils() {
}
private static class InstanceHolder {
@SuppressLint("StaticFieldLeak")
private static final FloatingWindowUtilssInstance =new FloatingWindowUtils();
private InstanceHolder() {
}
}
public static FloatingWindowUtilsgetInstance() {
return FloatingWindowUtils.InstanceHolder.sInstance;
}
public void init(Context context) {
this.context = context;
if (windowManager !=null)return;
// 获取WindowManager服务
windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
//获取屏宽
screenWidth = DensityUtils.getScreenSize(false).x;
}
/**
* 展示悬浮窗
* @param layoutId 悬浮窗布局文件id
*/
@SuppressLint("RtlHardcoded")
public void showFloatingWindow(@LayoutRes int layoutId){
// 新建悬浮窗控件
LayoutInflater layoutInflater = LayoutInflater.from(context);
// View floatView = layoutInflater.inflate(R.layout.floating_view, null);
View floatView = layoutInflater.inflate(layoutId, null);
if (floatView ==null){
throw new NullPointerException("悬浮窗view为null 检查布局文件是否可用");
}
showFloatingWindow(floatView);
}
/**
* 展示悬浮窗
* @param floatView 悬浮窗view
*/
@SuppressLint("RtlHardcoded")
public void showFloatingWindow(@NonNull View floatView){
if (this.floatView !=null)return;//有悬浮窗在显示 不再显示新的悬浮窗
// 新建悬浮窗控件
if (floatView ==null){
throw new NullPointerException("悬浮窗view为null 确认view不为null");
}
this.floatView = floatView;
//设置触摸事件
floatView.setOnTouchListener(new FloatingOnTouchListener());
//悬浮窗设置点击事件
floatView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(context, "点击了悬浮窗", Toast.LENGTH_SHORT).show();
}
});
// 设置LayoutParam
layoutParams =new WindowManager.LayoutParams();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
}
layoutParams.gravity = Gravity.LEFT|Gravity.CENTER;
//设置flags 不然悬浮窗出来后整个屏幕都无法获取焦点,
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
layoutParams.format = PixelFormat.RGBA_8888;
layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
layoutParams.x =0;
layoutParams.y =0;
// 将悬浮窗控件添加到WindowManager
windowManager.addView(floatView, layoutParams);
}
/**
* 隐藏悬浮窗
*/
public void hideFloatWindow(){
if (floatView !=null){
windowManager.removeViewImmediate(floatView);
floatView =null;
}
}
public void unInit() {
hideFloatWindow();
this.context =null;
// 获取WindowManager服务
windowManager =null;
}
private class FloatingOnTouchListenerimplements View.OnTouchListener {
private int x;
private int y;
//标记是否执行move事件 如果执行了move事件 在up事件的时候判断悬浮窗的位置让悬浮窗处于屏幕左边或者右边
private boolean isScroll;
//标记悬浮窗口是否移动了 防止设置点击事件的时候 窗口移动松手后触发点击事件
private boolean isMoved;
//事件开始时和结束的时候 X和Y坐标位置
private int startX;
private int startY;
@Override
public boolean onTouch(View view, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
x = (int) event.getRawX();
y = (int) event.getRawY();
isMoved =false;
isScroll =false;
startX = (int) event.getRawX();
startY = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
int nowX = (int) event.getRawX();
int nowY = (int) event.getRawY();
int movedX = nowX -x;
int movedY = nowY -y;
x = nowX;
y = nowY;
layoutParams.x =layoutParams.x + movedX;
layoutParams.y =layoutParams.y + movedY;
// 更新悬浮窗控件布局
windowManager.updateViewLayout(view, layoutParams);
isScroll =true;
break;
case MotionEvent.ACTION_UP:
int stopX = (int) event.getRawX();
int stopY = (int) event.getRawY();
if (Math.abs(startX - stopX) >=1 || Math.abs(startY - stopY) >=1) {
isMoved =true;
}
if (isScroll){
autoView(view);
}
break;
}
return isMoved;
}
//悬浮窗view自动停靠在屏幕左边或者右边
private void autoView(View view) {
// 得到view在屏幕中的位置
int[] location =new int[2];
view.getLocationOnScreen(location);
if (location[0]
layoutParams.x =0;
}else {
layoutParams.x = DensityUtils.getScreenSize(false).x - view.getWidth();
}
windowManager.updateViewLayout(view, layoutParams);
}
}
public ViewgetFloatView(){
return floatView;
}
}
最后给大家放一个介绍比较全面的帖子。
https://www.jianshu.com/p/3246f7289704