引言
上篇看我的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实现就讲到这里啦,大家有什么疑问,可以给我留言,上面所讲有什么错误,也欢迎批评指正,谢谢大家。