首先,我们需要知道,悬浮窗分为两种:Activity级别的悬浮窗,系统级别的悬浮窗

Activity级别的悬浮窗跟随所属Activity的生命周期而变化,而系统级别的悬浮窗则可以脱离Activity而存在。

 

由此可知,要实现360手机卫士那样的悬浮窗效果,就需要使用系统级别的悬浮窗

 

下面学习实现桌面悬浮窗效果的代码步骤:

Demo描述,悬浮窗为一个ImageView ,可以在桌面 ,任意应用,锁屏上方任意移动

 

1、配置清单文件AndroidManifest.xml 中 添加系统悬浮窗的权限



<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />



 

2、开始Activity代码的编写

 先看成员变量:



private WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
    private static WindowManager windowManager;
    private static ImageView imageView;



 onCreate()方法:

获取WindwoManager对象,该对象是系统级别的



windowManager = (WindowManager) getApplication().getSystemService(WINDOW_SERVICE);



使用WindowManager可以显示在其他应用最上层,甚至手机桌面最上层显示窗口。

 

3、添加一个UI空间,作为悬浮窗的内容 ,当然Demo是一个ImageView作为悬浮窗内容,实际项目中就需要用复杂View,ViewGroup来扩展功能了



//注意,悬浮窗只有一个,而当打开应用的时候才会产生悬浮窗,所以要判断悬浮窗是否已经存在,
     if (imageView != null){
            windowManager.removeView(imageView);
        }
        // 使用Application context 创建UI控件,避免Activity销毁导致上下文出现问题,因为现在的悬浮窗是系统级别的,不依赖与Activity存在
           imageView = new ImageView(getApplicationContext());
        imageView.setImageResource(R.mipmap.normal);



 

4、设置系统级别的悬浮窗的参数,保证悬浮窗悬在手机桌面上



lp.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
                  |WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;

        lp.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                  |WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;



//TYPE_SYSTEM_ALERT  系统提示,它总是出现在应用程序窗口之上
//TYPE_SYSTEM_OVERLAY   系统顶层窗口。显示在其他一切内容之上。此窗口不能获得输入焦点,否则影响锁屏
// FLAG_NOT_FOCUSABLE 悬浮窗口较小时,后面的应用图标由不可长按变为可长按,不设置这个flag的话,home页的划屏会有问题
// FLAG_NOT_TOUCH_MODAL不阻塞事件传递到后面的窗口
关于 WindowManager.LayoutParams 的详解 请参考:Android中WindowManager.LayoutParams类详解
5、悬浮窗默认显示的位置



lp.gravity = Gravity.LEFT|Gravity.TOP;  //显示在屏幕左上角



 

6、悬浮窗相对5默认位置的位置差和悬浮窗宽高设置



//显示位置与指定位置的相对位置差
        lp.x = 0;
        lp.y = 0;
        //悬浮窗的宽高
        lp.width = WindowManager.LayoutParams.WRAP_CONTENT;
        lp.height = WindowManager.LayoutParams.WRAP_CONTENT;



 

7、设置悬浮窗背景透明



lp.format = PixelFormat.TRANSPARENT;



 

8、将悬浮窗添加到WindowManager对象中



windowManager.addView(imageView,lp);



 

9.设置悬浮窗的响应事件

 这里为移动悬浮窗操作,可以自己扩展添加点击等响应事件



imageView.setOnTouchListener(new View.OnTouchListener() {
            private float lastX; //上一次位置的X.Y坐标
            private float lastY;
            private float nowX;  //当前移动位置的X.Y坐标
            private float nowY;
            private float tranX; //悬浮窗移动位置的相对值
            private float tranY;

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                boolean ret = false;
                switch (event.getAction()){
                    case MotionEvent.ACTION_DOWN:
                        // 获取按下时的X,Y坐标
                        lastX = event.getRawX();
                        lastY = event.getRawY();
                        ret = true;
                        break;
                    case MotionEvent.ACTION_MOVE:
                        // 获取移动时的X,Y坐标
                        nowX = event.getRawX();
                        nowY = event.getRawY();
                        // 计算XY坐标偏移量
                        tranX = nowX - lastX;
                        tranY = nowY - lastY;
                        // 移动悬浮窗
                        lp.x += tranX;
                        lp.y += tranY;
                        //更新悬浮窗位置
                        windowManager.updateViewLayout(imageView,lp);
                        //记录当前坐标作为下一次计算的上一次移动的位置坐标
                        lastX = nowX;
                        lastY = nowY;
                        break;
                    case MotionEvent.ACTION_UP:
                        break;
                }
                return ret;
            }
        });



10、扩展移除悬浮窗功能

11、效果图:


完整代码:
注意添加权限!!!




android 拖动至此松手即可关闭浮窗_android

android 拖动至此松手即可关闭浮窗_android_02

1 package com.xqx.window.app;
  2 
  3 import android.app.Activity;
  4 import android.graphics.PixelFormat;
  5 import android.os.Bundle;
  6 import android.view.*;
  7 import android.widget.ImageView;
  8 
  9 /**
 10  * 系统级别悬浮窗,可以在手机桌面上显示的悬浮窗
 11  */
 12 public class FloatWindowActivity extends Activity {
 13 
 14     private WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
 15     private static WindowManager windowManager;
 16     private static ImageView imageView;
 17 
 18     @Override
 19     protected void onCreate(Bundle savedInstanceState) {
 20         super.onCreate(savedInstanceState);
 21         setContentView(R.layout.activity_float_window);
 22 
 23         // 1、获取系统级别的WindowManager
 24         windowManager = (WindowManager) getApplication().getSystemService(WINDOW_SERVICE);
 25 
 26         // 判断UI控件是否存在,存在则移除,确保开启任意次应用都只有一个悬浮窗
 27         if (imageView != null){
 28             windowManager.removeView(imageView);
 29         }
 30         // 2、使用Application context 创建UI控件,避免Activity销毁导致上下文出现问题
 31         imageView = new ImageView(getApplicationContext());
 32         imageView.setImageResource(R.mipmap.normal);
 33 
 34 
 35         // 3、设置系统级别的悬浮窗的参数,保证悬浮窗悬在手机桌面上
 36         // 系统级别需要指定type 属性
 37         // TYPE_SYSTEM_ALERT 允许接收事件
 38         // TYPE_SYSTEM_OVERLAY 悬浮在系统上
 39         // 注意清单文件添加权限
 40 
 41         //系统提示。它总是出现在应用程序窗口之上。
 42         lp.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
 43                   |WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
 44 
 45         // FLAG_NOT_TOUCH_MODAL不阻塞事件传递到后面的窗口
 46         // FLAG_NOT_FOCUSABLE 悬浮窗口较小时,后面的应用图标由不可长按变为可长按,不设置这个flag的话,home页的划屏会有问题
 47         lp.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
 48                   |WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
 49 
 50         //悬浮窗默认显示的位置
 51         lp.gravity = Gravity.LEFT|Gravity.TOP;
 52         //显示位置与指定位置的相对位置差
 53         lp.x = 0;
 54         lp.y = 0;
 55         //悬浮窗的宽高
 56         lp.width = WindowManager.LayoutParams.WRAP_CONTENT;
 57         lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
 58 
 59         lp.format = PixelFormat.TRANSPARENT;
 60         windowManager.addView(imageView,lp);
 61 
 62         //设置悬浮窗监听事件
 63         imageView.setOnTouchListener(new View.OnTouchListener() {
 64             private float lastX; //上一次位置的X.Y坐标
 65             private float lastY;
 66             private float nowX;  //当前移动位置的X.Y坐标
 67             private float nowY;
 68             private float tranX; //悬浮窗移动位置的相对值
 69             private float tranY;
 70 
 71             @Override
 72             public boolean onTouch(View v, MotionEvent event) {
 73                 boolean ret = false;
 74                 switch (event.getAction()){
 75                     case MotionEvent.ACTION_DOWN:
 76                         // 获取按下时的X,Y坐标
 77                         lastX = event.getRawX();
 78                         lastY = event.getRawY();
 79                         ret = true;
 80                         break;
 81                     case MotionEvent.ACTION_MOVE:
 82                         // 获取移动时的X,Y坐标
 83                         nowX = event.getRawX();
 84                         nowY = event.getRawY();
 85                         // 计算XY坐标偏移量
 86                         tranX = nowX - lastX;
 87                         tranY = nowY - lastY;
 88                         // 移动悬浮窗
 89                         lp.x += tranX;
 90                         lp.y += tranY;
 91                         //更新悬浮窗位置
 92                         windowManager.updateViewLayout(imageView,lp);
 93                         //记录当前坐标作为下一次计算的上一次移动的位置坐标
 94                         lastX = nowX;
 95                         lastY = nowY;
 96                         break;
 97                     case MotionEvent.ACTION_UP:
 98                         break;
 99                 }
100                 return ret;
101             }
102         });
103     }
104 
105 }


FloatWindowActivity.java