关于Android应用中的悬浮窗(二)——实现

现在越来越多的Android APP都有悬浮窗的功能,而悬浮窗主要是使用Window来实现的,我们对Window的操作是通过 WindowManager 来完成的。在设置悬浮窗时,需要在清单文件中添加权限 <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />,除了设置清单文件,由于这个权限是危险权限,我们还需要在代码中进行动态的申请。可以去看我上一篇的文章 悬浮窗的权限申请
[https://blog.csdn.net/puppet_zz/article/details/79688849]


实现

Window有三种类型,是应用Window子Window系统Window应用Window 对应一个 Acitivity,子 Window 不能单独存在,需要依附在特定的父 Window 中,比如常见的一些 Dialog 就是一个子 Window。系统 Window是需要声明权限才能创建的 Window,比如 Toast 和系统状态栏都是系统 Window。而我们要实现的悬浮窗也是系统Window。实现悬浮窗,我们通过WindowManager来进行一些设置,WindowManager是一个接口,其继承的ViewManager有三个方法。

public interface ViewManager{
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

这三个方法就是添加、更新、移除View。通过WindowManager来实现悬浮窗的代码如下:

WindowManager.LayoutParams layoutParams layoutParams = new WindowManager.LayoutParams();
WindowManager windowManager windowManager = (WindowManager) getApplication().getSystemService(WINDOW_SERVICE);
//设置Window的类型
layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;
//设置Window显示的模式
layoutParams.format = PixelFormat.RGBA_8888;
//设置Window不可获取焦点
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
//设置Window的位置(如果把gravity设置为CENTER,在设置xy的位置将不起效果)
layoutParams.gravity = Gravity.LEFT|Gravity.TOP;
layoutParams.x = 0;
layoutParams.y = 0;
//设置Window的宽高
layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
//设置Window的布局
LayoutInflater inflater = LayoutInflater.from(getApplication());
View lavFloat= inflater.inflate(R.layout.float_layout, null);

这样就设置好悬浮窗了,同时我们需要悬浮窗随手的移动并移动,并且悬浮窗可以松手后自动贴边

lavFloat.setOnTouchListener(new View.OnTouchListener() {
            int lastX = 0;
            int lastY = 0;
            int moveX = 0;
            int moveY = 0;
            int curX = 0;
            int curY = 0;
            boolean isMove;//是否在移动
            boolean isWelt;//是否贴边
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        lastX = (int) event.getRawX();
                        lastY = (int) event.getRawY();
                        isMove = false;
                        return false;
                    case MotionEvent.ACTION_MOVE:
                        curX = (int) event.getRawX();
                        curY = (int) event.getRawY();
                        FloatService.this.layoutParams.x = curX - lavFloat.getMeasuredWidth() / 2;
                        FloatService.this.layoutParams.y = curY - lavFloat.getMeasuredHeight() / 2;
                        // 刷新
                        if (view!=null){
                            windowManager.updateViewLayout(view, FloatService.this.layoutParams);
                        }
                        return true;
                    case MotionEvent.ACTION_UP:
                        int finalX = (int) event.getRawX();
                        int finalY = (int) event.getRawY();
                        int clickX =Math.abs(finalX-lastX);
                        int clickY =Math.abs(finalY- lastY) ;
                        if (clickX < 20 || clickY < 20) {//滑动小于20 视为点击事件
                            isMove = false;
                        } else {
                            isMove = true;
                        }
                        //设置贴边时,有个弹簧动画
                        SpringForce springForce = new SpringForce(0)
                                .setDampingRatio(0.75f)
                                .setStiffness(SpringForce.STIFFNESS_HIGH);

                        SpringForce force = new SpringForce(1)
                                .setDampingRatio(0.75f)
                                .setStiffness(SpringForce.STIFFNESS_HIGH);
                        SpringAnimation animationX = new SpringAnimation(lavFloat, SpringAnimation.TRANSLATION_X).setSpring(springForce);
                        SpringAnimation animation = new SpringAnimation(lavFloat, SpringAnimation.TRANSLATION_X).setSpring(force);

                        if (finalY < lavFloat.getHeight()) {//触摸位置小于view的高度 (理解为屏幕顶部)
                            moveY = 0;
                            if (finalX <= screenWidth / 2) {//贴左边
                                moveX = 0;
                            } else {//贴右边
                                moveX = screenWidth;
                            }
                        }

                        if (finalY > screenHeight - lavFloat.getHeight()) {//触摸位置大于屏幕高度view的高度 (理解为屏幕底部)
                            moveY = screenHeight;
                            if (finalX <= screenWidth / 2) {//贴左边
                                moveX = 0;
                            } else {//贴右边
                                moveX = screenWidth;
                            }
                        }

                        if (finalY >= lavFloat.getHeight() && finalY <= screenHeight - lavFloat.getHeight()) {
                            moveY = finalY - lavFloat.getHeight() / 2;
                            if (finalX <= screenWidth / 2) {//贴左边
                                moveX = 0;
                            } else {//贴右边
                                moveX = screenWidth;
                            }
                        }

                        FloatService.this.layoutParams.x = moveX;
                        FloatService.this.layoutParams.y = moveY;

                        if (isMove) {
                            if (view!=null){
                                windowManager.updateViewLayout(view, FloatService.this.layoutParams);
                            }
                        }

                        isWelt = false;
                        if (moveX ==0) {
                            isWelt = true;
                            animationX.setStartValue((float) finalX);
                            animationX.start();
                        } else if (moveX ==screenWidth){
                            isWelt = true;
                            animation.setStartValue((float) finalX);
                            animation.start(); 
                        }
                        return isMove;//false 为点击 true 为移动
                    default:
                        break;
                }
                return false;//false才能有点击操作
            }
        });

同时设置对悬浮窗可以进行点击操作

lavFloat.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (isWelt) {//贴边才能点击
                    //点击逻辑
                }
            }
        });

我们开启悬浮窗时可以在Activity中开启,也可以在Service中开启。可以根据具体需求具体分析,不过建议在Service中开启,因为Service有单独的生命周期,不与Activity周期重叠,可以更好的操作悬浮窗。有时候我们需要开启悬浮窗,有些时候也需要隐藏悬浮窗,如果在Service中设置的话,我们可以开启服务来开启悬浮窗,需要隐藏时,关闭服务来隐藏悬浮窗。关闭悬浮窗需要在服务销毁时移除悬浮窗。

 @Override
    public int onStartCommand(Intent intent, int flags, int startId) {//添加悬浮窗
        openFloat();//就是上面的代码
        return super.onStartCommand(intent, flags, startId);
    }


@Override
    public void onDestroy() {//移除悬浮窗
        super.onDestroy();
        windowManager.removeViewImmediate(view);
    }

当应用退到后台时,悬浮窗会在桌面显示的,我们应该让应用在后台时,悬浮窗进行隐藏。可以建立一个广播,来进行判断,在后台时,关闭服务。

悬浮窗的添加就基本完成,配合上一篇 悬浮窗的权限适配 就可以完整的来实现悬浮窗。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值