View的事件分发机制(源码分析)
来源:互联网 发布:python聚类结果可视化 编辑:程序博客网 时间:2024/06/09 22:56
View的事件分发机制
点击事件的传递规则
事件分发就是当一个MotionEvent产生了以后,系统需要把这个事件传递给一个具体的View,而这个传递过程就是分发过程,由三个很重要的方法来共同完成:dispatchTouchEvent,onInterceptTouchEvent和onTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev)
用来进行事件的分发,如果事件能够传递到当前View,则一定为触发此方法,返回的结果受当前View的onTouchEvent和下级View的dispatchTouchEvent方法的影响,表示是否消费当前事件
public boolean onInterceptTouchEvent(MotionEvent event)
在上述方法内部调用,用来判断是否拦截某个事件,如果当前View拦截了某个事件,那么在同一个事件序列当中,此方法不会被再次调用,返回结果表示是否拦截当前事件
public boolean onTouchEvent(MotionEvent event)
在dispatchTouchEvent方法中调用,用来处理点击事件,返回结果表示是否消费当前事件,如果不消费,则在同一个事件序列中,当前View无法再次接收到事件
三个方法的关系如下:
public boolean dispatchTouchEvent(MotionEvent ev){ boolean consume = false; if(onInterceptTouchEvent(ev)){ consume = onTouchEvent(ev); }else{ consume = child.dispatchTouchEvent(ev); } return consume;}
点击事件的传递规则:对于一个根ViewGroup来说,点击事件产生后,首先会传递给它自身,这时它的dispatchTouchEvent就会被调用,如果这个ViewGroup的onInterceptTouchEvent方法返回true就表示它要拦截当前事件,接着这个事件就会交给这个ViewGroup处理,即它的onTouchEvent方法就会被调用;如果这个ViewGroup的onInterceptTouchEvent方法返回false就表示它不拦截当前事件,这时当前事件就会继续传递给它的子元素,接着子元素的dispatchTouchEvent方法就会被调用,如此反复直到事件最终被处理
当一个View需要处理事件时,如果它设置了OnTouchListener,那个onTouch方法会被回调。如果返回false,则当前View的onTouchEvent方法会被调用;如果返回true,那么onTouchEvent方法将不会被调用。所以,给View设置OnTouchlistener,其优先级比onTouchEvent要高,在onTouchEvent方法中,如果当前设置的有OnClickListener,那么它的onClick方法会被调用。可以看出,OnClickListener的优先级最低,处于事件传递的尾端
当一个点击事件产生后,它的传递过程遵循如下顺序:Activity->Window->View,顶级View收到事件后,就会按照事件分发机制去分发事件。
总结:
- 同一个事件序列是从手指接触屏幕的那一刻起,到手指离开屏幕的那一刻结束,down->move->…->move->up
- 正常情况下, 一个事件序列只能被一个View拦截且消耗。一旦一个元素拦截了此事件,那么同一个事件序列内的所有事件都会直接交给它处理
- 某个View一旦决定拦截,那么这一个事件序列都只能由它来处理(如果事件序列能够传递给它的话),并且它的onInterceptTouchEvent不会再被调用(拦截后,下次不会再询问它是否要拦截了)
- 某个View一旦开始处理事件,如果它不消费ACTION_DOWN事件(onTouchEvent返回了false),那么同一事件序列中的其他事件都不会再交给它来处理
- 如果View只消费了ACTION_DOWN事件,那么这个点击事件会消失,当前View可以持续收到后续事件,最终这些消失的点击事件会传递给Activity处理
- ViewGroup默认不拦截任何事件,即onInterceptTouchEvent返回false
- View的onTouchEvent默认都会消费事件(返回true),除非它是不可点击的(clickable和longClickable同时为false),View的longClickable属性默认都会false,clickable属性要分情况,比如Button的clickable属性默认为true,TextView的就默认为false
- View的enable属性不影响onTouchEvent的默认返回值,只要它的clickable或者longClickable有一个为true,那么它的onTouchEvent就返回true
- onClick会发生的前提是当前View是可点击的,并且它收到了down和up的事件
- 事件传递过程是由外向内的,由父级分发给子View,通过requestDisallowInterceptTouchEvent方法可以在子元素中干预父元素的事件分发过程,但是ACTION_DOWN事件除外
事件分发的源码分析
1.Activity对点击事件的分发过程
事件先传给当前Activity,由Activity的dispatchTouchEvent来进行事件派发,具体工作是由Activity内部的Window来完成的,Window会将事件传递给decor view,decor view一般就是当前界面的底层容器(setContentView设置的View的父容器),可以通过Activity.getWindow.getDecorView()获得
源码:Activity#dispatchTouchEvent
/** * Called to process touch screen events. You can override this to * intercept all touch screen events before they are dispatched to the * window. Be sure to call this implementation for touch screen events * that should be handled normally. * * @param ev The touch screen event. * * @return boolean Return true if this event was consumed. */public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev);}
首先事件交给window进行分发,如果所有的View的onTouchEvent都返回了false,那么Activity的onTouchEvent就会被调用
源码:Window#superDispatchTouchEvent
/** * Used by custom windows, such as Dialog, to pass the touch screen event * further down the view hierarchy. Application developers should * not need to implement or call this. * */public abstract boolean superDispatchTouchEvent(MotionEvent event);
Window类是一个抽象类,其实现类是PhoneWindow
源码:PhoneWindow#superDispatchTouchEvent
@Overridepublic boolean superDispatchTouchEvent(MotionEvent event) { return mDecor.superDispatchTouchEvent(event);}
通过源码可以看出,PhoneWindow将事件直接传递给了DecorView
// This is the top-level view of the window, containing the window decor.private DecorView mDecor;@Overridepublic final View getDecorView() { if (mDecor == null) { installDecor(); } return mDecor;}
这个mDecor就是我们通过setContentView设置的View的父级,也就是说现在事件传递到了顶级(根)View
2.顶级View对点击事件的分发过程
首先看一下ViewGroup的dispatchTouchEvent方法
// 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. intercepted = true; }
从上面代码中我们可以看出,ViewGroup在如下两种情况下会判断是否要拦截当前事件:事件类型为ACTION_DOWN或者mFirstTouchTarget!=null。当ViewGroup不拦截事件并将事件交给子元素处理时mFirstTouchTarget!=null。也就是说一旦ViewGroup拦截事件,当ACTION_MOVE和ACTION_UP事件到来时,(actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null)为false,导致ViewGroup的onInterceptTouchEvent不会再被调用(intercepted = true),并且同一序列中的其他事件都会默认交给它处理
这里有一个特殊情况,当disallowIntercept(final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)为true时,会执行intercepted = false。FLAG_DISALLOW_INTERCEPT是一个标记位,这个标记位是通过requestDisallowInterceptTouchEvent方法来设置的,一般用于子View中。一旦子View请求父级不要拦截后,ViewGroup将无法拦截除了ACTION_DOWN以外的其他点击事件。
为什么是除了ACTION_DOWN以外的事件呢?因为ViewGroup会在ACTION_DOWN事件到来时做重置状态的操作,而在resetTouchState方法中会对FLAG_DISALLOW_INTERCEPT进行重置
// 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); resetTouchState();}
当ViewGroup不拦截事件的时候,事件会向下分发交由它的子View进行处理
final View[] children = mChildren;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; } ……}
首先遍历ViewGroup的所有子元素,然后判断子元素是否能够接收到点击事件:子元素是否在播放动画和点击事件的坐标是否落在子元素的区域内;如果某个子元素满足这两个条件,那么事件就会传递给它来处理。dispatchTransformedTouchEvent实际上调用的就是子元素的dispatchTouchEvent方法,这样事件就交由子元素处理了,从而完成了一轮事件分发
/** * Transforms a motion event into the coordinate space of a particular child view, * filters out irrelevant pointer ids, and overrides its action if necessary. * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead. */private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { final boolean handled; // Canceling motions is a special case. We don't need to perform any transformations // or filtering. The important part is the action, not the contents. 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; } ……}
如果子元素的dispatchTouchEvent返回true,mFirstTouchTarget就会被赋值同时跳出for循环
newTouchTarget = addTouchTarget(child, idBitsToAssign);alreadyDispatchedToNewTouchTarget = true;break;
如果子元素的dispatchTouchEvent返回false,ViewGroup就会把事件分发给下一个子元素(如果还有下一个子元素)
mFirstTouchTarget的赋值在addTouchTarget内部完成,mFirstTouchTarget是一种单链表结构,mFirstTouchTarget是否被赋值,将直接影响到ViewGroup对事件的拦截策略,如果mFirstTouchTarget为null,那么ViewGroup就默认拦截接下来同一序列中所有的点击事件
/** * Adds a touch target for specified child to the beginning of the list. * Assumes the target child is not already present. */private TouchTarget addTouchTarget(View child, int pointerIdBits) { TouchTarget target = TouchTarget.obtain(child, pointerIdBits); target.next = mFirstTouchTarget; mFirstTouchTarget = target; return target;}
如果遍历完所有的子元素后事件都没有被消费:ViewGroup没有子元素或子元素在onTouchEvent中返回了false,这时ViewGroup会自己处理点击事件
// Dispatch to touch targets.if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);}
3.View对点击事件的处理过程
/** * Pass the touch screen motion event down to the target view, or this * view if it is the target. * * @param event The motion event to be dispatched. * @return True if the event was handled by the view, false otherwise. */public 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,如果OnTouchListener中的onTouch方法返回true,那么onTouchEvent就不会被调用(OnTouchListener的优先级高于onTouchEvent,这样做的好处是方便在外界处理点击事件)。
onTouchEvent的实现如下,可以看出当View处于不可用状态依然会消耗点击事件
/** * Implement this method to handle touch screen motion events. * <p> * If this method is used to detect click actions, it is recommended that * the actions be performed by implementing and calling * {@link #performClick()}. This will ensure consistent system behavior, * including: * <ul> * <li>obeying click sound preferences * <li>dispatching OnClickListener calls * <li>handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when * accessibility features are enabled * </ul> * * @param event The motion event. * @return True if the event was handled, false otherwise. */public boolean onTouchEvent(MotionEvent event) { final float x = event.getX(); final float y = event.getY(); final int viewFlags = mViewFlags; if ((viewFlags & ENABLED_MASK) == DISABLED) { if (event.getAction() == 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)); } ……}
接着,如果View设置有代理,还会执行TouchDelegate的onTouchEvent方法
if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; }}
再看一下onTouchEvent中对点击事件的具体处理
if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { switch (event.getAction()) { case MotionEvent.ACTION_UP: boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0; if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) { …… if (!mHasPerformedLongPress) { // 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; }
只要View的CLICKABLE和LONG_CLICKABLE有一个为true,就会消费这个事件,即onTouchEvent返回为true,当ACTION_UP事件发生时,会触发performClick方法,如果View设置了OnClickListener,那么performClick方法内部会调用它的onClick方法
/** * Call this view's OnClickListener, if it is defined. Performs all normal * actions associated with clicking: reporting accessibility event, playing * a sound, etc. * * @return True there was an assigned OnClickListener that was called, false * otherwise is returned. */public 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;}
setOnClickListener和setOnLongClickListener会自动将View的CLICKABLE和LONG_CLICKABLE设为true
/** * Register a callback to be invoked when this view is clicked. If this view is not * clickable, it becomes clickable. * * @param l The callback that will run * * @see #setClickable(boolean) */public void setOnClickListener(OnClickListener l) { if (!isClickable()) { setClickable(true); } getListenerInfo().mOnClickListener = l;}/** * Register a callback to be invoked when this view is clicked and held. If this view is not * long clickable, it becomes long clickable. * * @param l The callback that will run * * @see #setLongClickable(boolean) */public void setOnLongClickListener(OnLongClickListener l) { if (!isLongClickable()) { setLongClickable(true); } getListenerInfo().mOnLongClickListener = l;}
参考:《Android开发艺术探索》
- 源码分析View的事件分发机制
- View的事件分发机制源码分析
- View的事件分发机制(源码分析)
- View事件分发机制源码分析
- Android—— View事件分发机制的源码分析
- View事件的分发机制流程源码分析
- Android源码分析--View的事件分发机制
- View的事件分发机制,从源码角度分析一下
- View的事件分发机制源码解析
- Android源码分析(二):View的事件分发机制探析
- View的事件分发机制分析
- View的事件分发源码分析
- View事件分发源码分析(一)
- View事件分发源码分析(二)
- android源码分析之View的事件分发(上)
- 源码跟踪分析View的事件分发(改)
- View事件分发机制分析
- 源码分析android的事件分发机制
- memcpy注意点
- Java中 ArrayList、Vector和LinkedList 的使用和详解(转)
- 训练之线段树A Simple Problem with Integers
- Shader 简单流水效果
- 【.Net码农】C#中的partial class(部分类)
- View的事件分发机制(源码分析)
- Android Studio ButterKnife使用详解---省时省力的神器
- Runtime系列(二)--Runtime的使用场景
- ionic的安装使用并创建第一个app
- java-微信支付接入
- 博弈论一点点
- JAVA 迭代器(Iterator)
- LINUX系统监控常用命令(一)
- 八皇后