美文网首页
Android-悬浮窗口

Android-悬浮窗口

作者: 喂_balabala | 来源:发表于2024-06-26 17:43 被阅读0次

    在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 {
                // 权限请求被拒绝,根据情况处理,如提示用户权限重要性或提供备选方案
            }
        }
    }
    

    相关文章

      网友评论

          本文标题:Android-悬浮窗口

          本文链接:https://www.haomeiwen.com/subject/cuescjtx.html