触摸事件相关方法
方法 | 中文简称 | 作用 | 被谁调用 | ViewGroup专有 | 覆盖 | 调用 |
dispatchTouchEvent
| 分发 | 找到合适的View(自己或是孩子)处理触摸事件 | 父控件的dispatchTouchEvent,具体是dispatchTransformedTouchEvent |
| 99%不覆盖,覆盖也会调用super的此方法 | 99.9%不调用 |
onIntersetTouchEvent | 拦截 | 返回true代表交由自己处理,返回false正常分发 | 自己的dispatchTouchEvent | 是 | 可能覆盖。 一般需要判断触摸事件进行相应的返回,而不是直接返回true或false | 不调用 |
onTouchEvent | 处理 | 对用户触摸做出反馈 | VG:dispatchTransformedTouchEvent View:dispatchTouchEvent |
| 自定义控件通常会覆盖此方法 | 特殊情况会调用 |
setOnTouchListener | 设置触摸监听 | 在控件的外部获得触摸事件 | 开发人员调用 |
| 覆盖监听的onTouch方法 | 需要获知控件上的触摸事件 |
dispatchTransformedTouchEvent | 分发变形事件 | 对触摸事件的位置进行转换 | 自己的dispatchTouchEvent | 是 | 私有方法不能覆盖 | 私有方法不能调用 |
performClick | 触发点击事件 | 触发点击监听的onClick方法 | View的 onTouchEvent |
| 不覆盖 | 模拟用户点击 |
requestDisallowInterceptTouchEvent | 要求不拦截 | 要求控件不拦截触摸事件 | 开发人员调用 | 是 | 不覆盖 | 如果不想被父控件抢夺触摸事件,需要调用父控件的此方法 |
特殊说明
方法 | 要点 | ||||||||||||||||
dispatchTouchEvent(View) | ① 检查是否有触摸监听,如果有则进②,如果没有则进入④ ② 调用触摸监听的onTouch方法,如果onTouch方法返回true,则进入③,如果false,则进入④ ③ 不会调用自己的onTouchEvent方法,返回true,代表分发成功,结束 ④ 调用自己的onTouchEvent方法,如果返回true则代表分发成功,结束。如果返回false,则分发失败,结束 | ||||||||||||||||
dispatchTouchEvent(ViewGroup) | ① 检查是否被调用过requestDisallowInterceptTouchEvent方法[1],如果是则进入③,如果否则进入②, ② 调用自己的onInterceptTouchEvent 检查返回值,如果true进入④;false 进入③ ③ 倒序遍历孩子(没有孩子,进入④,有孩子,进入(3.1) ④ 调用父类的dispatchTouchEvent,也就是View的dispatchTouchEvent方法,结束
对于一组触摸事件: 1 当一组触摸事件的第一个事件,分发成功的话,会记录下来,在分发下一个触摸事件的时候,就不会进行遍历查找了 2 如果原来分发给某个孩子在处理了,但onIntersetTouchEvent根据情况返回true了,那么会制作一个CANCEL类型的触摸事件分发给孩子(孩子会递归分发给孙子),代表这一组触摸事件结束了,没有后续事件了。
| ||||||||||||||||
dispatchTransformedTouchEvent | 1 只有ViewGroup才有,而且是一个私有方法,不能覆盖或调用 2 dispatchTouchEvent(ViewGroup)的③和④,都是通过此方法间接实现· 3 如果此方法的第三个参数View如果是null,则会调用自己的onTouchEvent方法,如果是不是null,测调用View的 4 在调用孩子的dispatchTouchEvent时,会根据滚动和孩子的位置进行坐标的偏转 | ||||||||||||||||
onInterceptTouchEvent | 1 应该根据情况返回true,比如ViewPager会判断是偏向于水平滑动,并且滑动达到了一定距离;再比如ScrollView会检测是垂直的移动,才会拦截。 | ||||||||||||||||
onTouchEvent | 1返回ture,则代表期望持续获得到触摸事件 2 up类型的事件表示时间结束了,cancel类型的事件也是一样 | ||||||||||||||||
performClick | 这个方法会在View的onTouchEvent的up事件中被调用 当你想模拟用户点击某个View,可以调用View上的performClick方法 | ||||||||||||||||
requestDisallowInterceptTouchEvent | 1 此方法会在mGroupFlags(用二进制来保存多种状态)添加上FLAG_DISALLOW_INTERCEPT,在分发的时候会检查是否具有此 FLAG_DISALLOW_INTERCEPT 2 此方法会递归调用父控件的此方法 3 部分ViewGroup可能会重写此方法,导致在某些情况下分发触摸事件仍然会抢夺孩子的触摸事件,比如SwipeRefreshLayout。 | ||||||||||||||||
setOnTouchListener |
|
关键代码
方法 |
|
dispatch TouchEvent (View) | boolean result = false; if (onFilterTouchEventForSecurity(event)) { //各种各样监听的集合 ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null // 检查是否具有触摸监听 && (mViewFlags & ENABLED_MASK) == ENABLED // 检查是否enable && li.mOnTouchListener.onTouch(this, event) // 调用触摸监听的onTouch方法 ) { // 如果onTouch方法返回true,则分发结果是true,否则用onTouchEvent的返回值作为结果 result = true; } if (!result && onTouchEvent(event)) {// 如果onTouch方法返回true,则不会调用onTouchEvent了 result = true; } } return result;
|
dispatch TouchEvent (ViewGroup) | // 是否处理,这个是返回值 boolean handled = false; if (onFilterTouchEventForSecurity(ev)) { if (actionMasked == MotionEvent.ACTION_DOWN) { // 清除上一组触摸事件遗留的各种信息 cancelAndClearTouchTargets(ev); resetTouchState(); } // 检查是否拦截 final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { // 检查是否不允许拦截 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) {// 如果允许拦截则会调用 onInterceptTouchEvent intercepted = onInterceptTouchEvent(ev); } else { intercepted = false; } } else { intercepted = true; } boolean alreadyDispatchedToNewTouchTarget = false; if (!canceled && !intercepted) { // 如果没有取消,也没有拦截,则进行正常的分发 if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { // 各种down事件才遍历孩子 final int childrenCount = mChildrenCount; if (newTouchTarget == null && childrenCount != 0) { final View[] children = mChildren; for (int i = childrenCount - 1; i >= 0; i--) { final View child = (preorderedList == null) ? children[childIndex] : preorderedList.get(childIndex); // 各种跳出循环和下一次循环 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { //这个方法内部会把需要消费触摸事件的孩子记录为 mFirstTouchTarget newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } } } } } // 如果孩子都不要触摸事件,则自己处理,注意dispatchTransformedTouchEvent的第三个参数是null if (mFirstTouchTarget == null) { handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { TouchTarget target = mFirstTouchTarget; while (target != null) { final TouchTarget next = target.next; // down已经分发过了,不再重发分发 if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { // 这里分发的是除down类型的各种触摸事件 if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } } target = next; } } } return handled; |
dispatch Transformed TouchEvent | // 注意第三个参数 private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { final boolean handled; // 发送取消类型事件,对于取消类型事件,位置不重要,所以不需要进行偏移 final int oldAction = event.getAction(); if (cancel || oldAction == MotionEvent.ACTION_CANCEL) { event.setAction(MotionEvent.ACTION_CANCEL); if (child == null) { handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); } event.setAction(oldAction); return handled; } // 部分代码跟多点触摸相关,重点关注child = null 调用父类View的分发方法, // 不然会对触摸事件中的位置进行偏移再调用孩子的分发方法,这就是此方法名称的核心 if (/*...*/) { if (/*...*/) { if (child == null) { handled = super.dispatchTouchEvent(event); } else { //根据滚动和孩子的位置对触摸事件进行偏移, final float offsetX = mScrollX - child.mLeft; final float offsetY = mScrollY - child.mTop; event.offsetLocation(offsetX, offsetY); // 发送偏移后的触摸事件,保证发送给孩子的触摸事件的x和y是相对于其左上角的位置 handled = child.dispatchTouchEvent(event); // 偏移回来,保证在后续的处理中位置是对的 event.offsetLocation(-offsetX, -offsetY); } return handled; } } // 逻辑与上相同,略 return handled; }
|
request Disallow Intercept TouchEvent | // 检查上次是否已经设置过一样的flag,如果重复就及时返回,不处理 if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) { // We're already in this state, assume our ancestors are too return; } if (disallowIntercept) {// 如果不允许(即调用此方法传入的是true,则给 mGroupFlags 添加上 FLAG_DISALLOW_INTERCEPT mGroupFlags |= FLAG_DISALLOW_INTERCEPT; } else { // 如果允许,则给 mGroupFlags 删除掉 FLAG_DISALLOW_INTERCEPT mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; } // 将会递归调用父控件的此方法 if (mParent != null) { mParent.requestDisallowInterceptTouchEvent(disallowIntercept); } |
onTouchEvent(View) | //... |
setOn Touch Listener | // 把ListenerInfo中的对象赋值称传入的监听对象
|
performClick | final boolean result; final ListenerInfo li = mListenerInfo; // 检查是否有点击监听 if (li != null && li.mOnClickListener != null) { // 播放音效 playSoundEffect(SoundEffectConstants.CLICK); // 调用点击监听的onClick方法 li.mOnClickListener.onClick(this); result = true; } else { result = false; } sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); return result;
|