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

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

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

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

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

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

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

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

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

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

            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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容