可移动悬浮窗关于随着手势移动的坐标区分与需要注意的事项

想要做一个简单的像悬浮球那样的能在手机桌面上随意拖动的效果,首先你需要知道
1:往手机桌面上添加一个自定义view(悬浮球)使用的是WindowManager.addView(View view, WindowManager.LayoutParams params); ---->WindowManager.LayoutParams是用于指定view在桌面中的位置,以及view在桌面中的一些属性;
2:如果想出现悬浮球效果,在清单文件中必须声明 <uses-permission android :name= "android.permission.SYSTEM_ALERT_WINDOW" /> 权限
3:如果想随意移动你的自定义View必须在View中重写onTouchEvent()方法来记录坐标的变化(注意,每个View中都有onTouchEvent方法,注意区别onTouchEvent方法),最后使用windowManager.updateViewLayout(View view, WIndowManager.LayoutParams params);更新自定义view(悬浮球)的位置

现在我们开始实现自定义view(悬浮球)
1:先定义一个悬浮球的布局文件small_floatball,由于这次讲述的重点不在这里,所以不详细说明,直接上代码
<? xml version= "1.0" encoding= "utf-8" ?>
<FrameLayout xmlns: android = "http://schemas.android.com/apk/res/android"
android :layout_width= "50dp"
android :layout_height= "50dp"
android :id= "@+id/small_window_layout"
android :background= "@android:color/transparent"
android :orientation= "vertical" >

<ImageView
android :layout_width= "35dp"
android :layout_height= "35dp"
android :id= "@+id/img_bg"
android :background= "@drawable/icon_bg"
android :layout_gravity= "center"
/>
<ImageView
android :layout_width= "wrap_content"
android :layout_height= "wrap_content"
android :id= "@+id/img_ball"
android :background= "@drawable/icon_ball"
android :layout_gravity= "center" />
</FrameLayout>

效果图:(注意xml文件中的Framelayout和ImageView的大小)








2:现在我们开始自定义view
public class FloatBall extends LinearLayout {


/**
* 记录当手指按下时的手指的坐标(坐标是以屏幕(包括状态栏)左上角为原点)
* 注意:手指必须按到这个自定义view
* 使用getRawX(), getRawY()
*/
private float mDownX;
private float mDownY;

/**
* 记录当手指移动时的手指的坐标(坐标是以屏幕(包括状态栏)左上角为原点)
* 注意:手指必须按到这个自定义view
* 使用getRawX(), getRawY()
*/
private float mOnMoveX;
private float mOnMoveY;

/**
* 记录当手指按下时的手指相对于view这个控件的坐标(坐标是以控件左上角为原点)
* 注意:手指必须按到这个自定义view
* 使用getX(), getY()
*/
private float mInViewX;
private float mInViewY;

/**
* 用于指定自定义view(悬浮球)在桌面上的属性,包括自定义view(悬浮球)的大小,在桌面中的位置等信息
*/
private WindowManager.LayoutParams mParams;
/**
* 用于待会在自定义view的构造函数中使用WindowManager.add(this, params)直接将自定义的view加到桌面中
* 还用于更新自定义view在桌面中的位置,使用的是windowManager.updataViewLayout(this, params);
*/
private WindowManager mWindowManager;

public FloatBall(Context context) {
super(context);
/**
* 先将刚才写好的悬浮球的布局文件实例化,然后加入到this中,也就是直接加入到这个自定义的LinearLayout中
* 这个LinearLayout其实就是承载刚才我们写的那个View的,可以把LinearLayout直接当成悬浮球
*/
LayoutInflater.from(context).inflate(R.layout.small_floatball, this);

/**
* 获取到这个控件
*/
View view = findViewById(R.id.small_window_layout);

/**
* 获取WindowManager
*/
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

/**
* 下面这些都是给这个自定义view的在桌面中的位置,大小以及其他属性进行赋值的,这里先不要纠结这个这个属性的意思,知道他是给悬浮球定义属性和大小的就好,我在下面会详细说明
*/
mParams = new WindowManager.LayoutParams();
mParams.type = WindowManager.LayoutParams.TYPE_PHONE;
mParams.format = PixelFormat.RGBA_8888;
mParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
mParams.gravity = Gravity.LEFT | Gravity.TOP;
mParams.height = view.getLayoutParams().height;
mParams.width = view.getLayoutParams().width;
mParams.x = mWindowManager.getDefaultDisplay().getWidth();
mParams.y = mWindowManager.getDefaultDisplay().getHeight() / 2;

/**
* 将悬浮球直接加入到桌面中
* 这个可以看出,我们在自定义view的构造方法中直接把this(也就是自定义控件)加入到了桌面中,所以我们直接在主Activity中new 这个自定义控件就会直接把控件加入到桌面中
*/
mWindowManager.addView(this, mParams);
}


/**
* 接下来还有三个方法,主要是和坐标相关,坐标这里有坑,所以我先讲一下关于坐标的一些该注意的地方,然后再讲接下来的两个方法,以及之前的mParams
*/

/**
* 这个是当我们触碰悬浮球时对应的事件
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
//当按手指按下时的事件
case MotionEvent.ACTION_DOWN:
mDownX = event.getRawX();
mDownY = event.getRawY() - getStatusBarHeight();
mOnMoveX = event.getRawX();
mOnMoveY = event.getRawY() - getStatusBarHeight();
mInViewX = event.getX();
mInViewY = event.getY();
break;
//当手指移动时的事件
case MotionEvent.ACTION_MOVE:
mOnMoveX = event.getRawX();
mOnMoveY = event.getRawY() - getStatusBarHeight();
upDateFloatBallLocation();
break;
//当手指拿起时的事件
case MotionEvent.ACTION_UP:
// 如果手指离开屏幕时,xDownInScreen和xInScreen相等,且yDownInScreen和yInScreen相等,则视为触发了单击事件。
if (mOnMoveX == mDownX && mOnMoveY == mDownY) {

}
break;

}
return true;
}

private void upDateFloatBallLocation() {
mParams.x = (int)(mOnMoveX - mInViewX);
mParams.y = (int)(mOnMoveY - mInViewY);
mWindowManager.updateViewLayout(this, mParams);
}

//这个方法是获得状态栏的高度,可以不用深究
private int getStatusBarHeight() {
int statusBarHeight = 0;
if (statusBarHeight == 0) {
try {
Class<?> c = Class.forName("com.android.internal.R$dimen");
Object o = c.newInstance();
Field field = c.getField("status_bar_height");
int x = (Integer) field.get(o);
statusBarHeight = getResources().getDimensionPixelSize(x);
} catch (Exception e) {
e.printStackTrace();
}
}
return statusBarHeight;
}
}


接下来我们来说刚才没有解释的三个方法和mParams参数的含义

1:构造函数中的mParams的主要的参数含义

//初始化mParams
mParams = new WindowManager.LayoutParams();

/**
* 接下来三个不详细介绍,感性趣的同学可以去查官方api
*/
mParams.type = WindowManager.LayoutParams.TYPE_PHONE;
mParams.format = PixelFormat.RGBA_8888;
mParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;

//这个gravity属性是指定view的对齐方式,之前我们在位置坐标中所得左上角就是在这里定义的
mParams.gravity = Gravity.LEFT | Gravity.TOP;
//这个是设置view在桌面中的大小
mParams.height = view.getLayoutParams().height;
mParams.width = view.getLayoutParams().width;
//这个非常非常容易搞错!!!!这个是定义控件在桌面中的位置,这个位置是以view的左上角为原点的,而不是以这个view的中心作为原点!搞清楚这个你就可以理解之后的onTouchEvent中的一些不理解的地方
mParams.x = mWindowManager.getDefaultDisplay().getWidth();//让这个悬浮按钮的初始x坐标是最左边
mParams.y = mWindowManager.getDefaultDisplay().getHeight() / 2; //让这个悬浮按钮的初始y坐标是中间

2:onTouchEvent()
还记得之前的那几个坐标参数的含义吗

/**
* 记录当手指按下时的手指的坐标(坐标是以屏幕(包括状态栏)左上角为原点)
* 注意:手指必须按到这个自定义view
* 使用getRawX(), getRawY()
*/
private float mDownX;
private float mDownY;

/**
* 记录当手指移动时的手指的坐标(坐标是以屏幕(包括状态栏)左上角为原点)
* 注意:手指必须按到这个自定义view
* 使用getRawX(), getRawY()
*/
private float mOnMoveX;
private float mOnMoveY;

/**
* 记录当手指按下时的手指相对于view这个控件的坐标(坐标是以控件左上角为原点)
* 注意:手指必须按到这个自定义view
* 使用getX(), getY()
*/
private float mInViewX;
private float mInViewY;




public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
//当按手指按下时的事件
case MotionEvent.ACTION_DOWN:
mDownX = event.getRawX();
mDownY = event.getRawY() - getStatusBarHeight();
mOnMoveX = event.getRawX();
mOnMoveY = event.getRawY() - getStatusBarHeight();
mInViewX = event.getX();
mInViewY = event.getY();
break;
//当手指移动时的事件
case MotionEvent.ACTION_MOVE:
mOnMoveX = event.getRawX();
mOnMoveY = event.getRawY() - getStatusBarHeight();
upDateFloatBallLocation();
break;
//当手指拿起时的事件
case MotionEvent.ACTION_UP:
// 如果手指离开屏幕时,xDownInScreen和xInScreen相等,且yDownInScreen和yInScreen相等,则视为触发了单击事件。
if (mOnMoveX == mDownX && mOnMoveY == mDownY) {

}
break;

}
return true;
}

在这里我只想解释一下为什么要剪掉状态栏的高度,因为悬浮窗是不能移动到状态栏之上的,但是我们的getRawY的参考坐标原点是屏幕左上角,也是状态栏的左上角,因为移动不过去我们就把坐标原点改为ActionBar的左上角,所以剪掉了状态栏的高度


2:upDateFloatBallLocation()

private void upDateFloatBallLocation() {
mParams.x = (int)(mOnMoveX - mInViewX);
mParams.y = (int)(mOnMoveY - mInViewY);
//更新悬浮球位置函数
mWindowManager.updateViewLayout(this, mParams);
}

这里我解释下
mParams.x = (int)(mOnMoveX - mInViewX);
mParams.y = (int)(mOnMoveY - mInViewY);
这里回想刚才1中说明的那个问题,mParams.x和mParams.y是自定义view的位置,但是这个位置是以这个view的左上角的点作为原点的,如果mInViewX和mInViewY就是(0, 0),也就是view的左上角,那么mParams.x = mOnMoveX; mParams.y=mOnMoveY;
但是如果不是(0, 0)那么你想想是不是应该是
mParams.x = (int)(mOnMoveX - mInViewX);
mParams.y = (int)(mOnMoveY - mInViewY);
如果想不通,那就画图试试,注意getX, getRawX, getY和getRawY的区别



这里提醒下,mParams必须是WindowManager.LayoutParams而不是view的LayoutParams


源码地址:http://download.csdn.net/detail/szx584820/9808229
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要实现可自由移动、监听点击事件的悬浮窗,可以通过以下步骤来实现: 1. 在 AndroidManifest.xml 文件中添加悬浮窗权限: ```xml <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> ``` 2. 创建一个浮动窗口布局的 xml 文件,在其中添加一个可移动的 View 控件和必要的监听事件。 3. 在 Service 中创建 WindowManager 对象,并使用 addView() 方法将浮动窗口布局添加到 WindowManager 中。 4. 在浮动窗口布局的 View 控件中实现 onTouchEvent() 方法,监听触摸事件,并根据触摸事件的坐标改变浮动窗口的位置。 5. 在浮动窗口布局的 View 控件中实现 OnClickListener() 方法,监听点击事件,并执行相应的操作。 6. 在 Service 的 onDestroy() 方法中使用 removeView() 方法将浮动窗口布局从 WindowManager 中移除,释放资源。 下面是一个简单的实现代码示例: ```java public class FloatWindowService extends Service { private WindowManager mWindowManager; private View mFloatView; private WindowManager.LayoutParams mParams; @Override public void onCreate() { super.onCreate(); mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE); mFloatView = LayoutInflater.from(this).inflate(R.layout.float_window_layout, null); mParams = new WindowManager.LayoutParams( WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY : WindowManager.LayoutParams.TYPE_PHONE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT); mParams.gravity = Gravity.TOP | Gravity.LEFT; mParams.x = 0; mParams.y = 0; mWindowManager.addView(mFloatView, mParams); // 监听触摸事件,改变浮动窗口位置 mFloatView.setOnTouchListener(new View.OnTouchListener() { private int mLastX; private int mLastY; private int mDownX; private int mDownY; @Override public boolean onTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mLastX = mParams.x; mLastY = mParams.y; mDownX = (int) event.getRawX(); mDownY = (int) event.getRawY(); break; case MotionEvent.ACTION_MOVE: int moveX = (int) event.getRawX(); int moveY = (int) event.getRawY(); int dx = moveX - mDownX; int dy = moveY - mDownY; mParams.x = mLastX + dx; mParams.y = mLastY + dy; mWindowManager.updateViewLayout(mFloatView, mParams); break; } return false; } }); // 监听点击事件 mFloatView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // 执行相应的操作 } }); } @Override public void onDestroy() { super.onDestroy(); mWindowManager.removeView(mFloatView); } @Nullable @Override public IBinder onBind(Intent intent) { return null; } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值