通过onTouchEvent实现一个简单的跟随触摸点移动的圆
public class CircleFollowView extends View {
private static final String Tag = "CircleFollowView";
private float currentX = 50;
private float currentY = 50;
private int radius = 50;
// 控件宽度
private int mWidth;
// 控件高度
private int mHeight;
private int lastX;
private int lastY;
public CircleFollowView(Context context) {
super(context);
}
public CircleFollowView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CircleFollowView(Context context, AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
private void initView() {
// 取最小的长度的一半作为半径
// layout中View要设定绝对大小,例如具体dp或者match_patent,否则点击屏幕其他地方也可能移动圆
this.radius = (mWidth / 2 < mHeight / 2) ? mWidth / 2 : mHeight / 2;
this.currentX = mWidth / 2;
this.currentY = mHeight / 2;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthMode == MeasureSpec.EXACTLY) {
mWidth = widthSize;
} else if (widthMode == MeasureSpec.AT_MOST) {
throw new IllegalArgumentException(
"width must be EXACTLY,you should set like android:width=\"200dp\"");
}
if (heightMode == MeasureSpec.EXACTLY) {
mHeight = heightSize;
} else if (widthMeasureSpec == MeasureSpec.AT_MOST) {
throw new IllegalArgumentException(
"height must be EXACTLY,you should set like android:height=\"200dp\"");
}
setMeasuredDimension(mWidth, mHeight);
}
@SuppressLint("DrawAllocation")
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
initView();
Paint p = new Paint();
p.setColor(Color.RED);
canvas.drawCircle(currentX, currentY, radius, p);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// 触摸点到屏幕左上角的距离
int rawX = (int) event.getRawX();
int rawY = (int) event.getRawY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = rawX;
lastY = rawY;
break;
case MotionEvent.ACTION_MOVE:
int offsetX = rawX - lastX;
int offsetY = rawY - lastY;
offsetLeftAndRight(offsetX);
offsetTopAndBottom(offsetY);
lastX = rawX;
lastY = rawY;
break;
case MotionEvent.ACTION_UP:
lastX = rawX;
lastY = rawY;
break;
}
return true;
}
}
上面一种方式是通过修改View距离屏幕的位置实现滑动,下面使用Scroller(其他内容相同,只展示onTouchEvent部分):
int offsetX;
int offsetY;
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = (int) event.getX();
lastY = (int) event.getY();
break;
case MotionEvent.ACTION_MOVE:
offsetX = x - lastX;
offsetY = y - lastY;
((View) getParent()).scrollBy(-offsetX, -offsetY);
invalidate();
break;
default:
break;
}
return true;
}
scroll的位移从左到右为负值,从右到左是正值,从上到下是负值,从下到上是正值,所以使用scrollBy时参数取负。
((View) getParent()).scrollBy(-offsetX, -offsetY);如果替换为
View viewGroup = ((View) getParent());
mScroller.startScroll(viewGroup.getScrollX(), viewGroup.getScrollY(), -offsetX, -offsetY,1000);//1s完成滑动
需要配合
public void computeScroll() {
super.computeScroll();
if (mScroller.computeScrollOffset()) {
((View) getParent()).scrollTo(mScroller.getCurrX(),
mScroller.getCurrY());
// 通过重绘来不断调用computeScroll
invalidate();
}
}
通过invalidate()->onDraw()->computeScroll()一部分一部分重绘实现滑动,但是不能实时,可以实现一种延迟的滑动效果。
scrollTo/scrollBy这种方式能比较方便的实现滑动效果并且不影响点击时间,但是他只能滑动View的内部,所以上面代码中滑动使用的是getParent(),实际运用时,滑动的部分最好单独写在一个ViewGroup中,否则父组件内的所有内容会一起滑动。
第三种修改LayoutParams,向左移动,就增加marginLeft:
public boolean onTouchEvent(MotionEvent event) {
// 触摸点到屏幕左上角的距离
int rawX = (int) event.getRawX();
int rawY = (int) event.getRawY();
VelocityTracker velocityTracker = VelocityTracker.obtain();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = rawX;
lastY = rawY;
break;
case MotionEvent.ACTION_MOVE:
int offsetX = rawX - lastX;
int offsetY = rawY - lastY;
MarginLayoutParams layoutParams = (MarginLayoutParams) getLayoutParams();
layoutParams.leftMargin += offsetX;
layoutParams.topMargin += offsetY;
requestLayout();//或者setLayoutParams(layoutParams);
lastX = rawX;
lastY = rawY;
break;
case MotionEvent.ACTION_UP:
lastX = rawX;
lastY = rawY;
break;
}
return true;
}
最后,如果滑动的效果不是实时的,也可以采用动画的方式,动画比较适合复杂的效果和没有交互的View。