想要做一个简单的像悬浮球那样的能在手机桌面上随意拖动的效果,首先你需要知道
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 {
private float mDownX;
private float mDownY;
private float mOnMoveX;
private float mOnMoveY;
private float mInViewX;
private float mInViewY;
private WindowManager.LayoutParams mParams;
private WindowManager mWindowManager;
public FloatBall(Context context) {
super(context);
LayoutInflater.from(context).inflate(R.layout.small_floatball, this);
View view = findViewById(R.id.small_window_layout);
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
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;
mWindowManager.addView(this, mParams);
}
@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();
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()
还记得之前的那几个坐标参数的含义吗
private float mDownX;
private float mDownY;
private float mOnMoveX;
private float mOnMoveY;
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