我们上一篇已经说明了我们需要改进的三个需求,分别是:
- 拖拽悬浮窗,需要判断拖拽距离后才会产生拖拽效果。
- 根据悬浮窗在手机屏幕的位置,自动判断靠边。
- 悬浮窗首次绘制时的屏幕位置能随意更改。
那么,我们这一篇就来说说这三个需求的代码实现。
1.判断拖拽距离 和 自动判断靠边
FloatWindowParams 为我自己写的一个悬浮窗参数类,并不会影响到你对本代码块的逻辑及算法的理解。
顺便说下,访问修饰符为protected 都是我自写悬浮窗参数类里的参数,在这里当成成员变量一块贴了出来
/**
* 系统悬浮窗的参数类
*/
protected WindowManager.LayoutParams mParams;
/**
* 悬浮窗布局
*/
protected View mFloatWindowLayout;
/**
* 悬浮窗宽度
*/
protected float mFloatWidth;
/**
* 悬浮窗高度
*/
protected float mFloatHeight;
/**
* 悬浮窗靠边的参数
*/
protected boolean mKeepSideFlag;
/**
* 悬浮窗参数类
*/
private FloatWindowParams mParams;
/**
* ACTION_DOWN时的X坐标
*/
private float mOldStopX;
/**
* ACTION_DOWN时的Y坐标
*/
private float mOldStopY;
/**
* 绘制停止移动时的悬浮窗开关
* 当判断为拖拽,允许触发ACTION_UP事件
* 当判断未拖拽,关闭触发ACTION_UP事件
*/
private boolean mDrawStopFloatWindowFlag;
/**
* 手机屏幕宽像素
*/
private float mScreenW;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
mOldStopX = event.getRawX();
mOldStopY = event.getRawY();
break;
}
case MotionEvent.ACTION_MOVE: {
//根据移动逻辑,绘制悬浮窗口方法
judgeMoveFloatWindow(event);
break;
}
case MotionEvent.ACTION_UP: {
if (mDrawStopFloatWindowFlag) {
judgeStopFloatWindow(event,
mParams.mKeepSideFlag);
}
break;
}
}
return false; //此处必须返回false,否则OnClickListener获取不到监听
}
/**
* 判断移动悬浮窗口<br>
* 通过四种情况,来判断是否达成移动条件<br>
* 达成了-关闭点击事件开关,并实时绘制悬浮窗<br>
* 未达成-打开点击事件开关<br>
* 条件:(条件中的移动距离是实时计算的)<br>
* X轴移动距离 > 悬浮窗的W<br>
* Y轴移动距离 > 悬浮窗的H<br>
* 情况:<br>
* 第一种-X轴移动距离为正值,Y轴移动距离为正值 <br>
* 第二种-X轴移动距离为正值,Y轴移动距离为负值 <br>
* 第三种-X轴移动距离为负值,Y轴移动距离为正值 <br>
* 第四种-X轴移动距离为负值,Y轴移动距离为负值 <br>
*
* @param event event
*/
private void judgeMoveFloatWindow(MotionEvent event) {
if (event.getRawX() - mOldStopX >= 0) {
if (event.getRawY() - mOldStopY >= 0) {
//判断拖拽的距离我这里写的是悬浮窗的X,Y轴像素
if ((event.getRawX() - mOldStopX) > mParams.mFloatWidth ||
(event.getRawY() - mOldStopY) > mParams.mFloatHeight) {
mDrawStopFloatWindowFlag = true;
//绘制悬浮窗口
drawMoveFloatWindow(event);
}
} else if (event.getRawY() - mOldStopY < 0) {
//判断拖拽的距离我这里写的是悬浮窗的X,Y轴像素
if ((event.getRawX() - mOldStopX) > mParams.mFloatWidth ||
-(event.getRawY() - mOldStopY) > mParams.mFloatHeight) {
mDrawStopFloatWindowFlag = true;
//绘制悬浮窗口
drawMoveFloatWindow(event);
}
}
} else if (event.getRawX() - mOldStopX < 0) {
if (event.getRawY() - mOldStopY >= 0) {
//判断拖拽的距离我这里写的是悬浮窗的X,Y轴像素
if (-(event.getRawX() - mOldStopX) > mParams.mFloatWidth ||
(event.getRawY() - mOldStopY) > mParams.mFloatHeight) {
mDrawStopFloatWindowFlag = true;
//绘制悬浮窗口
drawMoveFloatWindow(event);
}
} else if (event.getRawY() - mOldStopY < 0) {
//判断拖拽的距离我这里写的是悬浮窗的X,Y轴像素
if (-(event.getRawX() - mOldStopX) > mParams.mFloatWidth ||
-(event.getRawY() - mOldStopY) > mParams.mFloatHeight) {
mDrawStopFloatWindowFlag = true;
//绘制悬浮窗口
drawMoveFloatWindow(event);
}
}
}
}
/**
* 判断悬浮窗停止拖拽后是否靠边<br>
* 如果靠边,判断靠左还是靠右<br>
*
* @param event event
* @param keepSideFlag 靠边开关
*/
private void judgeStopFloatWindow(MotionEvent event, boolean keepSideFlag) {
if (keepSideFlag) {
if (event.getRawX() < mScreenW / 2) {//判断悬浮窗在屏幕左半边
drawStopFloatWindow(event, 1);
} else if (event.getRawX() >= mScreenW / 2) {//判断悬浮窗在屏幕右半边
drawStopFloatWindow(event, 2);
}
} else {//不靠边
drawStopFloatWindow(event, 3);
}
}
/**
* 绘制移动悬浮窗口
*
* @param event event
*/
private void drawMoveFloatWindow(MotionEvent event) {
//绘制悬浮窗时,X,Y轴坐标各减去悬浮窗布局的X,Y轴的一半
mParams.mParams.x = (int) (event.getRawX() - mParams.mFloatWidth / 2);
mParams.mParams.y = (int) (event.getRawY() - mParams.mFloatHeight / 2);
mWindowManager.updateViewLayout(mParams.mFloatWindowLayout, mParams.mParams);
}
/**
* 绘制停止拖拽后的悬浮窗<br>
* 根据是否靠边或靠左靠右来绘制<br>
*
* @param event event
* @param site 1-LEFT,2-RIGHT,3-不靠边
*/
private void drawStopFloatWindow(MotionEvent event, int site) {
switch (site) {
case 1: {
mParams.mParams.x = 0;//靠左
mParams.mParams.y = (int) (event.getRawY() - mParams.mFloatHeight / 2);
mWindowManager.updateViewLayout(mParams.mFloatWindowLayout, mParams.mParams);
mDrawStopFloatWindowFlag = false;
break;
}
case 2: {
mParams.mParams.x = (int) (mScreenW);//靠右
mParams.mParams.y = (int) (event.getRawY() - mParams.mFloatHeight / 2);
mWindowManager.updateViewLayout(mParams.mFloatWindowLayout, mParams.mParams);
mDrawStopFloatWindowFlag = false;
break;
}
case 3: {
mParams.mParams.x = (int) (event.getRawX() - mParams.mFloatWidth / 2);
mParams.mParams.y = (int) (event.getRawY() - mParams.mFloatHeight / 2);
mWindowManager.updateViewLayout(mParams.mFloatWindowLayout, mParams.mParams);
mDrawStopFloatWindowFlag = false;
break;
}
}
}
2.悬浮窗首次绘制的位置
以下代码块,在位置的概念上加入了除数。
让位置的概念从之前的9种一下子变得更自由更随意
说点题外话,之前没有加除数限制的时候
好像当除数大于10000还是大于100000的时候
手机会死机,真的死机哦,只能拔电池或者长按电源键
public final static int GRAVITY_TOP = 1000000;
public final static int GRAVITY_BOTTOM = 1000001;
public final static int GRAVITY_LEFT = 1000002;
public final static int GRAVITY_RIGHT = 1000003;
public final static int GRAVITY_CENTER = 1000004;
/**
* 悬浮窗绘制的位置<br>
* 可以是位置,也可以是X,Y轴坐标的除数<br>
* 当为除数时,将会取商的一半来绘制<br>
* 也就是说:被除数为Y轴,除数为1,商为Y轴,那么绘制点将在Y轴中间<br>
* 如果位置逻辑错误,或者除数小于1,或者除数大于5,悬浮窗最终都将绘制在左上<br>
* 例如:FloatWindowParams.GRAVITY_LEFT 或 正整数(1-5)
*
* @param x X轴位置或者除数(1-5)
* @param y Y轴位置或者除数(1-5)
*/
private void drawFloatWindowGravity(int x, int y) {
switch (x) {
case FloatWindowParams.GRAVITY_LEFT: {
switch (y) {
case FloatWindowParams.GRAVITY_TOP: {//左上
mParams.x = 0;
mParams.y = 0;
break;
}
case FloatWindowParams.GRAVITY_BOTTOM: {//左下
mParams.x = 0;
mParams.y = (int) mScreenH;
break;
}
case FloatWindowParams.GRAVITY_CENTER: {//左中
mParams.x = 0;
mParams.y = (int) (mScreenH / 2 - mFloatHeight / 2);
break;
}
default: {//X轴为静态常量-左,Y轴为除数
if (0 < y && y < 6) {
mParams.x = 0;
mParams.y = (int) (mScreenH / y / 2 - mFloatHeight / 2);
} else {
mParams.x = 0;
mParams.y = 0;
}
break;
}
}
break;
}
case FloatWindowParams.GRAVITY_RIGHT: {
switch (y) {
case FloatWindowParams.GRAVITY_TOP: {//右上
mParams.x = (int) mScreenW;
mParams.y = 0;
break;
}
case FloatWindowParams.GRAVITY_BOTTOM: {//右下
mParams.x = (int) mScreenW;
mParams.y = (int) mScreenH;
break;
}
case FloatWindowParams.GRAVITY_CENTER: {//右中
mParams.x = (int) mScreenW;
mParams.y = (int) (mScreenH / 2 - mFloatHeight / 2);
break;
}
default: {//X轴为静态常量-右,Y轴为除数
if (0 < y && y < 6) {
mParams.x = (int) mScreenW;
mParams.y = (int) (mScreenH / y / 2 - mFloatHeight / 2);
} else {
mParams.x = 0;
mParams.y = 0;
}
break;
}
}
break;
}
case FloatWindowParams.GRAVITY_CENTER: {
switch (y) {
case FloatWindowParams.GRAVITY_TOP: {//中上
mParams.x = (int) (mScreenW / 2 - mFloatWidth / 2);
mParams.y = 0;
break;
}
case FloatWindowParams.GRAVITY_BOTTOM: {//中下
mParams.x = (int) (mScreenW / 2 - mFloatWidth / 2);
mParams.y = (int) mScreenH;
break;
}
case FloatWindowParams.GRAVITY_CENTER: {//中中
mParams.x = (int) (mScreenW / 2 - mFloatWidth / 2);
mParams.y = (int) (mScreenH / 2 - mFloatHeight / 2);
break;
}
default: {//X轴为静态常量-中,Y轴为除数
if (0 < y && y < 6) {
mParams.x = (int) (mScreenW / 2 - mFloatWidth / 2);
mParams.y = (int) (mScreenH / y / 2 - mFloatHeight / 2);
} else {
mParams.x = 0;
mParams.y = 0;
}
break;
}
}
break;
}
default: {//X轴为除数
switch (y) {
case FloatWindowParams.GRAVITY_TOP: {//X轴为除数,Y轴为静态常量-上
if (0 < x && x < 6) {
mParams.x = (int) (mScreenW / x / 2 - mFloatWidth / 2);
mParams.y = 0;
} else {
mParams.x = 0;
mParams.y = 0;
}
break;
}
case FloatWindowParams.GRAVITY_BOTTOM: {//X轴为除数,Y轴为静态常量-下
if (0 < x && x < 6) {
mParams.x = (int) (mScreenW / x / 2 - mFloatWidth / 2);
mParams.y = (int) mScreenH;
} else {
mParams.x = 0;
mParams.y = 0;
}
break;
}
case FloatWindowParams.GRAVITY_CENTER: {//X轴为除数,Y轴为静态常量-中
if (0 < x && x < 6) {
mParams.x = (int) (mScreenW / x / 2 - mFloatWidth / 2);
mParams.y = (int) (mScreenH / 2 - mFloatHeight / 2);
} else {
mParams.x = 0;
mParams.y = 0;
}
break;
}
default: {//X轴为除数,Y轴为除数
if (0 < x && x < 6 && 0 < y && y < 6) {
mParams.x = (int) (mScreenW / x / 2 - mFloatWidth / 2);
mParams.y = (int) (mScreenH / y / 2 - mFloatHeight / 2);
} else {
mParams.x = 0;
mParams.y = 0;
}
break;
}
}
break;
}
}
}
代码篇这里也分享完了
可能有朋友觉得这些代码没有办法直接粘贴复制,不够实用
那我只能说,朋友们将本篇内容理解错误了
做为代码篇,我的用意只是想通过代码的形式,让各位朋友理解代码本身的逻辑及算法
至于实用,我想说,朋友们不要着急。
又实用又方便又简单,即可以直接粘贴复制又可以随意更改的悬浮窗工具类
将会在Android悬浮窗进阶版-下篇(福利篇)放出