View事件分发机制
来源:互联网 发布:手机上头像源码怎么用 编辑:程序博客网 时间:2024/05/16 02:03
点击事件的传递规则
点击事件产生后,传递过程:Activity->Window->顶层View->分发到具体的View。前两个传递比较简单,不用说。顶层View一般为ViewGroup,ViewGroup会首先根据onInterceptTouchEvent判断是否拦截,如果拦截,那么就会调用自己的onTouchEvent方法进行处理,如果不拦截,就会分发给子View,子View再调用自己的dispatchTouchEvent做进一步分发。如果所有的View都不消费掉这个事件,那么会调用Activity的onTouchEvent方法来处理。
对于一个View而言,OnTouchListener的优先级高于自己的onTouchEvent方法,在onTouchEvent方法中,TouchDelegate的优先级最高,其次是OnLongClickListener(如果是长按的话),最后才是OnClickListener。
源码解析
当触摸事件发生时,Activity的dispatchTouchEvent首先被调用。
// Activity$dispatchTouchEventpublic boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } // 首先分发给Window去处理,如果该事件没有被消费掉,那么 // 会调用Activity的ouTouchEvent方法 if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev);}
// PhoneWindow$superDispatchTouchEvent@Overridepublic boolean superDispatchTouchEvent(MotionEvent event) { return mDecor.superDispatchTouchEvent(event);}
// DecorView$superDispatchTouchEventpublic boolean superDispatchTouchEvent(MotionEvent event) { return super.dispatchTouchEvent(event);}
// ViewGroup$dispatchTouchEvent@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) { ... // 出于安全考虑,如果设置了窗口被遮挡后丢弃事件的属性,那么窗口被 // 遮挡时,事件直接被丢弃,而不会分发出去。也就是说,当出现了Toast、 // 对话框等的时候,触摸事件就不会被响应了。该属性一般不会被设置。 if (onFilterTouchEventForSecurity(ev)) { ... // Handle an initial down. if (actionMasked == MotionEvent.ACTION_DOWN) { // Throw away all previous state when starting a new touch gesture. // The framework may have dropped the up or cancel event for the previous gesture // due to an app switch, ANR, or some other state change. cancelAndClearTouchTargets(ev); // 这个方法中会重置FLAG_DISALLOW_INTERCEPT标识,该标识表示当前 // ViewGroup不能拦截触摸事件,也就是说,无论如何该标识都不能控制 // ACTION_DOWN事件的拦截 resetTouchState(); } // Check for interception. final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { intercepted = onInterceptTouchEvent(ev); ev.setAction(action); // restore action in case it was changed } else { intercepted = false; } } else { // There are no touch targets and this action is not an initial down // so this view group continues to intercept touches. // 也就是说如果ViewGorup拦截了某触摸手势的ACTION_DOWN事件后, // 该触摸手势的后续事件都交给这个ViewGroup进行处理,不再需要用 // onInterceptTouchEvent判断是否拦截 intercepted = true; } ... }}...
// 还是ViewGroup$dispatchTouchEvent@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) { ... // ViewGroup不拦截时的事件分发 for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; final View child = (preorderedList == null) ? children[childIndex] : preorderedList.get(childIndex); // If there is a view that has accessibility focus we want it // to get the event first and if not handled we will perform a // normal dispatch. We may do a double iteration but this is // safer given the timeframe. if (childWithAccessibilityFocus != null) { if (childWithAccessibilityFocus != child) { continue; } childWithAccessibilityFocus = null; i = childrenCount - 1; } if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); continue; } newTouchTarget = getTouchTarget(child); if (newTouchTarget != null) { // Child is already receiving touch within its bounds. // Give it the new pointer in addition to the ones it is handling. newTouchTarget.pointerIdBits |= idBitsToAssign; break; } resetCancelNextUpFlag(child); if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // Child wants to receive touch within its bounds. mLastTouchDownTime = ev.getDownTime(); if (preorderedList != null) { // childIndex points into presorted list, find original index for (int j = 0; j < childrenCount; j++) { if (children[childIndex] == mChildren[j]) { mLastTouchDownIndex = j; break; } } } else { mLastTouchDownIndex = childIndex; } mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } // The accessibility focus didn't handle the event, so clear // the flag and do a normal dispatch to all children. ev.setTargetAccessibilityFocus(false); } ... if (mFirstTouchTarget == null) { // ViewGroup进行拦截或者在子View中没有处理事件时,ViewGroup // 调用父类View的dispatchTouchEvent进行处理。 // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } ...}
明显,在正常的事件(通常是ACTION_DOWN)分发时,ViewGroup会依次查询每个子View,如果子View能够接收到触摸事件,就会把事件交给该子View去处理。判断是否能接收到触摸事件有两点:view没有在播放动画,也没有准备播放动画;触摸事件的坐标处于view的范围内。子View收到事件后,会调用自己的dispatchTouchEvent来进一步分发。如果子View处理了该事件,那么mFirstTouchTarget就会在addTouchTarget方法中被赋值,并且终止事件的分发。在之前的代码中,可以看到,如果mFirstTouchTarget为null,也就是没有任何子View处理该事件(通常是ACTION_DOWN),那么接下来的所有事件都会被当前的ViewGroup拦截。
// ViewGroup$dispatchTransformedTouchEventprivate boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { ... // Perform any necessary transformations and dispatch. if (child == null) { handled = super.dispatchTouchEvent(transformedEvent); } else { final float offsetX = mScrollX - child.mLeft; final float offsetY = mScrollY - child.mTop; transformedEvent.offsetLocation(offsetX, offsetY); if (! child.hasIdentityMatrix()) { transformedEvent.transform(child.getInverseMatrix()); } handled = child.dispatchTouchEvent(transformedEvent); } ...}
// View$dispatchTouchEventpublic boolean dispatchTouchEvent(MotionEvent event) { ... boolean result = false; ... if (onFilterTouchEventForSecurity(event)) { //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } if (!result && onTouchEvent(event)) { result = true; } } ... return result;}
View在处理触摸事件时,会先判断是否设置了OnTouchListener,如果设置了而且onTouch方法处理了该事件,就不再调用onTouchEvent进行处理;否则才会调用onTouchEvent。明显,onTouch优先级高于onTouchEvent。
// View$onTouchEventpublic boolean onTouchEvent(MotionEvent event) { ... if ((viewFlags & ENABLED_MASK) == DISABLED) { if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } // A disabled view that is clickable still consumes the touch // events, it just doesn't respond to them. return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE); } if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } } if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) { switch (action) { case MotionEvent.ACTION_UP: // 如果在ListView等可以滚动的容器中ACTION_DOWN时, // 会有一个延迟之后,才会置位PFLAF_PRESSED,此前都是 // PFLAG_PREPRESSED被置位。 boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0; if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) { // take focus if we don't have it already and we should in // touch mode. boolean focusTaken = false; if (isFocusable() && isFocusableInTouchMode() && !isFocused()) { focusTaken = requestFocus(); } if (prepressed) { // The button is being released before we actually // showed it as pressed. Make it show the pressed // state now (before scheduling the click) to ensure // the user sees it. setPressed(true, x, y); } if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) { // This is a tap, so remove the longpress check removeLongPressCallback(); // Only perform take click actions if we were in the pressed state if (!focusTaken) { // Use a Runnable and post this rather than calling // performClick directly. This lets other visual state // of the view update before click actions start. if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { performClick(); } } } } break; ... } return true; } return false;}
明显,只要是CLICKABLE或者LONG_CLICKABLE或者CONTEXT_CLICKABLE的,不管View是否是enabled状态,都会消费点击事件。如果View是enabled的,那么会看是否设置了代理,如果有代理,那么先调用代理的onTouchEvent方法看是否被消费掉,如果没有消费,那么才会真正由View来做处理。在ACTION_UP时如果点击事件没有被OnLongClickListener.onLongClick方法消费掉,那么就会调用OnClickListener的onClick方法。
// View$performLongClickpublic boolean performLongClick() { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED); boolean handled = false; ListenerInfo li = mListenerInfo; if (li != null && li.mOnLongClickListener != null) { handled = li.mOnLongClickListener.onLongClick(View.this); } if (!handled) { handled = showContextMenu(); } if (handled) { performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); } return handled;}
// View$performClickpublic boolean performClick() { final boolean result; final ListenerInfo li = mListenerInfo; if (li != null && li.mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); li.mOnClickListener.onClick(this); result = true; } else { result = false; } sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); return result;}
- view 事件分发机制
- View 事件分发机制
- View事件分发机制
- view事件分发机制
- View事件分发机制
- view事件分发机制
- view事件分发机制
- view事件分发机制
- View事件分发机制
- View 事件分发机制
- View事件分发机制
- View事件分发机制
- view 事件分发机制
- View事件分发机制
- View事件分发机制
- View事件分发机制
- view 事件分发机制
- view事件分发机制
- POJ 1008 (模拟)
- php array_filter过滤数组为空值
- Base64编码与解码
- Android M InCallUI动画简析
- C语言中访问结构体成员时 点 . 和 箭头 -> 的区别
- View事件分发机制
- Linux 网络 I/O 模型简介(图文)
- HTML文档设置标记
- LeetCode 045 Jump Game II
- hdu 2852 线段树 单点更新
- Leetcode no. 257
- ReactNative(三)——WebStorm的基本配置
- Hibernate错误
- IntentFilter过滤规则