这种创建悬浮view的前提是获取到了悬浮的权限
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
思路: 1,获取WindowManager
2,使用LayoutInflater新建悬浮的View
3,为悬浮的View设置LayoutParam(包括默认的相对屏幕的对齐方式,让view获取焦点等)
4,为悬浮的View设置触摸的监听,重写OnTouch(View view, MotionEvent event),根据手势处理view移动停止时的位置(根据当前view停留的位置坐标,结合屏幕宽度,确定贴边是左还是右)
在展示悬浮View时要动态判断是否有悬浮权限:
/**
* Created by Lyq
* on 2021-09-17
*/
public class PermissionUtils {
/***
* 检查悬浮窗开启权限
* @param context
* @return
*/
public static boolean checkFloatPermission(Context context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT)//19 4.4
return true;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {//23 6.0
try {
Class cls = Class.forName("android.content.Context");
Field declaredField = cls.getDeclaredField("APP_OPS_SERVICE");
declaredField.setAccessible(true);
Object obj = declaredField.get(cls);
if (!(obj instanceof String)) {
return false;
}
String str2 = (String) obj;
obj = cls.getMethod("getSystemService", String.class).invoke(context, str2);
cls = Class.forName("android.app.AppOpsManager");
Field declaredField2 = cls.getDeclaredField("MODE_ALLOWED");
declaredField2.setAccessible(true);
Method checkOp = cls.getMethod("checkOp", Integer.TYPE, Integer.TYPE, String.class);
int result = (Integer) checkOp.invoke(obj, 24, Binder.getCallingUid(), context.getPackageName());
return result == declaredField2.getInt(cls);
} catch (Exception e) {
return false;
}
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {//26 8.0
AppOpsManager appOpsMgr = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
if (appOpsMgr == null)
return false;
int mode = appOpsMgr.checkOpNoThrow("android:system_alert_window", android.os.Process.myUid(), context
.getPackageName());
return Settings.canDrawOverlays(context) || mode == AppOpsManager.MODE_ALLOWED || mode == AppOpsManager.MODE_IGNORED;
} else {
return Settings.canDrawOverlays(context);
}
}
}
}
悬浮View的具体实现(封的工具类):
/**
* Created by Lyq
* on 2021-09-17
*/
public class FloatingViewUtils {
private Context context;
private int screenWidth;
private WindowManager.LayoutParams layoutParams;
private WindowManager windowManager;
private View floatView;
private FloatingViewUtils() {
}
private static class InstanceHolder {
@SuppressLint("StaticFieldLeak")
private static final FloatingViewUtils sInstance = new FloatingViewUtils();
private InstanceHolder() {
}
}
public static FloatingViewUtils getInstance() {
return FloatingViewUtils.InstanceHolder.sInstance;
}
public void init(Context context) {
this.context = context;
if (windowManager != null) return;
//获取WindowManager服务
windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
//获取屏宽
screenWidth = getScreenWidth(MyApplication.getInstance());
}
/**
* 展示悬浮窗
* @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();
unInit();
}
});
// 设置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);
}
/**
* 移除悬浮View
*/
public void unInit() {
hideFloatWindow();
this.context = null;
// 获取WindowManager服务
windowManager = null;
}
/**
* 隐藏悬浮窗
* ? ?
*/
public void hideFloatWindow() {
if (floatView != null) {
windowManager.removeViewImmediate(floatView);
floatView = null;
}
}
private class FloatingOnTouchListener implements 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);
//判断view位置是否在屏幕中线的左侧,是的话贴屏幕左边,否则贴屏幕右边
if (location[0] < (getScreenWidth(MyApplication.getInstance()))/2) {
layoutParams.x = 0;
} else {
layoutParams.x = getScreenWidth(MyApplication.getInstance()) - view.getWidth();
}
windowManager.updateViewLayout(view, layoutParams);
}
}
public View getFloatView() {
return floatView;
}
/**
* 获取屏幕宽度
* @param context
* @return
*/
public int getScreenWidth(Context context) {
WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
return manager.getDefaultDisplay().getWidth();
}
}
具体的使用:
class MainActivity : AppCompatActivity() {
private val FloatRequestCode = 1002
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val test_button = findViewById<Button>(R.id.test_button)
test_button.setOnClickListener {
val checkFloatPermission = PermissionUtils.checkFloatPermission(this)
if(!checkFloatPermission){
requestFloatPermission(this,FloatRequestCode)
}else{
Toast.makeText(this,"有悬浮权限",Toast.LENGTH_SHORT).show()
val floatingWindowUtils = FloatingViewUtils.getInstance()
floatingWindowUtils.init(this)
floatingWindowUtils.showFloatingWindow(R.layout.layout_floatview)
}
}
}
/**
* 悬浮窗开启权限
* @param context
* @param requestCode
*/
fun requestFloatPermission(context: Activity, requestCode: Int) {
val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION)
intent.data = Uri.parse("package:" + context.packageName)
context.startActivityForResult(intent, requestCode)
}
/**
* 设置中开启后再次校验,来展示悬浮View
*/
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == FloatRequestCode) {
val handler = Handler()
handler.postDelayed(Runnable {
if (!checkFloatPermission(this)) {
requestFloatPermission(this, FloatRequestCode)
}
}, 1000)
}
}
}
|