Toast之悬浮窗(二)

引言

上篇看我的Toast之悬浮窗不需要权限,已经讲了怎么去实现一个不需要权限的悬浮窗,也讲了在其中需要注意的问题以及它适用的版本等等。但是我似乎没有讲它怎么移动呀,那实现悬浮窗之后,很多绚丽的动画都不能实现了呀,那怎么办?小编现在就带领大家来实现它的移动吧。

疑惑

肯定有同学问,直接设置view的触摸事件来实现它的移动不就行了,还需要单独来讲一讲它的移动么?做过悬浮窗的童鞋肯定知道前面的这种想法是不对的,view是添加到WindowManager中的,需要用Windowmanager去改变View的位置。真的有这么简单么?还是让我们一起来往下看吧。

view之Touch事件

按照touch事件的想法,我们去给Toast的view设置一个Touch事件

 mToastView.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            int action = event.getAction();
            switch (action){
                case MotionEvent.ACTION_DOWN:
                    mDownX = (int) event.getRawX();
                    mDownY = (int) event.getRawY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    int rawX = (int) event.getRawX();
                    int rawY = (int) event.getRawY();
                    int dx = rawX - mDownX;
                    int dy = rawY - mDownY;
                    mParams.x = mToastViewLastX + dx;
                    mParams.y = mToastViewLastY + dy;
                    v.setX(mParams.x);
                    v.setY(mParams.y);
                    break;
                case MotionEvent.ACTION_UP:
                    mToastViewLastX = mParams.x;
                    mToastViewLastY = mParams.y;
                    break;
            }
            return true;
        }
    });

走完之后发现不行,接下来验证WindowManager来改变View的位置

WindowManager之updateViewLayout

WindowManager改变view的位置有updateViewLayout(View view, ViewGroup.LayoutParams params)这个方法,我们直接获取到位置之后去改变View的坐标

 mToastView.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            int action = event.getAction();
            switch (action){
                case MotionEvent.ACTION_DOWN:
                    mDownX = (int) event.getRawX();
                    mDownY = (int) event.getRawY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    int rawX = (int) event.getRawX();
                    int rawY = (int) event.getRawY();
                    int dx = rawX - mDownX;
                    int dy = rawY - mDownY;
                    mParams.x = mToastViewLastX + dx;
                    mParams.y = mToastViewLastY + dy;
                    mWindowManager.updateViewLayout(v, mParams);
                    break;
                case MotionEvent.ACTION_UP:
                    mToastViewLastX = mParams.x;
                    mToastViewLastY = mParams.y;
                    break;
            }
            return true;
        }
    });

运行一下,发现程序崩了,我们来看报的错误

java.lang.IllegalArgumentException: Window type can not be changed after the window is added.这是什么鬼,type不能被改变,我没有改变type呀,怎么会报这个错误呢,表示不懂,我们去看一下哪里抛出的这个异常吧。
似乎报错的类是IWindowSession,这是一个系统的AIDL生成类,我们就不去看它了,我们来看上层的ViewRootImpl这个类中的relayoutWindow()方法中的关键代码.

报错的方法应该就是mWindowSession.relayout(),我们看上面的那个Log,大概知道,就是我们在updateViewLayout的时候需要再设置它的type,没有去给window设置它的类型,所以会报这种错误。那我们就给他设置类型吧。

 mParams = new WindowManager.LayoutParams();
    if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2) {
        mParams.type = WindowManager.LayoutParams.TYPE_TOAST;
    } else {
        mParams.type = WindowManager.LayoutParams.TYPE_PHONE;
    }
    mParams.flags =  WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
    mParams.width =  WindowManager.LayoutParams.WRAP_CONTENT;
    mParams.height =  WindowManager.LayoutParams.WRAP_CONTENT;
    mParams.dimAmount = 0.6F;
    mParams.gravity = Gravity.START;

设置之后运行,果然就可以啦,这样我们就可以开心的玩耍了。

Toast实现之系统7.0

运行之后会发现,首先你会发现你返回的Toast的那个View不存在,你把相关代码注释掉之后,你再运行,悬浮窗生成之后等一下就消失了,这就是google官方给的文档说的7.0以后TYPE_TOAST不能用了,至于系统做了什么样的处理,有兴趣的童鞋可以自己去看一下系统的源码.

WindowManagerService中addWindow()的处理

// If adding a toast requires a token for this app we always schedule hiding
// toast windows to make sure they don't stick around longer then necessary.
// We hide instead of remove such windows as apps aren't prepared to handle
// windows being removed under them.
//
// If the app is older it can add toasts without a token and hence overlay
// other apps. To be maximally compatible with these apps we will hide the
// window after the toast timeout only if the focused window is from another
// UID, otherwise we allow unlimited duration. When a UID looses focus we
// schedule hiding all of its toast windows.
if (type == TYPE_TOAST) {
    if (!getDefaultDisplayContentLocked().canAddToastWindowForUid(callingUid)) {
        Slog.w(TAG_WM, "Adding more than one toast window for UID at a time.");
        return WindowManagerGlobal.ADD_DUPLICATE_ADD;
    }
    // Make sure this happens before we moved focus as one can make the
    // toast focusable to force it not being hidden after the timeout.
    // Focusable toasts are always timed out to prevent a focused app to
    // show a focusable toasts while it has focus which will be kept on
    // the screen after the activity goes away.
    if (addToastWindowRequiresToken
            || (attrs.flags & LayoutParams.FLAG_NOT_FOCUSABLE) == 0
            || mCurrentFocus == null
            || mCurrentFocus.mOwnerUid != callingUid) {
        mH.sendMessageDelayed(
                mH.obtainMessage(H.WINDOW_HIDE_TIMEOUT, win),
                win.mAttrs.hideTimeoutMilliseconds);
    }
}

总结

悬浮窗之Toast实现就讲到这里啦,大家有什么疑问,可以给我留言,上面所讲有什么错误,也欢迎批评指正,谢谢大家。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值