Android-悬浮窗口
2024-06-26 本文已影响0人
喂_balabala
在Android系统中,如果应用需要弹出一个悬浮窗口,就需要申请一项特殊权限
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
在Android O之前的系统中申请了该权限后,再给对应的window设置
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.type = WindowManager.LayoutParams.TYPE_PHONE;
悬浮窗口就可以显示出来。
但是在Android O的系统中,google规定申请
android.permission.SYSTEM_ALERT_WINDOW
权限的应用需要给悬浮窗口设置如下type:
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
悬浮窗口才能显示出来,“TYPE_APPLICATION_OVERLAY”是重点。
如果不设置该TYPE,应用会Crash,报错如下(后面的2002表示设置的type为TYPE_PHONE):
AndroidRuntime: android.view.WindowManager$BadTokenException: Unable to add window android.view.ViewRootImpl$W@c8d1f1a -- permission denied for window type 2002
另外说一下:申请
android.permission.SYSTEM_ALERT_WINDOW
权限不能使用 requestPermissions 方法。
可以使用下面的方法:
Intent intent = new Intent(android.provider.Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, 100);
完整代码
1、添加权限
在AndroidManifest.xml中添加悬浮窗所需的权限:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
2、请求权限(针对Android 6.0及以上)
- 对于Android 6.0(API级别23)及以上的设备,需要运行时请求SYSTEM_ALERT_WINDOW权限。示例:
private static final int REQUEST_CODE_DRAW_OVER_OTHER_APPS_PERMISSION = 200;
// 在Activity或Fragment中请求权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, REQUEST_CODE_DRAW_OVER_OTHER_APPS_PERMISSION);
}
- 处理权限请求的结果
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE_DRAW_OVER_OTHER_APPS_PERMISSION) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) {
// 权限被拒绝
Toast.makeText(this, "Permission denied", Toast.LENGTH_SHORT).show();
} else {
// 权限已授予,可以显示悬浮窗
showFloatingWindow();
}
}
}
3、实现悬浮窗
- 定义悬浮窗的布局和显示逻辑:
public void showFloatingWindow() {
// 布局参数
WindowManager.LayoutParams params;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
} else {
params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_PHONE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
}
// 设置悬浮窗的位置
params.gravity = Gravity.TOP | Gravity.START; // 例如,设置在屏幕左上角
params.x = 100; // x坐标
params.y = 100; // y坐标
// 创建浮动窗口视图
LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
View floatingView = inflater.inflate(R.layout.floating_window_layout, null);
// 获取WindowManager
WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
try {
// 添加悬浮窗到WindowManager
windowManager.addView(floatingView, params);
} catch (Exception e) {
e.printStackTrace();
}
}
- 浮窗拖动
// 设置触摸监听器以实现拖动
floatingLayout.setOnTouchListener(new View.OnTouchListener() {
private int initialX;
private int initialY;
private float initialTouchX;
private float initialTouchY;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 记录按下时的坐标
initialX = params.x;
initialY = params.y;
initialTouchX = event.getRawX();
initialTouchY = event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
// 计算移动的距离,并更新悬浮窗位置
params.x = initialX + (int) (event.getRawX() - initialTouchX);
params.y = initialY + (int) (event.getRawY() - initialTouchY);
windowManager.updateViewLayout(floatingView, params);
break;
case MotionEvent.ACTION_UP:
// 手指抬起,可以根据需要做一些操作,这里直接返回
break;
default:
return false;
}
return true; // 消费掉这个事件,防止它被其他视图消费
}
});
- 关闭浮窗
public void removeFloatingWindow() {
// 获取WindowManager实例
WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
// 确保floatingView是之前添加到WindowManager的那个View实例
// 这里假设floatingView是在showFloatingWindow()方法中创建并添加的
View floatingView = ...; // 你需要确保这指向正确的View实例
if (floatingView != null && windowManager != null) {
// 移除悬浮窗视图
windowManager.removeView(floatingView);
}
}
悬浮窗开启关闭时前后台切换功能
app退到后台
方案一、启动Home页
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
startActivity(intent);
如果Launcher和Home不是同一个,就不能这么用。比如说机顶盒Launcher,启动第三方app都是从这里打开的。然后这里如果执行了上述代码,启动了Home,那就跳转到了Android系统的Home,就不是退到后台的效果了。
方案二、执行Activity#moveTaskToBack()
moveTaskToBack(false);
- 关于
moveTaskToBack
的参数
/**
* Move the task containing this activity to the back of the activity
* stack. The activity's order within the task is unchanged.
*
* @param nonRoot If false then this only works if the activity is the root
* of a task; if true it will work for any activity in
* a task.
*
* @return If the task was moved (or it was already at the
* back) true is returned, else false.
*/
public boolean moveTaskToBack(boolean nonRoot) {}
-
nonRoot=false
时,只有当当前Activity为root activity根Activity时才会把当前task退回到后台。notRoot=true
时,不管当前是否是root activity都会把当前task退回到后台。
app切到前台
方案一、使用Intent启动需要切到前台的Activity
Intent intent = new Intent(this, MainActivity.class);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.setAction(Intent.ACTION_MAIN);
//intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
startActivity(intent);
- 这里的MainActivity.class就是需要启动的Activity
方案二、通过ActivityMananger把task切到前台
ActivityManager am = (ActivityManager) this.getSystemService(Context.ACTIVITY_SERVICE);
am.moveTaskToFront(getTaskId(), ActivityManager.MOVE_TASK_WITH_HOME);
- 报错信息
java.lang.SecurityException: Permission Denial: moveTaskToFront() from pid=20744, uid=10516 requires android.permission.REORDER_TASKS
- 这个方法需要权限
android.permission.REORDER_TASKS
- 可能影响Google上架
权限申请
<uses-permission android:name="android.permission.REORDER_TASKS"/>
// 检查是否已经有了权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.REORDER_TASKS)
!= PackageManager.PERMISSION_GRANTED) {
// 如果应用之前请求过此权限但用户拒绝了请求,此方法将返回 true。
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.REORDER_TASKS)) {
// 显示解释为什么需要这个权限的对话框,然后再次请求权限
} else {
// 没有权限,直接请求权限
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.REORDER_TASKS},
MY_PERMISSIONS_REQUEST_REORDER_TASKS);
}
} else {
// 已经有权限,可以执行相应操作
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == MY_PERMISSIONS_REQUEST_REORDER_TASKS) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 权限被用户同意,可以执行moveTaskToFront操作
} else {
// 权限请求被拒绝,根据情况处理,如提示用户权限重要性或提供备选方案
}
}
}