最近再写一个下拉的scrollview,里边要处理比较复杂的手势,发现自己这方面很迷茫,于是自己用log总结了一下
一个scrollview有
dispatchTouchEvent
onInterceptTouchEvent
OnTouchListener(已设置)
onTouchEvent
fling
onScrollChanged
这些手势的顺序是
1)public boolean dispatchTouchEvent(MotionEvent ev) 这个方法用来分发TouchEvent
2)public boolean onInterceptTouchEvent(MotionEvent ev) 这个方法用来拦截TouchEvent
3)public boolean onTouchEvent(MotionEvent ev) 这个方法用来处理TouchEvent
1、如果dispatchTouchEvent返回true ,则交给这个view的onTouchEvent处理, 如果最终需要处理事件的view的onTouchEvent()返回了false,那么该事件将被传递至其上一层次的view的onTouchEvent()处理。如果最终需要处理事件的view 的onTouchEvent()返回了true,那么后续事件将可以继续传递给该view的onTouchEvent()处理。
2、如果dispatchTouchEvent返回 false ,则交给这个 view的interceptTouchEvent方法来决定是否要拦截这个事件,如果 interceptTouchEvent 返回 true ,也就是表示拦截掉了,则交给它的 onTouchEvent 来处理,
3、如果 interceptTouchEvent 返回 false ,那么就传递给子view ,由子 view 的 dispatchTouchEvent 再来开始这个事件的分发。
4、如果事件传递到某一层的子 view 的onTouchEvent 上了,这个方法返回了 false ,那么这个事件会从这个view 往上传递,由父view的 onTouchEvent 来接收。
5、如果传递到最上面的 onTouchEvent 也返回 false 的话,这个事件就会“消失”,而且接收不到下一次事件。
但是如果设置了ontouchelistener之后呢?
onTouchListener的接口的优先级是要高于onTouchEvent的,假若onTouchListener中的onTouch方法返回true,表示此次事件已经被消费了,那onTouchEvent是接收不到消息的。
接着是fling方法,要注意的是,一定要在scrollview的ontouevent中调用父类的ontouchevent方法,要不然scrollview不能滑动,查看源码可以知道,在这里面调用了scrollview的fling方法,fling方法只执行一遍,fling意味“甩”,意思就是当用户大力向下滑,接着松手,就会调用fling函数,参数为应该滑动的距离
case MotionEvent.ACTION_UP: if (mIsBeingDragged) { final VelocityTracker velocityTracker = mVelocityTracker; velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId); if (getChildCount() > 0) { if ((Math.abs(initialVelocity) > mMinimumVelocity)) { fling(-initialVelocity); } else { if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) { postInvalidateOnAnimation(); } } } mActivePointerId = INVALID_POINTER; endDrag(); } break;
最后是onscrollchanged的方法,这是再ontouchevent里面的代码
// Calling overScrollBy will call onOverScrolled, which // calls onScrollChanged if applicable. if (overScrollBy(0, deltaY, 0, mScrollY, 0, range, 0, mOverscrollDistance, true)) { // Break our velocity if we hit a scroll barrier. mVelocityTracker.clear(); }
可见onscrollchanged和fling方法都是再scrollview的ontouchevent的方法里面调用的,如果ontouchevent方法中不调用父类的ontouchevent方法,则onscrollchanged方法和ontouchevent方法都不会调用的
关于点击事件的机制,这里给出一些结论,根据这些结论可以更好地理解整个传递机制,如下所示:
(1)同一个事件序列是指从手指接触屏幕的那一刻起,到手指离开屏幕的那一刻结束,在这个过程中所产生的一系列的事件,这个事件序列以down事件开始,中间含有数量不定的move事件,可以为0,最终以up事件结束。
(2)正常情况下,一个事件序列只能被一个View拦截且消耗。这一条的原因可以参考(3),因为一旦一个元素拦截了某些事件,那么同一个事件序列内的所有事件都会直接交给它处理,因此同一个事件序列中的事件不能分别由两个View同时处理,但是通过特殊手段可以做到,比如一个View将本该自己处理的事件通过onTouchEvent强行传递给其他View处理。
(3)某个View一旦决定拦截,那么这一个事件序列都只能由它来处理(如果事件序列能够传递给它的话),并且它的onInterceptTouchEvent不会被调用。这条也很好理解,就是说当一个View决定拦截一个事件后,那么系统会把同一个事件序列内的其他方法都直接交给它来处理,因此就不用再调用这个View的onInterceptTouchEvent去询问它是否要拦截了。
(4)某个View一旦开始处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回了false),那么同一事件序列中的其他事件都不会再交给它来处理,并且事件将重新交由它的父元素去处理,即父元素的onTouchEvent会被调用。意思就是事件一旦交给一个View处理,那么它就必须消耗掉,否则同一事件序列中剩下的事件就不再交给它来处理了。
(5)如果View不消耗除ACTION_DOWN以外的其他事件,那么这个点击事件会消失,此时父元素的onTouchEvent并不会被调用,并且当前View可以持续收到后续的事件,最终这些消失的点击事件会传递给Activity处理。
(6)ViewGroup默认不拦截任何事件,ViewGroup源码的onInterceptTouchEvent方法默认返回false。
(7)View没有onInterceptTouchEvent方法,一旦有点击事件传递给它,那么它的onTouchEvent方法就会被调用。
(8)View的onTouchEvent默认都会消耗事件(返回true),除非它是不可点击的(clickable和longclickable同时为false)。View的longClickable属性默认都是false,cilckable属性要分情况,比如Button的clickable属性默认为true,而TextView的clickable属性默认为false。
(9)View的enable属性不影响onTouchEvent的默认返回值。哪怕一个View是disable状态的,只要它的clickable或者longclickable有一个为true,那么它的onTouchEvent就返回true。
(10)onClick会发生的前提是当前View是可点击的,并且它收到了down和up事件。
(11)事件传递过程是由内向外的,即事件总是先传递给父元素,然后再由父元素分发给子View,通过requestDisallowInterceptTouchEvent方法可以在子元素中干预父元素的事件分发过程,但是ACTION_DOWN事件除外。
更新:今天写一个东西的时候,发现在ontouchevent里面捕捉不到down事件,后来找到原因是由于ontouchevent的函数又从子控件向父控件的方向执行,当子控件的down事件返回true,将down事件截取,但是后续事件返回false或者recycle,接着父控件就捕捉到了这个触摸事件的后续事件,当然是残缺的,所以这类事件最好应该再oninterceptTouchevent或者dispatchtouchevent里面执行
更新:父控件通过oninterceptTouchEvent截取了这个touchevent(注意,之后oninterceptTouchEvent返回true之后就不会再次调用了),那么以后的touchevent就会由父控件的ontouchevent进行处理,但是此时如果父控件的ontouchevent返回false,那么这个touchevent就交给再上一层的父控件进行处理,如果上一层控件不处理,则改touchevent直接抛弃
更新:如果子控件的onTouchevent返回true,代表要截取这个touchevent以后的所有action,但是父控件还是可以通过oninterceptTouchevent来决定截取否,但是再实现过程中发现了一个问题,就是父控件的oninterceptTouchevent不执行,这就是因为子控件调用了getParent().requestDisallowInterceptTouchEvent(true)方法.一旦底层View收到touch的action后调用这个方法那么父层View就不会再调用onInterceptTouchEvent了,也无法截获以后的action(这类控件:继承absListView的子view)