ViewConfiguration. get(getContext()).getScaledTouchSlop()
VelocityTracker velocityTracker = VelocityTracker.obtain();
velocityTracker.addMovement(event);
//获取速度之前,需要先计算速度 这里的1000表示时间间隔,1000ms
velocityTracker.computeCurrentVelocity(1000);
int xVelocity = (int) velocityTracker.getXVelocity();
int yVelocity = (int) velocityTracker.getYVelocity();
最后,当不需要使用它的时候,需要调用clear方法来重置并回收内存:
velocityTracker.clear();
velocityTracker.recycle();
GestureDetector mGestureDetector = new GestureDetector(this);
//解决长按屏幕后无法拖动的现象
mGestureDetector.setIsLongpressEnabled(false);
//接着,接管目标View的onTouchEvent方法,在待监听View的onTouchEvent方法中添
加如下实现
boolean consume = mGestureDetector.onTouchEvent(event);
return consume;
做完了上面两步,我们就可以有选择地实现OnGestureListener和OnDoubleTapListener
中的方法了.
这里有一个建议供读者参考:如果只是监听滑动相关的,建议自己
在onTouchEvent中实现,如果要监听双击这种行为的话,那么就使用GestureDetector。
如何使用Scroller呢?它的典型
代码是固定的,如下所示
Scroller scroller = new Scroller(mContext);
// 缓慢滚动到指定位置
private void smoothScrollTo(int destX,int destY) {
int scrollX = getScrollX();
int delta = destX -scrollX;
// 1000ms内滑向destX,效果就是慢慢滑动
mScroller.startScroll(scrollX,0,delta,0,1000);
invalidate();
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
postInvalidate();
}
}
主要是操作View的translationX和translationY属性.
//View动画
val loadAnimation = AnimationUtils.loadAnimation(applicationContext, R.anim.animation_scroll)
btn_animation_scroll.startAnimation(loadAnimation)
//属性动画
btn_animation_scroll.animate().translationX(200f).translationY(200f).start()
val layoutParams = btn_layout_params.layoutParams as? FrameLayout.LayoutParams
layoutParams?.leftMargin = layoutParams?.leftMargin?.plus(100)
//或者btn_layout_params.setLayoutParams(params)
btn_layout_params.requestLayout()
原理: 调用startScroll()方法,其实里面并没有进行滑动,而是因为紧接着调用的invalidate(),导致View重绘.在View的draw方法里面又会去调用computeScroll方法.computeScroll是需要自己去实现的,computeScroll会去向Scroller获取当前的scrollX和scrollY(Scroller内部是根据时间去计算当前的scrollX和scrollY的值),然后又调用scrollTo,又invalidate(),又要重绘,如此反复.实现了弹性滑动.
View不断重绘,不断通过时间流逝来计算新的滑动位置,小幅度的滑动,最终形成了弹性滑动.
模仿Scroller来实现View的弹性滑动
final int startX = 0;
final int deltaX = 100;
ValueAnimator animator = ValueAnimator.ofInt(0,1).setDuration(1000);
animator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animator) {
float fraction = animator.getAnimatedFraction();
mButton1.scrollTo(startX + (int) (deltaX * fraction),0);
}
});
animator.start();
不断发生延时消息,不断scrollTo.发送消息无法精确地定时,系统的消息调度是需要时间的,并且所需时间不定.
主要是3个方法: dispatchTouchEvent(),onInterceptTouchEvent(),onTouchEvent()
三者关系伪代码
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean consume = false;
if (onInterceptTouchEvent(ev)) {
consume = onTouchEvent(ev);
} else {
consume = child.dispatchTouchEvent(ev);
}
return consume;
}
对于场景1,当用户左右滑动时,让外部的View拦截点击事件,当用户上下滑动时让内部的View拦截点击事件. 如何判断用户是上下还是左右滑动: 用2个按下点与抬起点的距离差判断,如果横向多,那么就是横向滑动,如果竖向多,就是竖向滑动.
对于场景2和3,需要根据具体的业务场景来判断当时应该让谁拦截事件.
抛开具体的场景,滑动冲突其实有一种通用的解决方式.
外部拦截法,就是指点击事件都先经过父控件的处理,如果父控件需要这个事件就拦截,然后自己处理.不需要就不拦截,传递给子控件.
它的伪代码如下所示:
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean intercepted = false;
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
intercepted = false;
break;
}
case MotionEvent.ACTION_MOVE: {
if (父容器需要当前点击事件) {
intercepted = true;
} else {
intercepted = false;
}
break;
}
case MotionEvent.ACTION_UP: {
intercepted = false;
break;
}
default:
break;
}
mLastXIntercept = x;
mLastYIntercept = y;
return intercepted;
}