一直很好奇,View 点击&触摸事件它们触发的优先级是怎么样,哪个先执行,哪个后执行,返回只true、false对后续执行有什么影响呢?主要分析onClick,onLongClick,onTouch,onTouchEvent这4个函数,有兴趣的可以一起往下看。
源码分析基于Android-23
简述作用
- onTouch&onTouchEvent 都是可以监听Touch事件,区别在于,前者是通过setOnTouchListener进行注册,后者只要重写即可;
- onClick 监听点击事件;
- onLongClick 监听长按事件。
OnTouchListener&onTouchEvent的调用关系
//View类中
public boolean dispatchTouchEvent(MotionEvent event) {
//省略部分代码
if (onFilterTouchEventForSecurity(event)) {
//省略部分代码
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {//1
result = true;
}
if (!result && onTouchEvent(event)) {//2
result = true;
}
}
//省略部分代码
return result;
}
- 注释1:当设置了setOnTouchListener,那么li.mOnTouchListener就不为空,li.mOnTouchListener的onTouch函数就会被触发;
- 注释2:如果li.mOnTouchListener的onTouch函数被触发并返回ture,那么重写的onTouchEvent就不会被触发;
onTouch&onTouchEvent的调用关系小结
- li.mOnTouchListener的onTouch函数没有比触发或返回false,那么重写onTouchEvent才会被触发;
- mOnTouchListener的onTouch方法优先级比onTouchEvent高;
流程图
onClick&onLongClick&onTouchEvent的调用关系
//View类中
public boolean onTouchEvent(MotionEvent event) {
......
switch (action) {
case MotionEvent.ACTION_UP:
......
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {//3
removeLongPressCallback();
......
performClick();
}
......
break;
case MotionEvent.ACTION_DOWN:
......
if (isInScrollingContainer) {//1
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
checkForLongClick(0);//2
}
break;
case MotionEvent.ACTION_CANCEL:
removeTapCallback();//当发生canel,则移除判断点击任务
removeLongPressCallback();//当发生canel,则移除判断长按任务
......
break;
case MotionEvent.ACTION_MOVE:
......
if (!pointInView(x, y, mTouchSlop)) {
removeTapCallback();//当发生move事件,则移除判断tap的任务
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
......
removeLongPressCallback();//当发生move事件,则移除判断长按任务
}
}
break;
}
return true;
}
return false;
}
- 注释2:先分析这里点,假设isInScrollingContainer为false,则发送500毫秒延时任务执行长按;
- 注释1:当View被滑动控件包裹,则isInScrollingContainer为true,发送100毫秒的延时任务来判断判断是tap事件还是滑动事件,100毫秒内没有任何滑动,则发送400毫秒延时任务执行长按;
- 注释3:如果onLongClick没有被触发或者返回false,mHasPerformedLongPress 则为false,将移除长按任务,接着执行performClick,通过从ViewRootImpl得到的handle往主线程队列发送点击任务(主要是触发li.mOnClickListener.onClick),等待被执行;
OnClickListener&OnLongClickListenerOnTouchEvent&onTouchEvent的调用关小结
- down事件,View被滑动控件包裹,发送100毫秒的延时任务来判断是tap事件还是滑动事件,接着发送400毫秒的延时任务判断是是否长按;
- 如果onLongClick被触发并返回true,那么onClick则不会被触发;
被滚动控件包裹,为什么要先发送100毫秒的延时任务呢?
重点是要区分tap(点击或长按)、滚动,如果是点击事件,需要给View设置一个按下的效果,但如果这个事件最终是滚动事件,那么先有按下后再有松开,用户体验显示是不合理的,所以有这样的判断。
流程图
总结
- OnTouchListener onTouch函数优先级比onTouchEvent高;
- 当onTouchEvent被触发,onClick、onLongClick才有可能被执行,
- 当onLongClick消费了事件,那么onClick将无法执行;
- 点击事件实现,在up事件时,利用ViewRootImpl构建的handler,往主线程MessageQueue发一个runnable点击任务,等待被分发执行;
- 长按事件实现,发送100毫秒延时任务判断是tap还是滑动,如果是tap,则再发送400毫秒延时任务执行长按;
有分析不对的地方请指出,互相学习,谢谢哦!