在说事件分发机制之前我们先来看看都有哪些基本的事件
事件 | 触发场景 | 单词事件流中触发的次数 |
---|---|---|
MotionEvent.ACTION_DOWN | 在屏幕上按下 | 1次 |
MotionEvent.ACTION_UP | 在屏幕上抬起 | 0或1次 |
MotionEvent.ACTION_MOVE | 在屏幕上移动 | 0或N次 |
MotionEvent.ACTION_CANCEL | 滑动超出空间边界时 | 0或1次 |
当我们的手指在android手机上点按或者滑动时会触发一系列的事件流,你可能触摸到的是一个Button或者是一个Layout,那么这些事件是怎么一级级传递,哪些你看到的UI是怎么知道是否要响应你的指令操作呢?
我们先看一下我们看到的App的Activity上都包含哪些UI:
从图中我们基本看到,我们App展示的一个页面大致包含如下几部分: Activity:是我们看到的整屏幕范围 View Group:View子类,同时也是View的容器,可以包含多个View View:我们常规看到的各种UI组件的基类
分发机制的核心方法 对事件分发起关键作用的有三个方法
虽然说是三个核心关键所在,但是也并不是Activity ViewGroup 和View都同时具备
我们先整体看一下
组件 | dispatchTouchEvent | onTouchEvent | onInterceptTouchEvent |
---|---|---|---|
Activity | 有 | 有 | 否 |
ViewGroup | 有 | 有 | 有 |
View | 有 | 有 | 否 |
下面根据实际的代码看看
//Activityclass MyActivity extends AppCompatActivity{ @Override public boolean onTouchEvent(MotionEvent event) { return super.onTouchEvent(event); }
@Override public boolean dispatchTouchEvent(MotionEvent ev) { return super.dispatchTouchEvent(ev); }
}//Viewclass MyView extends View{ @Override public boolean onTouchEvent(MotionEvent event) { return super.onTouchEvent(event); }
@Override public boolean dispatchTouchEvent(MotionEvent event) { return super.dispatchTouchEvent(event); }
}//ViewGroupclass MyViewGroup extends ViewGroup{ @Override public boolean onTouchEvent(MotionEvent event) { return super.onTouchEvent(event); }
@Override public boolean dispatchTouchEvent(MotionEvent ev) { return super.dispatchTouchEvent(ev); }
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { return super.onInterceptTouchEvent(ev); }}
在实际的继承关系中ViewGroup继承于View因此ViewGroup的onTouchEvent本质是从VIew继承而来的
核心代码剥离 Activity
/** * 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); }
新版本的Android源码与老版本的发生了变化,不过本质还是一样
首先事件传递给了Activity它会分派给子View来处理(一般Activity不处理)
/**
* Called when a touch screen event was not handled by any of the views
* under it. This is most useful to process touch events that happen
* outside of your window bounds, where there is no view to receive it.
*
* @param event The touch screen event being processed.
*
* @return Return true if you have consumed the event, false if you haven't.
* The default implementation always returns false.
*/
public
boolean onTouchEvent(MotionEvent event)
{
if
(mWindow.shouldCloseOnTouch(this, event))
{
finish();
return
true;
}
return
false;
}
至此Activity大致完成事件的处理,也就是Activity负责将事件传递下去,一般不做处理
ViewGoup的事件分发
@Override public boolean dispatchTouchEvent(MotionEvent ev) { if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(ev, 1); }
// If the event targets the accessibility focused view and this is it, start // normal event dispatch. Maybe a descendant is what will handle the click. if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) { ev.setTargetAccessibilityFocus(false); }
boolean handled = false; if (onFilterTouchEventForSecurity(ev)) { final int action = ev.getAction(); final int actionMasked = action & MotionEvent.ACTION_MASK;
// 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(); }
// 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; }
// If intercepted, start normal event dispatch. Also if there is already // a view that is handling the gesture, do normal event dispatch. if (intercepted || mFirstTouchTarget != null) { ev.setTargetAccessibilityFocus(false); }
// Check for cancelation. final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL;
// Update list of touch targets for pointer down, if needed. final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0; TouchTarget newTouchTarget = null; boolean alreadyDispatchedToNewTouchTarget = false; if (!canceled && !intercepted) {
// If the event is targeting accessibility focus we give it to the // view that has accessibility focus and if it does not handle it // we clear the flag and dispatch the event to all children as usual. // We are looking up the accessibility focused host to avoid keeping // state since these events are very rare. View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus() ? findChildWithAccessibilityFocus() : null;
if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { final int actionIndex = ev.getActionIndex(); // always 0 for down final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex) : TouchTarget.ALL_POINTER_IDS;
// Clean up earlier touch targets for this pointer id in case they // have become out of sync. removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount; if (newTouchTarget == null && childrenCount != 0) { final float x = ev.getX(actionIndex); final float y = ev.getY(actionIndex); // Find a child that can receive the event. // Scan children from front to back. final ArrayList<View> preorderedList = buildTouchDispatchChildList(); final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled(); final View[] children = mChildren; for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = getAndVerifyPreorderedIndex( childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView( preorderedList, children, 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 (!child.canReceivePointerEvents() || !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 (preorderedList != null) preorderedList.clear(); }
if (newTouchTarget == null && mFirstTouchTarget != null) { // Did not find a child to receive the event. // Assign the pointer to the least recently added target. newTouchTarget = mFirstTouchTarget; while (newTouchTarget.next != null) { newTouchTarget = newTouchTarget.next; } newTouchTarget.pointerIdBits |= idBitsToAssign; } } }
// 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); } else { // Dispatch to touch targets, excluding the new touch target if we already // dispatched to it. Cancel touch targets if necessary. TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; while (target != null) { final TouchTarget next = target.next; if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } if (cancelChild) { if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); target = next; continue; } } predecessor = target; target = next; } }
// Update list of touch targets for pointer up or cancel, if needed. if (canceled || actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { resetTouchState(); } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) { final int actionIndex = ev.getActionIndex(); final int idBitsToRemove = 1 << ev.getPointerId(actionIndex); removePointersFromTouchTargets(idBitsToRemove); } }
if (!handled && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1); } return handled; }
按照老版本的源码可所简化为
// ViewGroup中该方法的核心部分伪代码public boolean dispatchTouchEvent(MotionEvent ev) { if (!onInterceptTouchEvent(ev)) { return child.dispatchTouchEvent(ev); //不拦截,则传给子View进行分发处理 } else { return onTouchEvent(ev); //拦截事件,交由自身对象的onTouchEvent方法处理 }}
1 当ViewGroup 不进行拦截的话会一次遍历子View是否响应这个事件 2 加入ViewGroup拦截了此事件的话电泳自身的onTouch事件,而由于ViewGroup是View的子类因此实际此时走的是View的事件分发的处理逻辑了,进而我们来接着说View的时间分发机制
View的事件分发
// View中该方法的核心部分伪代码public boolean dispatchTouchEvent(MotionEvent ev) { //如果该对象的监听成员变量不为空,则会调用其onTouch方法, if (mOnTouchListener != null && mOnTouchListener.onTouch(this, event)) { return true; //若onTouch方法返回TRUE,则表示消费了该事件,则dispachtouTouchEvent返回TRUE,让其调用者知道该事件已被消费。 } return onTouchEvent(ev); //若监听成员为空或onTouch没有消费该事件,则调用对象自身的onTouchEvent方法处理。}
从中我们可以看出在事件分发中 onTouch是有很高的优先级的
实际上,在View的onTouchEvent方法中,如果设置了onClickListener监听对象,则会调用其onClick方法。
本文只是村略的简易的缕缕没有太过认真的去细细的看有点粗浅,希望对大家对于事件分发的整体有个浅显的帮助