悬浮窗教程,很简单。知识点不多,写下来 总结一下。

权限

首先 是在AndroidManiFest.xml 定义权限

如果 Android版本大于6.0 ,还需要引导用户 同意这个权限才行。并不是 你定义了就会给你的。

//判断是否 有悬浮窗的权限
        if (!Settings.canDrawOverlays(getApplicationContext())) {
            Intent alertOver = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()));
             //这个页面不会返回值  所以用这个
            startActivity(alertOver);
        }

创建悬浮窗

下面这段代码 ,你可以直接复制到你的项目里面 运行。记得要申请权限!!!!

@SuppressLint("ClickableViewAccessibility")
    void initShow() {

        //获取服务
        WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
        // 设定参数
        WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
        //Android 8  对悬浮窗 进行了 改变
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
        } else {
            layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;
        }
        //设置位图格式 默认是不透明的
        layoutParams.format = PixelFormat.TRANSPARENT;
        //设置宽 高  可以是MATCH_PARENT ,WRAP_CONTENT ,或者 确切的数值
        layoutParams.width =300;
        layoutParams.height = 300;
        //设置悬浮窗 位置,这个受layoutParams.gravity 影响,它提供了从给定边缘的偏移量。
        // 也就是说 这个悬浮窗的实际x,y位置。是这里x,y 加上偏移量后的。
        layoutParams.x = 300;
        layoutParams.y = 300;
        //View以外的区域可以响应点击和触摸事件
        layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
        //容器与小部件之间的水平边距,作为容器宽度的百分比。
        // 通俗的讲 ,就是悬浮窗会在 x,y位置的那个方向,影响 layoutParams.x ,layoutParams.y的偏移量
        //这个默认是AXIS_X_SHIFT   NO_GRAVITY
        //将悬浮窗原点坐标系与屏幕重合  就是 把悬浮窗变成和普通view一样 都是以左上角为原点
        //这个很重要
        layoutParams.gravity = Gravity.LEFT | Gravity.TOP;
         //创建view  就是悬浮窗里面的内容
        TextView textView = new android.support.v7.widget.AppCompatTextView(getApplicationContext()){
            @Override
            public boolean performClick() {
                return super.performClick();
            }
        };
        textView.setText("我是悬浮窗");
        //给view添加触摸事件,来使用悬浮窗可以移动
        textView.setOnTouchListener(new View.OnTouchListener() {
            //这个用于保存 当手指按下时候,离悬浮窗左上角的 x,y距离,这里设定初始值 是悬浮窗宽高的一半
            float inX =layoutParams.width>>1, inY = layoutParams.height>>1;
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        //得到 点击位置 在悬浮窗中的位置
                        inX = event.getX();
                        inY = event.getY();
                        break;
                    case MotionEvent.ACTION_MOVE:
                        //得到当前滑动位置,在整个屏幕上面的坐标
                        float nowX = event.getRawX();
                        float nowY = event.getRawY();
                        Zprint.log(this.getClass()," inX inY",inX,inY,nowX,nowY);
                        //屏幕X坐标 减去 点击时候 在悬浮窗中的位置 X 距离
                        layoutParams.x= (int) (nowX-inX);
                        //屏幕高度坐标 要注意 状态栏高度
                        layoutParams.y = (int) (nowY-statusHeight-inY);
                        //更新悬浮窗 位置
                        windowManager.updateViewLayout(textView, layoutParams);
                        break;
                }
                return false;
            }
        });
        //这下面代码只是说明 在悬浮窗里面的view 与在activity里面的view ,没有任何不同
        textView.setOnClickListener(v -> textView.setText("点击了"));
        textView.setBackgroundColor(getColor(android.R.color.darker_gray));
        //添加view
        windowManager.addView(textView, layoutParams);
    }

首先,是获得windowManager,设置activity的宽高 也是通过这个类来实现的。获取这个实例的方式是通过getSystemService(WINDOW_SERVICE)在activity中的 也可以通过getWindowManager()来获取。
接着,就new 一个WindowManager.LayoutParams 布局参数。这个主要设置 悬浮窗的大小,类型(注意Android8),位置,位图之类的。上面 解释很清楚。如果不懂的 评论。
其次,就可以创建一个View 填充悬浮窗的内容。这个View既可以动态生成,也可以用xml写好,再用 LayoutInflater 实例出来这个View和在activity中的view 并没有什么区别!!!
最后 调用添加view就可以了。

更新悬浮窗位置

使用windowManager.updateViewLayout(View, ViewGroup.LayoutParams);来更新悬浮窗的位置。我们可以给悬浮窗里面的view 添加触摸事件 ,来获取触摸位置,更新悬浮窗,我上面那段触摸事件 可以实现当你按住悬浮窗任意一个位置时候,拖动悬浮窗,离开悬浮窗时候,手指最后离开悬浮窗中的位置是和一开始按住悬浮窗中的位置 是一样的
要注意 状态栏高度,ViewGroup.LayoutParams的x,y坐标是以状态栏和屏幕左边的交点为原点的。而getRaxY() 得到的是距离屏幕左上角的x,y坐标。

获取状态栏高度:

public int getStatusBarHeight() {
         //局部变量 声明后,要赋值,不像成员变量会有默认值
        int result = 0;
        int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
        if (resourceId > 0) {
            result = getResources().getDimensionPixelSize(resourceId);
        }
        return result;
    }

更新悬浮窗内容

更新悬浮窗内容,就和在activity中更新view一样。找到view的实例进行更新view即可。记住要在主线程中

附 得到Android主线程

//handler 持有主线程的looper
  private Handler mainHandler = new Handler(getMainLooper(), new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
             //这里接收处理mainHandler 发送来的message 
             //在这里写实际在主线程中运行的代码
            return false;
        }
    });
    //在你要更新view的地方,或者需要主线程操作的地方 发送message
     mainHandler.sendMessage(Message);

如果 你感觉上面代码 还是繁琐,也可以调用handler的post(Runnable)方法

mainHandler.post(Runnable r)