Android-悬浮窗口

在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 {
            // 权限请求被拒绝,根据情况处理,如提示用户权限重要性或提供备选方案
        }
    }
}
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值