什么是事件分发,分开解释即可,MotionEvent事件,当点击了,怎样处理,就是分发。
下面3个方法来共同完成,
- dispatchTouchEvent:用来进行事件的分发,如果事件能够传递给当前View,那么此方法一定会被调用,返回结果受当前View的onTouchEvent和View的dispatchTouchEvent方法的影响,表示是否当消耗当前事件
public boolean dispatchTouchEvent(MotionEvent ev){
boolean consume = false;
if(onInterceptTouchEvent(ev)){
consume = onTouchEvent(ev);
}else{
consume = child.dispatchTouchEvent(ev);
}
return consume;
}
- onInterceptTouchEvent:用来判断是否拦截某个事件,如果当前View拦截了某个事件,那么在同一个事件序列当中,此方法不会被再次调用,返回结果表示是否拦截当前事件;
- onTouchEvent:在dispatchTouchEvent方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前View无法再次接收到事件。
1。弹性滑动的实现
使用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(); } }
startScroll主要是用来开启滚动,startX和startY是滑动的起点,dx和dy是滑动的距离,duration是滑动时间。并且必须注意的是,仅仅调用startScroll方法是无法让View开始滑动的,因为它内部并没有做相关的滑动,仅仅是保存了我们传递的几个参数。那么View到底要怎么滑动呢?其实就是在调用该方法时,应该再调用invalidate方法,即重绘。重绘的时候则会导致View中的draw方法执行,draw方法中又会去调用computeScroll方法。遗憾的是computeScroll方法是一个空实现,因此需要我们自己去实现该方法。但是应该注意的是,在computeScroll方法中,我们应该调用View的postInvalidate方法来进行第二次重绘,和第一次重绘一样,也是为了导致computeScroll方法被调用,如此反复,知道滑动结束。
computeScroll方法也比较容易理解,它的代码格式一般都是固定的,主要是判断滑动是否完成,如果没有完成就去向Scroller获取当前的scrollX和scrollY,然后通过scrollTo实现滑动。最后再调用postInvalidate来让滑动持续进行。
首先startScroll开启滑动,并调用invalidate方法进行重绘。当View重绘后会在draw方法中调用computeScroll,而computeScroll又去向Scroller获取当前的scrollX和scrollY,然后通过scrollTo实现滑动。接着又调用postInvalidate进行第二次重绘,和第一次重绘一样,也是为了导致computeScroll方法被调用。然后继续向Scroller获取当前的scrollX和scrollY,并通过scrollTo滑动到最新的位置,如此反复,知道整个滑动结束。
弹性滑动的另外一种实现,
动画本身就是一种弹性滑动,所以对于动画实现的滑动不去专门讨论瞬间滑动和弹性滑动。通过动画让View进行滑动,其实质就是让View进行平移,其主要是操作translationX和translationY属性
R.anim.translate :
<?xml version="1.0" encoding="utf-8"?><set xmlns:android="http://schemas.android.com/apk/res/android" android:fillAfter="true" android:zAdjustment="normal"> <translate android:duration="2000" android:fromXDelta="0" android:fromYDelta="0" android:interpolator="@android:anim/linear_interpolator" android:toXDelta="200" android:toYDelta="200" /></set>
//加载动画 animation = AnimationUtils.loadAnimation(this, R.anim.translate);
mImageView.startAnimation(animation);
2。View的滑动冲突
产生:界面中存在内外两层同时可以滑动的View
外部拦截法和内部拦截法
-
外部拦截法:事件都先经过父容器的拦截处理,如果父容器需要此事件就拦截,如果不需要就不拦截。外部拦截法需要重写父容器的onInterceptTouchEvent方法
//分别记录上次滑动的坐标(onInterceptTouchEvent) private int mLastXIntercept = 0; private int mLastYIntercept = 0; 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: int deltaX = x - mLastXIntercept; int deltaY = y - mLastYIntercept; if(父容器需要当前点击事件){ intercepted = true; }else{ intercepted = false; } break; case MotionEvent.ACTION_UP: intercepted = false; break; default: break; } mLastXIntercept = x; mLastYIntercept = y; return intercepted; }
上述代码是外部拦截法的典型逻辑,针对不同的滑动冲突只需修改“父容器需要当前点击事件”这个条件即可
-
内部拦截法:指父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就消费,否则就交由父容器进行处理。这种方法需要配合requestDisallowInterceptTouchEvent方法才能正常工作
//分别记录上次滑动的坐标 private int mLastX = 0; private int mLastY = 0; public boolean dispatchTouchEvent(MotionEvent event){ int x = (int)event.getX(); int y = (int)event.getY(); switch(event.getAction()){ case MotionEvent.ACTION_DOWN: parent.requestDisallowInterceptTouchEvent(true); break; case MotionEvent.ACTION_MOVE: int deltaX = x - mLastX; int deltaY = y - mLastY; if(父容器需要此类点击事件){ parent.requestDisallowInterceptTouchEvent(false); } break; case MotionEvent.ACTION_UP: break; default: break; } mLastX = x; mLastY = y; return super.dispatchTouchEvent(event); }
上述代码是内部拦截法的典型代码,面对不同的滑动策略时只需要更改“父容器需要此类点击事件”这个条件即可
父元素所做的修改如下
public boolean onInterceptTouchEvent(MotionEvent event){ int action = event.getAction(); if(action == MotionEvent.ACTION_DOWN){ return false; }else{ return true; } }