开发中总会遇到一些滑动事件的冲突,还有自定义控件一些touch事件的设置,所以一直困扰于Touch事件的分发,这两天终于忍着恶心看完了Touch事件分发机制的源码,浅显见识,权当笔记记录。
一句话粗糙总结就是:分发是从父类向子类分发,子类消费的话父类不再执行除非拦截,子类不消费的话还回传给父类执行。以下是源码分析。Touch事件分发主要涉及到View和ViewGroup,ViewGroup继承自View,但是分发过程比View复杂。
View中涉及到两个方法,dispatchTouchEvent和onTouchEvent,先看dispatchTouchEvent的源码:
/** Pass the touch screen motion event down to the target view, or this
* view if it is the target.
* 把触摸屏幕的动作传递到目标view,如果这个view就是目标view的就传给它自己
* @param event The motion event to be dispatched. 要分发的动作事件
* @return True if the event was handled by the view, false otherwise. 如果这个事件被这个 *view处理并消费了就返回true,反之返回false
*/
public boolean dispatchTouchEvent(MotionEvent event) {
// If the event should be handled by accessibility focus first.
// 这个事件是否应该首先被可访问性焦点处理
if (event.isTargetAccessibilityFocus()) {
// We don't have focus or no virtual descendant has it, do not handle the event.
// 我们没有焦点或者没有拥有焦点的虚拟后代,就不要处理这个事件
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// We have focus and got the event, then use normal event dispatch.
//我们获取了焦点并且得到这个事件,那么就使用正常的事件分发机制分发事件。
event.setTargetAccessibilityFocus(false);
}
boolean result = false;//初始结果设为false不消费
//mInputEventConsistencyVerifier 用于调试目的的一致性校验,不为空去执行该对象的onTouchEvent,但不消费事件
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
//如果是down事件,为新手势做准备的清理工作,该方法看注释说是停止嵌套的scroll滑动
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) {//该方法注释 过滤触摸事件以应用安全策略,当window窗口被占用时,放弃掉这个touch事件
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {//第一个判断条件是按钮enable,第二个判断条件注释为 处理被鼠标头洞的滑动条,如果事件被当做鼠标拖拽的滑动条处理就返回true。整体看按钮enable但是被鼠标拖动的滑动条,返回true,事件被消解
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;//监听信息赋值
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
//监听事件不为空,并且touchlistener不为空,并且按钮enable,并且touchlistener中的touch方法返回了true消费了事件,则返回true,事件被消费
result = true;
}
if (!result && onTouchEvent(event)) {//result为false事件没被消费那么执行该view的onTouchEvent方法,如果onTouchEvent事件返回true则消费事件,整体返回true,事件被消费
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {//result为false,调试也不处理
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
//事件的结尾,或者事件取消或者down之后并没有处理,清除
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
上面的代码核心的点就是39行到48行,总结起来就是:如果设置了touchlistener并且该view为enable的,就执行touchlistener的ontouch方法。如果ontouch方法处理了事件并且返回了true,不继续执行,事件被消费;如果touchlistener为null,或者touchlistener不为null但是ontouch方法返回false,则去执行该view的onTouchEvent方法,onTouchEvent方法处理了事件并且返回true则事件在这里被消费。注意down,move,up事件是分开的,一步一步来的。
再看onTouchEvent方法源码:
/* * 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:
* obeying click sound preferences
* dispatching OnClickListener calls
* handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when
* accessibility features are enabled
* @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;
final int action = event.getAction();
if ((viewFlags & ENABLED_MASK) == DISABLED) {//可点击的disable的view仍然消费这个touch事件,只是对事件没有回应(不是重点)
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) {//如果view有代理,则执行代理的onTouchEvent事件
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {//如果view可点击,或者view可长按,或者view背景可点击,只要满足任一个条件,即进入下面处理并且总是在最后返回true
switch (action) {
case MotionEvent.ACTION_UP:
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.
//不是直接调用performClick而是使用线程并且post出去
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {//没有post执行就直接去执行performClick();方法
performClick();//该方法调用了clicklistener中的onclick方法
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
//判断是不是在一个正在滑动的容器中
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
//如果是在一个正在滑动的容器中,延迟一会按击的反馈以防这是滑动事件
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
//不是在正在滑动的容器中,设置是按下,并检查是不是长按,如果按下500ms则判断为长按
setPressed(true, x, y);//点击
checkForLongClick(0, x, y);//长按检查
}
break;
case MotionEvent.ACTION_CANCEL:
setPressed(false);
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_MOVE:
drawableHotspotChanged(x, y);
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {//挪出按钮取消点击和长按反馈
// Outside button
removeTapCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
setPressed(false);
}
}
break;
}
return true;
}
return false;
}
private void checkForLongClick(int delayOffset, float x, float y) {
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
mHasPerformedLongPress = false;
if (mPendingCheckForLongPress == null) {
mPendingCheckForLongPress = new CheckForLongPress();
}
mPendingCheckForLongPress.setAnchor(x, y);
mPendingCheckForLongPress.rememberWindowAttachCount();
postDelayed(mPendingCheckForLongPress,
ViewConfiguration.getLongPressTimeout() - delayOffset);
}
}
private final class CheckForLongPress implements Runnable {
private int mOriginalWindowAttachCount;
private float mX;
private float mY;
@Override
public void run() {
if (isPressed() && (mParent != null)
&& mOriginalWindowAttachCount == mWindowAttachCount) {
if (performLongClick(mX, mY)) {
mHasPerformedLongPress = true;
}
}
}
public void setAnchor(float x, float y) {
mX = x;
mY = y;
}
public void rememberWindowAttachCount() {
mOriginalWindowAttachCount = mWindowAttachCount;
}
}
public boolean performLongClick(float x, float y) {
mLongClickX = x;
mLongClickY = y;
final boolean handled = performLongClick();
mLongClickX = Float.NaN;
mLongClickY = Float.NaN;
return handled;
}
public boolean performLongClick() {
return performLongClickInternal(mLongClickX, mLongClickY);
}
private boolean performLongClickInternal(float x, float y) {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
boolean handled = false;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLongClickListener != null) {
handled = li.mOnLongClickListener.onLongClick(View.this);
}
if (!handled) {
final boolean isAnchored = !Float.isNaN(x) && !Float.isNaN(y);
handled = isAnchored ? showContextMenu(x, y) : showContextMenu();
}
if (handled) {
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
}
return handled;
}
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;
}
以上代码的核心点是,是可点击的控件都会处理这个事件,并且每次事件都返回true。
1,down事件时会判断是否只是单纯的点击,并且检测是不是长按事件,标示是mHasPerformedLongPress,查看checkForLongClick方法源码看到CheckForlongPress类源码可知,当mOnLongClickListener不为空,并且它的onLongClick方法返回true时mHasPerformedLongPress才赋值为true;
2,move的时候检查触摸事件是否移出了这个view,如果移出了则取消点击和长按的检测反馈处理;
3,up的时候如果移出了则不处理,如果没有移出,是点击事件并且mHasPerformedLongPress为false,也就是onLongClick方法返回false,则会执行onclicklistener监听的onclick方法,看performClick方法源码就是。总体来看短促的点击事件一定会执行onclicklistener监听的onclick方法,当长按的时候,如果onLongClick不返回true,长按和短按事件可以同时响应。
总结view的分发,其实就是如果控件是enable的并且有ontouchlistener就执行ontouchlistener的ontouch方法。如果ontouch返回true则结束dispatchTouchEvent返回true,事件被消费;如果ontouch返回false就去执行ontouchEvent。如果ontouchevent返回true,结束且dispatchTouchEvent返回true事件被消费;如果返回false则dispatchTouchEvent返回false。注意ontouchevent的up时会在恰当的时机回调点击事件和长按事。
ViewGroup涉及到三个方法,dispatchTouchEvent,onInterceptionTouchEvent,onTouchEvent。onInterceptionTouchEvent用于事件拦截,默认返回false不拦截;dispatchTouchEvent会调用onInterceptionTouchEvent方法判断该ViewGroup是否拦截事件,想要拦截事件的话可重写该方法,在适当的条件下返回true拦截事件;onTouchEvent方法完全继承自父类也就是view的,不再赘述。主要分发逻辑在dispatchTouchEvent方法中,源码如下:
@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.
//down事件时,清空之前所有的状态
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {//在down事件,或者mFirstTouchTarget即目标target之前给赋值了不为空了(事件被子类消费的话mFirstTouchTarget会被赋值,参见后面的代码)
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;//查看是否不允许被拦截
if (!disallowIntercept) {//不许拦截字段disallowIntercept为false,该值由requestDisallowInterceptTouchEvent方法赋值,一般由子类调用,用于强制父类不拦截触摸事件。为false表示ViewGroup可以拦截,那么调用onInterceptionTouchEvent方法看事件是否被拦截,这里如果重写onInterceptTouchEvent方法,可以实现,down事件被子view消费,但是move和up事件可以被拦截,交给父类处理。
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {//else就是disallowIntercept==true,表示不允许被父类拦截,intercepted只能为false
intercepted = false;
}
} else {//不是down事件如果是up或者move,cancel事件时,且mFirstTouchTarget 为空没有被赋值,说明事件在上一波事件时已经被拦截了,intercepted置为true
// 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 accessiiblity 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) {//ACTION_POINTER_DOWN是多点触摸按下的事件
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) {//新的触摸target不为null并且子view数量不为0,为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;
//轮询子类,看子view是否消费该事件
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 (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
//子view不能收到触摸事件或者子类不在触摸范围内,则continue轮询下一个子view
ev.setTargetAccessibilityFocus(false);
continue;
}
//能走到这里说明该子view能收到触摸事件并且在触摸范围内,
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.
//如果该方法为true表示子类消费了该事件,查看dispatchTransformedTouchEvent具体实现会发现,当child不为null 的时候就执行child的dispatchTouchEvent,否则执行父类的dispatchTouchEvent。后附dispatchTransformedTouchEvent方法的源码
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();
//addTouchTarget方法将child赋值给target.child, 并将target赋值给mFirstTouchTarget ,并且返回target赋值给newTouchTarget,看addTouchTarget方法实现即可知如此。后附addTouchTarget方法的源码
newTouchTarget = addTouchTarget(child, idBitsToAssign);
//alreadyDispatchedToNewTouchTarget 赋值为true,之后的代码中要用到
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) {//如果mFirstTouchTarget 为null说明没有找到子view消费该事件,那么执行父类(也就是view)的dispatchTouchEvent方法,并返回父类处理的结果。
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {//如果mFirstTouchTarget 不为null说明有子view消费了该事件,
// 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) {
//呼应上面down的事件,down事件有子view消费事件,这里会返回true
handled = true;
} else {//不是down事件 ,子view消费这个事件的话也会返回true
//这里有判断是否被拦截了,如果事件被拦截了,intercepted为true,cancelChild为true。
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {//cancelChild做参数,事件没有拦截且子类消费了返回true,handler赋值为true。如果被拦截了则回去执行父类(也就是view)的dispatchTouchEvent方法。
handled = true;
}
if (cancelChild) {//事件被拦截的话,target置为null
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;
}
/**
* 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;
}
// Calculate the number of pointers to deliver.
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
// If for some reason we ended up in an inconsistent state where it looks like we
// might produce a motion event with no pointers in it, then drop the event.
if (newPointerIdBits == 0) {
return false;
}
// If the number of pointers is the same and we don't need to perform any fancy
// irreversible transformations, then we can reuse the motion event for this
// dispatch as long as we are careful to revert any changes we make.
// Otherwise we need to make a copy.
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
// 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);
}
// Done.
transformedEvent.recycle();
return handled;
}
/**
* Adds a touch target for specified child to the beginning of the list.
* Assumes the target child is not already present.
*/
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
总结viewGroup的dispatchTouchEvent方法的核心是,先看是否被父view拦截,没有被拦截的话 轮询在触摸区域内的子view,执行子view的dispatchTouchEvent,子view也会轮询自己的子view,直到最后一个子view,如果子view消费了该事件,子类的dispatchTouchEvent方法返回true,父view不再消费事件,如果子
view都没有消费则返回给父view消费该事件。
一步步来,分好几种情况:
1,从down事件开始被拦截,看dispatchTouchEvent源码在30行代码进入,然后被拦截,intercepted为true,然后在59行代码进不去,此时mFirstTouchTarget是null,此时从168行进入判断,执行父类的dispatchTouchEvent。那么之后再过来的move和up事件,在30行进不去,导致intercepted为true,同样在59行代码进不去,此时mFirstTouchTarget为null,则同样从168行代码进入,执行父类的dispatchTouchEvent。
2,从down事件进入不拦截,intercepted为false,然后在30行代码进入执行,最终会轮询所有触摸到的子view,如果没有子view消费事件则同第一种情况之后事件都由父类处理;如果有子view消费该事件,mFirstTouchTarget,newTouchTarget不为null,alreadyDispatchedToNewTouchTarget均为true,在172行进入,并且在179行进入,返回true。之后再来move和up事件的时候,因为mFirstTouchTarget不为null会从30行进入,此时也有两种情况,情况a,在move或者up事件时不拦截,intercepted为false,从59行进入,然后68行进不去所以基本又出来了,然后在172进入,在184处cancelChild为false,在186处执行子view的dispatchTouchEvent,若子view消费则handler置为true,最终会返回true;否则最终会返回false。可见子view消费过down事件后是可以不消费move和up事件的。情况b,在move或者up事件时拦截,intercepted为true,59行进不去,在172行进入,在184处cancelChild为true,那么在186行代码处,看dispatchTransformedTouchEvent方法的实现,由于cancelChild为true,事件被设置为cancel,此时child不为null,执行子view的dispatchTouchEvent方法,但是是cancel所以返回false,会执行父类的dispatchTouchEvent方法。可见子view消费down事件后,如果父view拦截的话,子view将不能执行move和up事件。
从源头说起,当手指触摸屏幕的时候,首先事件会传递给Activity,执行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);
}
可知触摸事件通过getwindow().superDispatchTouchEvent(ev)方法被分发,getwindow返回的是Window的实例mWindow,看源码mWindow初始化的是Phonewindow:
/**
* Retrieve the current {@link android.view.Window} for the activity.
* This can be used to directly access parts of the Window API that
* are not available through Activity/Screen.
*
* @return Window The current window, or null if the activity is not
* visual.
*/
public Window getWindow() {
return mWindow;
}
在Activity源码6619行找到:mWindow = new PhoneWindow(this, window); 而Phonewindow的superDispatchTouchEvent()源码如下:
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
由此可见,Activity接收到触摸事件,传递给PhoneWindow分发,PhoneWindow又将事件传递给DecorView分发,查看DecorView的源码,知setcontentview设置的layout就是DecorView的一个子类,由此,Activity最终将触摸事件传递给自己的布局view一层层传递分发。如果子类都没有消费事件的话,转而会执行Activity的onTouchEvent(ev)方法,如果onTouchEvent方法也不消费的话就return false,没有任何事情发生。
下节讲两个简单的事件分发的应用:简单滑动冲突的解决。
P个S:谁能跟我讲讲这个优快云的排版是什么鬼,为啥编辑状态下的排版跟预览不一样啊,哭/(ㄒoㄒ)/~~