文章目录

  • OnTouchListener与OnClickListener的优先级
  • 事件分发
  • 滑动冲突
  • 处理滑动冲突
  • 1. 内部拦截法
    • 2.外部拦截法

当MotionEvent产生后,系统总归要将其传递到某个View,这个过程就是事件分发。事件分发机制离不开3个重要方法:dispatchTouchEvent(分发)、onInterceptTouchEvent(拦截,在dispatchTouchEvent中调用。需要注意,View中没有该方法)、onInterceptTouchEvent(处理事件,在dispatchTouchEvent中调用。)
这3个方法相爱相杀,紧密关联。ViewGroup收到点击事件后,它的dispatchTouchEvent方法首先会被调用。如果它的onInterceptTouchEvent方法返回了true,表示ViewGroup要将“快递”拦截下来,事件就成了“拦截件”。随后它的onTouchEvent会被调用来处理这件“快递”。如果ViewGroup的onInterceptTouchEvent返回了false,那么ViewGroup就不会拦截。快递就送到了View手里,View的dispatchTouchEvent就会被调用。如此循环往复,直到事件被处理。

OnTouchListener与OnClickListener的优先级

提到事件处理,当对一个Button同时设置了setOnTouchListener和setOnClickListener后,如果OnTouchListener的onTouch方法return 了false,那么两个方法都会执行。如果return了true,则只有onTouch被执行,onClick不会被执行。

现在通过源码分析原因:

View.java#dispatchTouchEvent()
...
if (onFilterTouchEventForSecurity(event)) {if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {result = true;}//mListenerInfo是单例处理返回的对象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;}
}
...

这个mListenerInfo就是单例处理返回的一个ListenerInfo 对象,这就保证了mListenerInfo 肯定不为null,即li != null

ListenerInfo getListenerInfo() {if (mListenerInfo != null) {return mListenerInfo;}mListenerInfo = new ListenerInfo();return mListenerInfo;
}

它里面有着众多xxxListener,如:

public OnClickListener mOnClickListener;protected OnLongClickListener mOnLongClickListener;private OnTouchListener mOnTouchListener;

而我们setOnTouchListener时,就会将new出来的OnTouchListener 赋值过来,这就保证了li.mOnTouchListener != null

我们setOnTouchListener重写的onTouch方法的返回值起到了决定性的作用,它影响了if条件语句的执行。如果return true,那就所有的条件都满足,万事大吉,程序走进if里,result = true。如果重写的onTouch方法return了false,那么整个if条件语句就不满足,就不再会往里走了。

在仔细看if块:

if (!result && onTouchEvent(event)) {result = true;}

如果result为true的话,那么“&&”前面就是false,onTouchEvent(event)就没有再执行的必要了。onTouchEvent负责事件的消费,而onClick是在MotionEvent.ACTION_UP中被调用—>performClickInternal()—>performClick():

public boolean performClick() {// We still need to call this method to handle the cases where performClick() was called// externally, instead of through performClickInternal()notifyAutofillManagerOnClick();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);notifyEnterOrExitForAutoFillIfNeeded(true);return result;
}

这个if判断和刚才onTouch时的if是十分类似的。前面已经分析过了,if里面的条件都满足,于是就往里走,调用了 li.mOnClickListener.onClick(this)。至此,我们setOnClickListener就会响应点击事件了。

总结一下,TouchEvent发射出来一个信号,如果被onTouch拦截到了,自然就没onClick什么事了。可如果一旦onTouch没拦截到这个信号,onClick就会成功处理这个信号。

事件分发

事件产生后的传递顺序如下:

来看ViewGroup对事件的分发过程,dispatchTouchEvent方法:

if (actionMasked == MotionEvent.ACTION_DOWN) {// 判断为DOWN事件,清除掉一些变量,重置状态cancelAndClearTouchTargets(ev);resetTouchState();
}// 没有拦截,为false
final boolean intercepted;
//当事件被ViewGroup的子元素成功处理时,mFirstTouchTarget会被赋值并指向子元素
//换句话说,当ViewGroup不拦截,并将事件交给子元素处理时,mFirstTouchTarget != null成立
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;
}

首先判断为ACTION_DOWN事件,会清除掉一些变量、重置状态。
后续有一个变量:final boolean intercepted。如果没有拦截,intercepted为false。这里有一个特殊情况:标志位FLAG_DISALLOW_INTERCEPT。这个标志位是在requestDisallowInterceptTouchEvent方法内设置的,一旦它被设置,那么ViewGroup就无法拦截除DOWN事件以外的其他事件(MOVE/UP等事件)。当ViewGroup分发事件时,DOWN事件会重置该标志位,导致子View的“努力徒劳”。当ViewGroup面临DOWM事件时,总会调用自己的onInterceptTouchEvent方法询问是否需要拦截事件。resetTouchState方法会对该标志位进行重置,故子View调用requestDisallowInterceptTouchEvent方法并不能影响ViewGroup对DOWM事件的处理结果,这一点,在使用内部拦截法处理滑动冲突时会得到验证。

然后往下走,
if (newTouchTarget == null && childrenCount != 0)
newTouchTarget 肯定为null,因为上面没几行刚刚赋值为null。childrenCount 表示控件的个数,肯定不为0,所以就会继续往里走下去,会遇到

final ArrayList<View> preorderedList = buildTouchDispatchChildList();

buildTouchDispatchChildList()—>return buildOrderedChildList():对子view进行排序。

ArrayList<View> buildOrderedChildList() {final int childrenCount = mChildrenCount;if (childrenCount <= 1 || !hasChildWithZ()) return null;if (mPreSortedChildren == null) {//专门用来存放排序后的child的mPreSortedChildren = new ArrayList<>(childrenCount);} else {// callers should clear, so clear shouldn't be necessary, but for safety...mPreSortedChildren.clear();mPreSortedChildren.ensureCapacity(childrenCount);}final boolean customOrder = isChildrenDrawingOrderEnabled();for (int i = 0; i < childrenCount; i++) {// add next child (in child order) to end of listfinal int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);final View nextChild = mChildren[childIndex];//获取View在Z轴方向的值final float currentZ = nextChild.getZ();// insert ahead of any Views with greater Zint insertIndex = i;//mPreSortedChildren 就是上面刚刚new出来的数组。专门用来存放排序后的children//从数组取出来和当前的currentZ比较,while里就是将Z值较大的往后放,较小的往前排。while (insertIndex > 0 && mPreSortedChildren.get(insertIndex - 1).getZ() > currentZ) {insertIndex--;}mPreSortedChildren.add(insertIndex, nextChild);}return mPreSortedChildren;
}for循环中通过nextChild.getZ()来获取到currentZ,currentZ就是View在Z轴方向上的值(码积木一样,View都是一层层码上去的)。
越靠上的View,它的currentZ就越大。mPreSortedChildren 用来存放排序后的children。while里将Z值大的View放后,Z值小的往前排。
最后将排序好的数组返回。

preorderedList 就是return的排好序的数组,随后还会经历一个for循环。对排好序的数组的循环从childrenCount -1开始,也就是先从数组最后开始。通过getAndVerifyPreorderedIndex方法获取对应的childIndex,并进一步获取到响应的child(就是我们的Button等控件View)。

                     //从数组最后面,currentZ最大的开始for (int i = childrenCount - 1; i >= 0; i--) {//先拿到View的下标final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);//根据下标,得到View(如:Button)      final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);if (childWithAccessibilityFocus != null) {if (childWithAccessibilityFocus != child) {continue;}childWithAccessibilityFocus = null;i = childrenCount - 1;}//拿到Button后,canViewReceivePointerEvents判断点击是否是动画、是否可见,//isTransformedTouchPointInView判断点击是否在View上。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);//dispatchTransformedTouchEvent负责将事件分发给谁来处理事件,return handled = child.dispatchTouchEvent(transformedEvent);//这意味这事件已经传递到View中了。//View#dispatchTouchEvent的返回结果--->result  只有当onTouchEvent处理后,result才为true。//换句话说,当Button处理了事件,才会命中这个if,break掉for循环;如果Button没处理,就开始下一次循环,取出比Button的currentZ值更小的父容器,将事件交给它。//Button处理事件就处理,Button不处理,就往上传递交给Button的父容器。如果父容器也不处理,那就交给父容器的父容器......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 indexfor (int j = 0; j < childrenCount; j++) {if (children[childIndex] == mChildren[j]) {mLastTouchDownIndex = j;break;}}} else {mLastTouchDownIndex = childIndex;}mLastTouchDownX = ev.getX();mLastTouchDownY = ev.getY();//Button处理事件后,调用addTouchTarget方法--->mFirstTouchTarget!=null--->newTouchTarget==mFirstTouchTargetnewTouchTarget = 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);}

将事件从ViewGroup分发给View,这样事件的处理就走到了View上,于是事件就得到了它应有的处理(这个方法不止一次被调用,而且有很大区别,第三个参数是否为null):

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,View child, int desiredPointerIdBits) {......//child不为null,就执行else中的child.dispatchTouchEvent(transformedEvent)方法//也就是调用Button的dispatchTouchEvent方法。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());}//这调用的就是Button的dispatchTouchEvent方法,即事件已经传递到View中了handled = child.dispatchTouchEvent(transformedEvent);}// Done.transformedEvent.recycle();return handled;
}
返回的handled,即handled = child.dispatchTouchEvent(transformedEvent)也就是返回View#dispatchTouchEvent的返回结果--->result
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;if (mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onTouchEvent(event, 0);}final int actionMasked = event.getActionMasked();if (actionMasked == MotionEvent.ACTION_DOWN) {// Defensive cleanup for new gesturestopNestedScroll();}if (onFilterTouchEventForSecurity(event)) {if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {result = true;}//noinspection SimplifiableIfStatementListenerInfo li = mListenerInfo;if (li != null && li.mOnTouchListener != null&& (mViewFlags & ENABLED_MASK) == ENABLED&& li.mOnTouchListener.onTouch(this, event)) {result = true;}//当onTouchEvent处理后,result才为true。if (!result && onTouchEvent(event)) {result = true;}}if (!result && mInputEventConsistencyVerifier != null) {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.if (actionMasked == MotionEvent.ACTION_UP ||actionMasked == MotionEvent.ACTION_CANCEL ||(actionMasked == MotionEvent.ACTION_DOWN && !result)) {stopNestedScroll();}return result;
}

再回到ViewGroup#dispatchTouchEvent中的if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign))中。如果事件被Button处理完,会返回handled (= child.dispatchTouchEvent(transformedEvent)),View#dispatchTouchEvent方法的返回值和View自己的onTouchEvent有关。看ViewGroup#dispatchTouchEvent,如果Button没有处理事件,就会开启下一个循环,currentZ会更小,事件就会流转到父容器的手里。如果父容器也不处理,那就继续向上return。
可是如果在for循环中,Button处理掉了事件,就会break,即终止了for循环,也就是终止了事件的分发,别人就处理不了了。所以如果Button一旦处理了,Button的父容器就拿不到这个事件了。
当Button处理完事件后,调用addTouchTarget方法:

private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);target.next = mFirstTouchTarget;mFirstTouchTarget = target;return target;
}

意味着newTouchTarget==mFirstTouchTarget、alreadyDispatchedToNewTouchTarget = true

再往后,mFirstTouchTarget != null,走else:

// Button处理事件后,mFirstTouchTarget != null,进入elseif (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循环内的操作,意味着while只会进去一次。while (target != null) {//next为nullfinal TouchTarget next = target.next;//alreadyDispatchedToNewTouchTarget就是true,上面刚刚给赋的值//两个条件都满足,即handled=true;while只循环一次,故退出循环后,直接就将handled = true返回了。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;//将null给到targettarget = next;}}

最后将handled返回就完事了。

滑动冲突

说起滑动冲突,我们都遇到过。ViewPager嵌套ListView,如果ViewPager的onInterceptTouchEvent return了true后,ListView的世界都坍塌了。

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;
}

是否执行onInterceptTouchEvent,取决于disallowIntercept是否为false。假设onInterceptTouchEvent被执行了,即intercepted=true,下面的if 就不会再走了:

//没拦截,就往里进了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 downfinal 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;//newTouchTarget在上面没几行就置空了。//childrenCount 表示控件的个数,肯定不为0if (newTouchTarget == null && childrenCount != 0) {final float x = ev.getX(actionIndex);final float y = ev.getY(actionIndex);// buildTouchDispatchChildList:对子View根据currentZ进行排序final ArrayList<View> preorderedList = buildTouchDispatchChildList();final boolean customOrder = preorderedList == null&& isChildrenDrawingOrderEnabled();final View[] children = mChildren;//从数组最后面,currentZ最大的开始for (int i = childrenCount - 1; i >= 0; i--) {//先拿到View的下标final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);//根据下标,得到View(如:Button)     final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);if (childWithAccessibilityFocus != null) {if (childWithAccessibilityFocus != child) {continue;}childWithAccessibilityFocus = null;i = childrenCount - 1;}//拿到Button后,canViewReceivePointerEvents判断点击是否是动画、是否可见,//isTransformedTouchPointInView判断点击是否在View上。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);//dispatchTransformedTouchEvent负责将事件分发给谁来处理事件,return handled = child.dispatchTouchEvent(transformedEvent);//这意味这事件已经传递到View中了。//View#dispatchTouchEvent的返回结果--->result  只有当onTouchEvent处理后,result才为true。//换句话说,当Button处理了事件,才会命中这个if,break掉for循环;如果Button没处理,就开始下一次循环,取出比Button的currentZ值更小的父容器,将事件交给它。//Button处理事件就处理,Button不处理,就往上传递交给Button的父容器。如果父容器也不处理,那就交给父容器的父容器......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 indexfor (int j = 0; j < childrenCount; j++) {if (children[childIndex] == mChildren[j]) {mLastTouchDownIndex = j;break;}}} else {mLastTouchDownIndex = childIndex;}mLastTouchDownX = ev.getX();mLastTouchDownY = ev.getY();//Button处理事件后,调用addTouchTarget方法--->mFirstTouchTarget!=null--->newTouchTarget==mFirstTouchTargetnewTouchTarget = 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;}}}

这也就意味着,这个ViewGroup的多层子View们就不会有得到事件的机会了。从ViewGroup这就已经拦截了。

然后就往下走if,这里mFirstTouchTarget肯定为null,因为它在上面那一大段if里面的addTouchTarget方法内才被赋值。

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循环内的操作,意味着while只会进去一次。while (target != null) {//next为nullfinal TouchTarget next = target.next;//alreadyDispatchedToNewTouchTarget就是true,上面刚刚给赋的值//两个条件都满足,即handled=true;while只循环一次,故退出循环后,直接就将handled = true返回了。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;//将null给到targettarget = next;}}

还是会调用dispatchTransformedTouchEvent方法,但是参数有改变,child为null。这样就会走到dispatchTransformedTouchEvent方法的if中:

if (child == null) {handled = super.dispatchTouchEvent(transformedEvent);
} else {......


调用的是super的dispatchTouchEvent方法,并非else中的child.dispatchTouchEvent方法。即调用的是ViewGroup自己的dispatchTouchEvent方法。即这个事件由ViewGroup自己处理。这也是当重写的onInterceptTouchEvent方法返回true后,其包含的子View就不会滑动了的原因所在。

处理滑动冲突

1. 内部拦截法

即父容器不进行拦截,让子View进行拦截。如果子View需要该事件,就消耗掉。否则,就还给父容器处理。和分发机制有点区别,内部拦截法需要requestDisallowInterceptTouchEvent方法的配合才行(上面已经结合源码解释过了),还得重写子View的分发方法dispatchTouchEvent方法:

    //内部拦截法@Overridepublic boolean dispatchTouchEvent(MotionEvent event) {int x = (int) event.getX();int y = (int) event.getY();switch (event.getAction()) {case MotionEvent.ACTION_DOWN: {getParent().requestDisallowInterceptTouchEvent(true);break;}case MotionEvent.ACTION_MOVE: {int deltaX = x - mLastX;int deltaY = y - mLastY;//父容器需要该事件if (Math.abs(deltaX) > Math.abs(deltaY)) {getParent().requestDisallowInterceptTouchEvent(false);}break;}case MotionEvent.ACTION_UP: {break;}default:break;}mLastX = x;mLastY = y;return super.dispatchTouchEvent(event);}

除此之外,父容器也得默认拦截除DOWN外的其他事件,只有这样,当子View调用getParent().requestDisallowInterceptTouchEvent(false)时,父容器才会继续拦截所需的事件。上面提到过,DOWN事件受标志位FLAG_DISALLOW_INTERCEPT控制。一旦父容器拦截DOWN事件,所有的事件就都无法传递到子元素中去了,内部拦截法也就成了“黄粱一梦”。

2.外部拦截法

即父容器进行拦截,如果父容器需要该事件就拦截,反之不拦截。外部拦截相比于内部拦截,更简单。外部拦截的做法,更符合事件分发机制。外部拦截法比较“省心”,只需要重写父容器的onInterceptTouchEvent方法即可:

    @Overridepublic boolean onInterceptTouchEvent(MotionEvent event) {// 外部拦截法int x = (int) event.getX();int y = (int) event.getY();switch (event.getAction()) {case MotionEvent.ACTION_DOWN: {mLastX = (int) event.getX();mLastY = (int) event.getY();break;}case MotionEvent.ACTION_MOVE: {int deltaX = x - mLastX;int deltaY = y - mLastY;if (Math.abs(deltaX) > Math.abs(deltaY)) {return true;}break;}case MotionEvent.ACTION_UP: {break;}default:break;}return super.onInterceptTouchEvent(event);}

事件分发机制并不是洪水猛兽相关推荐

  1. View事件分发机制(源码 API27)

    1.什么是事件分发机制 当用户触摸屏幕时,会产生一个touch事件,这个touch事件(motionEvent)传递到某个具体的view处理的整个过程 用户触摸屏幕会产生一个事件流(ACTION_DO ...

  2. Android事件分发机制详解

    2019独角兽企业重金招聘Python工程师标准>>> 之前在学习Android事件方法机制的时候,看过不少文章,但是大部分都讲的不是很清楚,我自己理解的也是云里雾里,也尝试过阅读源 ...

  3. android触摸事件分发,Android 事件分发机制

    Android 事件分发机制一直让人头痛,之前也是面向 GitHub 编程得过且过.今天下定决心了解一下,以便后面自己定制 View 效果.Android 触摸事件有三个基本类型:ACTION_DOW ...

  4. View事件分发机制(源码分析篇)

    01.Android中事件分发顺序 1.1 事件分发的对象是谁 事件分发的对象是事件.注意,事件分发是向下传递的,也就是父到子的顺序. 当用户触摸屏幕时(View或ViewGroup派生的控件),将产 ...

  5. 【Android View事件分发机制】关于拦截事件的注意点

    在父容器拦截事件时,为什么不能拦截DOWN事件呢? 先看看源码: 回顾一下事件分发机制原理,当事件来了之后,如果父容器不拦截,则会询问其child view ,当某child view 有事件需求,父 ...

  6. 【转】Android事件分发机制完全解析,带你从源码的角度彻底理解(下)

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9153761 记得在前面的文章中,我带大家一起从源码的角度分析了Android中Vi ...

  7. Android ViewGroup事件分发机制

    理~ 1.案例 首先我们接着上一篇的代码,在代码中添加一个自定义的LinearLayout: [java] view plaincopy package com.example.zhy_event03 ...

  8. Android View 事件分发机制详解

    想必很多android开发者都遇到过手势冲突的情况,我们一般都是通过内部拦截和外部拦截法解决此类问题.要想搞明白原理就必须了解View的分发机制.在此之前我们先来了解一下以下三个非常重要的方法: di ...

  9. Android View的事件分发机制解析

    作者:网易·周龙 最近刚看完android-Ultra-Pull-To-Refresh下拉刷新的源码,发现在写自定义控件时,对于View的事件的传递总是搞不太清楚,而View事件的分发机制,又是解决可 ...

最新文章

  1. java实现excel文件上传_java相关:SpringMVC下实现Excel文件上传下载
  2. luoguP1419 寻找段落(二分答案+单调队列)
  3. java佳沃维洛奇,新品速递:JAVA 双雄耀世登场
  4. 响应式布局框架 Pure-CSS 5.0 示例中文版-下
  5. xtrabackup实现数据备份与恢复
  6. 为什么我们需要Logstash,Fluentd等日志摄取器?
  7. linux 历史命令快捷键,Linux历史命令及bash快捷键
  8. 基于springboot+shiro一套可落地实施安全认证框架整合
  9. kettle系列-6.kettle实现多字段字典快速翻译
  10. HDU 2870 Largest Submatrix
  11. java可视化编程教程_JAVA可视化编程——SWING
  12. 《人间告白》---我看万物像你,我看你像万物
  13. 怎么修改照片文件的大小?教你一招改变图片大小尺寸
  14. 巴巴运动网学习笔记(76-80)
  15. 重装系统后怎么恢复数据?看完你就了解了
  16. 人数全球第一,但现在中国的问题不是人太多,而是太少 | 浪潮工作室
  17. web 前端常见英文汇总
  18. 百度AI入门课-day2作业
  19. 2月15日市场游资操作情况以及龙虎榜
  20. 如何做到数据分析报告(五)

热门文章

  1. Leetcode 1
  2. Web 开发学习笔记(1) --- 搭建你的第一个 Web Server
  3. springboot配置文件priperties大全
  4. iPhoneX-关于底部的那个一个横条的问题
  5. aws-ec2-双网卡问题
  6. python学习:函数传参数
  7. LeetCode 之 Merge Sorted Array(排序)
  8. Linux下如何把时间转成秒数,或把秒数转换成标准时间
  9. Android 动态生成 EditTest
  10. Java-第三章-使用if选择结构实现,如果年龄够7岁或5岁并且是男,可以搬桌子