Android 悬浮窗,悬浮view功能实现

写在前面:本文仅个人开发时遇到的问题及个人解决办法的记录。

国内各个手机厂商对ROM魔改的比较严重,还没有做兼容性测试,所以碰到沙雕的机子的时候,请再去寻找适配方法

最近项目开发中,需要实现一个悬浮窗,说一下实现方式,做一下记录。

        首先,简单的藐视就是:实现悬浮窗是用的WindowManager。利用context.getSystemService(Context.WINDOW_SERVICE)获取到WindowManger对象,调用里面的windowManager.addView(floatView, layoutParams)方法。floatView就是要展示的悬浮窗的View layoutParams是一些参数设置。

        下面介绍一下实现步骤(懒得看的可以下滑到最下面看代码):

        申请权限,这是必须的一步

           在你的清单文件中添加如下权限代码

           

然后在代码里面使用下面的方法判断是否有悬浮窗权限:

            Settings.canDrawOverlays(context)

如果没有权限,跳转到权限开启页面,打开悬浮窗权限。确切的说是跳转到开启 允许显示在其他应用上层  的权限

startActivityForResult(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())), 10086);

一切准备工作完成后开始我们的正式任务啊!!!!!!!!!!

第一步,获取到WindowManager对象;

(WindowManager) context.getSystemService(Context.WINDOW_SERVICE)

第二步,创建一个WindowManager.LayoutParams对象,用于设置一些悬浮view的参数

// 设置LayoutParam

layoutParams =new WindowManager.LayoutParams();

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;

}else {

layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;

}

//悬浮窗弹出的位置

layoutParams.gravity = Gravity.LEFT|Gravity.CENTER;

//注意:这一个flags的设置,之前搜索很多实现都没有设置这个,出现的情况就是在悬浮的view出现后  点击窗口其它地方没有反应,是因为不设置这个参数,悬浮窗弹出来后就占据整个窗口的焦点。

layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;

layoutParams.format = PixelFormat.RGBA_8888;

layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;

layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;

layoutParams.x =0;

layoutParams.y =0;

第三步 获取到需要悬浮显示的view对象

LayoutInflater layoutInflater = LayoutInflater.from(context);

View floatView = layoutInflater.inflate(R.layout.floating_view, null);

第四步,将悬浮view和layoutParams调用windowmanager的方法addView显示出来

// 将悬浮窗控件添加到WindowManager

windowManager.addView(floatView, layoutParams);

如果你需要对悬浮窗里不同view设置一些点击事件

我们在上面第三步里面获取到了悬浮窗的View对象,可以使用view.findviewById方法根据id拿到各个view,针对不同的view设置不同的事件。

对悬浮窗添加拖动事件

同样上面第三步我们获取到的view对象,设置触摸事件

//设置触摸事件

floatView.setOnTouchListener(new FloatingOnTouchListener());

//因为我的悬浮窗需求比较简单,所以没有那么多复杂的操作。只是拖动后,让悬浮view靠边停着。

private class FloatingOnTouchListenerimplements View.OnTouchListener {

private int x;

    private int y;

    //标记是否执行move事件 如果执行了move事件  在up事件的时候判断悬浮窗的位置让悬浮窗处于屏幕左边或者右边

    private boolean isScroll;

    //标记悬浮窗口是否移动了  防止设置点击事件的时候 窗口移动松手后触发点击事件

    private boolean isMoved;

    //事件开始时和结束的时候  X和Y坐标位置

    private int startX;

    private int startY;

    @Override

    public boolean onTouch(View view, MotionEvent event) {

switch (event.getAction()) {

case MotionEvent.ACTION_DOWN:

x = (int) event.getRawX();

                y = (int) event.getRawY();

                isMoved =false;

                isScroll =false;

                startX = (int) event.getRawX();

                startY = (int) event.getRawY();

break;

            case MotionEvent.ACTION_MOVE:

int nowX = (int) event.getRawX();

                int nowY = (int) event.getRawY();

                int movedX = nowX -x;

                int movedY = nowY -y;

                x = nowX;

                y = nowY;

                layoutParams.x =layoutParams.x + movedX;

                layoutParams.y =layoutParams.y + movedY;

                // 更新悬浮窗控件布局

                windowManager.updateViewLayout(view, layoutParams);

                isScroll =true;

break;

            case MotionEvent.ACTION_UP:

int stopX = (int) event.getRawX();

                int stopY = (int) event.getRawY();

                if (Math.abs(startX - stopX) >=1 || Math.abs(startY - stopY) >=1) {

isMoved =true;

                }

if (isScroll){

autoView(view);

                }

break;

        }

return isMoved;

    }

//悬浮窗view自动停靠在屏幕左边或者右边

    private void autoView(View view) {

// 得到view在屏幕中的位置

        int[] location =new int[2];

        view.getLocationOnScreen(location);

        if (location[0]

layoutParams.x =0;

        }else {

layoutParams.x = DensityUtils.getScreenSize(false).x - view.getWidth();

        }

windowManager.updateViewLayout(view, layoutParams);

    }

}

最后,放出来一个简单处理的类,有需求的可以根据需求自己修改

代码:


public class FloatingWindowUtils {

private Contextcontext;

    private int screenWidth;

    private WindowManager.LayoutParamslayoutParams;

    private WindowManagerwindowManager;

    private ViewfloatView;

    private FloatingWindowUtils() {

}

private static class InstanceHolder {

@SuppressLint("StaticFieldLeak")

private static final FloatingWindowUtilssInstance =new FloatingWindowUtils();

        private InstanceHolder() {

}

}

public static FloatingWindowUtilsgetInstance() {

return FloatingWindowUtils.InstanceHolder.sInstance;

    }

public void init(Context context) {

this.context = context;

        if (windowManager !=null)return;

        // 获取WindowManager服务

        windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

        //获取屏宽

        screenWidth = DensityUtils.getScreenSize(false).x;

    }

/**

    * 展示悬浮窗

    * @param layoutId 悬浮窗布局文件id

*/

    @SuppressLint("RtlHardcoded")

public void showFloatingWindow(@LayoutRes int layoutId){

// 新建悬浮窗控件

        LayoutInflater layoutInflater = LayoutInflater.from(context);

//        View floatView = layoutInflater.inflate(R.layout.floating_view, null);

        View floatView = layoutInflater.inflate(layoutId, null);

        if (floatView ==null){

throw new NullPointerException("悬浮窗view为null 检查布局文件是否可用");

        }

showFloatingWindow(floatView);

    }

/**

    * 展示悬浮窗

    * @param floatView 悬浮窗view

*/

    @SuppressLint("RtlHardcoded")

public void showFloatingWindow(@NonNull View floatView){

if (this.floatView !=null)return;//有悬浮窗在显示 不再显示新的悬浮窗

        // 新建悬浮窗控件

        if (floatView ==null){

throw new NullPointerException("悬浮窗view为null 确认view不为null");

        }

this.floatView = floatView;

        //设置触摸事件

        floatView.setOnTouchListener(new FloatingOnTouchListener());

        //悬浮窗设置点击事件

        floatView.setOnClickListener(new View.OnClickListener() {

@Override

            public void onClick(View v) {

Toast.makeText(context, "点击了悬浮窗", Toast.LENGTH_SHORT).show();

            }

});

        // 设置LayoutParam

        layoutParams =new WindowManager.LayoutParams();

        if (Build.VERSION.SDK_INT  >= Build.VERSION_CODES.O) {

layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;

        }

layoutParams.gravity = Gravity.LEFT|Gravity.CENTER;

        //设置flags 不然悬浮窗出来后整个屏幕都无法获取焦点,

        layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;

        layoutParams.format = PixelFormat.RGBA_8888;

        layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;

        layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;

        layoutParams.x =0;

        layoutParams.y =0;

        // 将悬浮窗控件添加到WindowManager

        windowManager.addView(floatView, layoutParams);

    }

/**

    * 隐藏悬浮窗

    */

    public void hideFloatWindow(){

if (floatView !=null){

windowManager.removeViewImmediate(floatView);

            floatView =null;

        }

}

public void unInit() {

hideFloatWindow();

        this.context =null;

        // 获取WindowManager服务

        windowManager =null;

    }

private class FloatingOnTouchListenerimplements View.OnTouchListener {

private int x;

        private int y;

        //标记是否执行move事件 如果执行了move事件  在up事件的时候判断悬浮窗的位置让悬浮窗处于屏幕左边或者右边

        private boolean isScroll;

        //标记悬浮窗口是否移动了  防止设置点击事件的时候 窗口移动松手后触发点击事件

        private boolean isMoved;

        //事件开始时和结束的时候  X和Y坐标位置

        private int startX;

        private int startY;

        @Override

        public boolean onTouch(View view, MotionEvent event) {

switch (event.getAction()) {

case MotionEvent.ACTION_DOWN:

x = (int) event.getRawX();

                    y = (int) event.getRawY();

                    isMoved =false;

                    isScroll =false;

                    startX = (int) event.getRawX();

                    startY = (int) event.getRawY();

break;

                case MotionEvent.ACTION_MOVE:

int nowX = (int) event.getRawX();

                    int nowY = (int) event.getRawY();

                    int movedX = nowX -x;

                    int movedY = nowY -y;

                    x = nowX;

                    y = nowY;

                    layoutParams.x =layoutParams.x + movedX;

                    layoutParams.y =layoutParams.y + movedY;

                    // 更新悬浮窗控件布局

                    windowManager.updateViewLayout(view, layoutParams);

                    isScroll =true;

break;

                case MotionEvent.ACTION_UP:

int stopX = (int) event.getRawX();

                    int stopY = (int) event.getRawY();

                    if (Math.abs(startX - stopX) >=1 || Math.abs(startY - stopY) >=1) {

isMoved =true;

                    }

if (isScroll){

autoView(view);

                    }

break;

            }

return isMoved;

        }

//悬浮窗view自动停靠在屏幕左边或者右边

        private void autoView(View view) {

// 得到view在屏幕中的位置

            int[] location =new int[2];

            view.getLocationOnScreen(location);

            if (location[0]

layoutParams.x =0;

            }else {

layoutParams.x = DensityUtils.getScreenSize(false).x - view.getWidth();

            }

windowManager.updateViewLayout(view, layoutParams);

        }

}

public ViewgetFloatView(){

return floatView;

    }

}



最后给大家放一个介绍比较全面的帖子。

https://www.jianshu.com/p/3246f7289704

你可能感兴趣的:(Android 悬浮窗,悬浮view功能实现)