我们知道View的绘制有三个重要的过程分别是Measure,Layout,Draw.Measure负责测量View的尺寸,Layout负责定位View的位置,Draw负责把View绘制到图片上。我们这就是通过Layout重新定位View。
public class CustomMoveView extends View {
private float lastX = 0, lastY = 0;//记录上一次点击 的点的坐标
private int widthPixels, heightPixels;//手机屏幕的宽、高,用于处理越界
public CustomMoveView(Context context) {
super(context);
}
public CustomMoveView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
DisplayMetrics dm2 = getResources().getDisplayMetrics();
widthPixels = dm2.widthPixels;//屏幕宽度
heightPixels = dm2.heightPixels;//屏幕高度
}
public CustomMoveView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public void computeScroll() {
super.computeScroll();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
/**
* 记录第一次点击点的坐标
* getRawX()、getRawY()触摸点相对屏幕左上角 的位置坐标
*
**/
lastX = event.getRawX();
lastY = event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
int offsetX = (int) (event.getRawX() - lastX);//X轴方向上的偏移量
int offsetY = (int) (event.getRawY() - lastY);//Y轴方向上的偏移量
/**
*way 1 :
*layout(r,t,l,b) 四个参数 左上角 和右下角的坐标
* getLeft()获取此view左边缘到父view的距离
* getTop()获取此view上边缘到父view的距离
* getRight()获取此view右边缘到父view的距离
* getBotton()获取此view下边缘到父view的距离
**/
layout(getLeft() + offsetX, getTop() + offsetY, getRight() + offsetX, getBottom() + offsetY);
/**
* 距离当前点的位置,便于计算下次偏移量
* **/
lastX = event.getRawX();
lastY = event.getRawY();
/**
way 2 :
**/
// offsetLeftAndRight(offsetX);
// offsetTopAndBottom(offsetY);
break;
case MotionEvent.ACTION_UP:
int owenW = getWidth();//自身的长度
int owenY = getHeight();//自身的高度
int left = getLeft(), top = getTop(), rignt = getRight(), bottom = getBottom();
//
// 判断是否越界,如果移出屏幕,则抬起时让View自动回到屏幕内
if (getLeft() < 0) {
left = 0;
rignt = owenY;
}
if (getTop() < 0) {
top = 0;
bottom = owenY;
}
if (getRight() > widthPixels) {
left = widthPixels - owenW;
rignt = widthPixels;
}
if (getBottom() > heightPixels) {
top = heightPixels - owenY;
bottom = heightPixels;
}
layout(left, top, rignt, bottom);
break;
}
return true;
}
}
代吗很简单,相关方法都有注释。来看下用法,像TextView一样在xml使用一样
<com.jsh.frame.myframe2.wightview.CustomMoveView
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginRight="34dp"
android:layout_marginTop="90dp"
android:background="@color/red_500"
/>
这里想说一下getX()与getRawX()的区别。
MotionEvent下面有两个方法:
getRawX(); //触摸点相对于屏幕的x坐标。
getX(); //你第一个手指触摸点相对于触摸的控件本身的x坐标。如果你两个手指下去,想获取第二的触摸位置就调用get(1),后面依次类推。
上面onTouchEvent中用getRawX()替换成getX(),代码如下:
`
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
/**
* 记录第一次点击点的坐标
**/
lastX = event.getX();
lastY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
int offsetX = (int) (event.getX() - lastX);//X轴方向上的偏移量
int offsetY = (int) (event.getY() - lastY);//Y轴方向上的偏移量
layout(getLeft() + offsetX, getTop() + offsetY, getRight() + offsetX, getBottom() + offsetY);
break;
case MotionEvent.ACTION_UP:
int owenW = getWidth();//自身的长度
int owenY = getHeight();//自身的高度
int left = getLeft(), top = getTop(), rignt = getRight(), bottom = getBottom();
/**判断是否越界**/
if (getLeft() < 0) {
left = 0;
rignt = owenY;
}
if (getTop() < 0) {
top = 0;
bottom = owenY;
}
if (getRight() > widthPixels) {
left = widthPixels - owenW;
rignt = widthPixels;
}
if (getBottom() > heightPixels) {
top = heightPixels - owenY;
bottom = heightPixels;
}
layout(left, top, rignt, bottom);
break;
}
return true;
}
`
与getRawX不同就是 每次Layout 后不用重新赋值 lastX、lastY.这是因为getRawX计算的偏移量是相对屏幕的,getX计算的偏移量是相对view本身的。比容下图中 view水平向右移动两次,触摸点从A移动到B再移动到C。A—>B : getRawX计算的偏移量是getRawX(b)-getRawX(a);B—>C : getRawX计算的偏移量是getRawX(c)-getRawX(b)所以每次layout后lastX要重新赋值,这很好理解。getX 是获取相对控件本身坐标值,B相对屏幕的坐标是(50,10),Layout完成后,B相对View的坐标就变成了(10,10),
C相对屏幕的坐标是(90,10),Layout完成后,C相对View的坐标也变回了(10,10),当Layout完成后,上一次的参考点坐标自己已经改变了,也就不用我们再去给他赋值了(我第一次看这里有点犯模糊,这里记录一下我自己的理解过程,脑子灵活的同学可以绕过)