View事件分发机制
来源:互联网 发布:生化危机7ps4淘宝 编辑:程序博客网 时间:2024/04/29 16:14
在开发过程中,我们经常遇到事件冲突现象,但是很多开发者不知道如何处理。在这里我认为只有认识View 事件的分发机制后,才会懂得如何下手,并且可以处理任何的事件冲突的现象。
一、三个方法
dispatchTouchEvent(MotionEvent ev)
该方法用于进行事件分发。如果事件能够传递给当前View,那么此方法一定会被调用,返回结果受当前View的onTouchEvent和下级的View的dispatchTouchEvent方法的影响,表示是否消耗当前事件
onInterceptTouchEvent(MotionEvent event)
该方法用于拦截事件。如果当前的View成功拦截来某个事件,那么在同一个事件序列当中,此方法将不会再次调用,返回结果表示是否拦截当前事件
onTouchEvent(MotionEvent event)
该方法用于处理事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前View无法再次接收事件。
二、三个方法联系
public boolean dispatchTouchEvent(MotionEvent ev){ boolean consume=false; if(onInterceptToucEvent(ev)){ consume=onTouchEvent(ev); }else{ consume=child.dispatchTouchEvent(ev); } return consume;}
对于一个根ViewGroup来说,点击事件产生后,首先会传递给它,这是它的dispatchTouchEvent方法会被调用,如果这个ViewGroup的onInterceptTouchEvent方法返回true就表示它要拦截当前事件,接着这个事件交给这个ViewGroup处理,即它的onTouchEvent方法会被调用,并且这个序列事件都会交给它处理,onInterceptTouchEvent方法不再调用,成功拦截只调用一次。如果这个ViewGroup的onInterceptTouchEvent方法返回false,就表示它不拦截当前事件,这个当前事件就会继续传递给它的子元素,接着子元素的dispatchTouchEvent方法就会被调用,此法反复直到事件被最终处理
三、分析
当一个View需要处理事件时,如果它设置来OnTouchListener,那么OnTouchListener中的onTouch方法会被回调,这时事件如何处理还要看onTouch方法的返回值,如果返回false,则当前View的onTouchEvent方法,如果返回true,那么onTouchEvent方法将不会再调用,由此可见,给View设置onTouchListener,其优先级比onTouchEvent方法高。在onTouchEvent方法中,如果当前设置了OnClickListener方法,那么它的onClick方法将被调用,由此可见,OnClickListener方法在三个优先级中表现为最低。
当一个点击事件产生后,它的传递过程顺序如下:Activity–>Window–>View,所以最先收到事件的是Activity,Activity再传输给Window,最后Window再传输给顶级View,也就是DecorView,顶级View收到事件后,就会按照事件分发机制去分发事件。如果在分发过程中,有个View消耗掉来该事件,那么该View的父容器不再调用调用onTouchEvent方法,除非父容器实现来事件的成功拦截。
四、详细分析
- 同一个事件序列是指从手指接触屏幕的那一刻起,到手指离开屏幕的那一刻结束,在这个过程中所产生的一系列事件,这个事件序列以down事件开始,中间含有数量不定的move事件,最终以up事件结束。
- 正常情况下,一个事件序列只能被一个view拦截且消耗。因为一旦一个元素拦截了某个事件后,那么同一个事件序列内的所有事件都会直接交给它处理,因此同一个事件序列中的事件不能分别由两个View同时处理,但是通过某个特殊手段可以做到,比如一个View将本该自己处理的事件通过onTouchEvent强行传递给其他View处理
- 某个View一旦决定拦截,那么这一个事件序列都只能由它来处理(如果事件序列能够传递给它的话)并且它的onInterceptTouchEvent不会再次调用,也就是说当一个View决定拦截一个事件后,那么系统会把同一个事件序列内的其他方法都直接交给它来处理,因此就不用调用这个View的onInterceptTouchEvent去询问它是否要拦截了。
- 某个View一旦开始处理事件,如果它不消耗ACTION_DOWN事件,即onTouchEvent方法返回false,那么同一事件序列中的其他事件都不会交给它来处理,并且事件将重新交给它的父元素去处理,即父元素的onTouchEvent方法将被调用。也就是说事件一旦交给一个view处理,那么它就鼻息消耗掉,否则同一个事件序列中剩下的事件就不再交给它处理了。
- 如果View不消耗ACTION_DOWN以外的其他事件,那么这个点击事件会消失,此时父元素的onTouchEvent并不会被调用,并且当前的View可以持续收到后续的事件,最终这些消失的点击事件传递给Activity来处理
- ViewGroup默认不拦截任何事件,即onInterceptTouchEvent的返回值的是false
- View没有onInterceptTouchEvent方法,一旦有点击事件,那么它的onTouchEvent方法会被调用
- View的onTouchEvent默认都会消耗事件(返回true),除非它是不可点击(clickable和longclickable
同时为false),View的longclickable的属性默认为false,clickable属性要分情况,比如Button的clickable属性默认为true,而TextView的clickable属性默认值为false - View的enable属性不影响onTouchEvent的默认返回值。只要它的clickable或则longclickable有一个为true,那么它的onTouchEvent就返回true
- onClick会发生的前提是当前的View是可点击的,并且它收到down和up事件
- 事件传递过程是由内到外,即事件总是先传递给父元素,然后在由父元素分发给子元素View,通过requestDisallowInterceptTouchEvent方法可以在子元素中干预父元素的事件分发过程,但是ACTION_DOWN事件除外。
先来看看单一的View类对touch事件的处理
/** * 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) { if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(event, 0); } if (onFilterTouchEventForSecurity(event)) { // 一般都成立 //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { // 先在ENABLED状态下尝试调用onTouch方法 return true; // 如果被onTouch处理了,则直接返回true } // 从这里我们可以看出,当你既设置了OnTouchListener又设置了OnClickListener,那么当前者返回true的时候, // onTouchEvent没机会被调用,当然你的OnClickListener也就不会被触发;另外还有个区别就是onTouch里可以 // 收到每次touch事件,而onClickListener只是在up事件到来时触发。 if (onTouchEvent(event)) { return true; } } if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); } return false; // 上面的都没处理,则返回false } /** * 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) { // View对touch事件的默认处理逻辑 final int viewFlags = mViewFlags; if ((viewFlags & ENABLED_MASK) == DISABLED) { // DISABLED的状态下 if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); // 复原,如果之前是PRESSED状态 } // A disabled view that is clickable still consumes the touch // events, it just doesn't respond to them. return (((viewFlags & CLICKABLE) == CLICKABLE || // CLICKABLE或LONG_CLICKABLE的view标记为对事件处理了, (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)); // 只不过是以do nothing的方式处理了。 } if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { // 如果有TouchDelegate的话,优先交给它处理 return true; // 处理了返回true,否则接着往下走 } } if (((viewFlags & CLICKABLE) == CLICKABLE || // View能对touch事件响应的前提要么是CLICKABLE要么是LONG_CLICKABLE (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { switch (event.getAction()) { case MotionEvent.ACTION_UP: // UP事件 // 如果外围有可以滚动的parent的话,当按下时会设置这个标志位 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; // 这行代码就是我们上篇博客中说的,设置了FocusableInTouchMode后,View在点击的时候就会 // 尝试requestFocus(),并将focusToken设置为true if (isFocusable() && isFocusableInTouchMode() && !isFocused()) { focusTaken = requestFocus(); // 能进来这个if,一般都会返回true } 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. // 在前面down事件的时候我们延迟显示view的pressed状态 setPressed(true); // 直到up事件到来的时候才显示pressed状态 } if (!mHasPerformedLongPress) { // 如果没有长按发生的话 // This is a tap, so remove the longpress check removeLongPressCallback(); // 移除长按callback // Only perform take click actions if we were in the pressed state if (!focusTaken) { // 看到没,focusTaken是false才会进入下面的if语句 // Use a Runnable and post this rather than calling // performClick directly. This lets other visual state // of the view update before click actions start. // 也就是说在touch mode下,不take focus的view第一次点击的时候才会触发onClick事件 if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { // 如果post失败了,则直接调用performClick()方法 performClick(); // 这2行代码会触发onClickListener } } } if (mUnsetPressedState == null) { mUnsetPressedState = new UnsetPressedState(); // unset按下状态的 } if (prepressed) { postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration()); } else if (!post(mUnsetPressedState)) { // If the post failed, unpress right now mUnsetPressedState.run(); } removeTapCallback(); } break; case MotionEvent.ACTION_DOWN: // 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) { // 如果是在可以滚动的container里面的话 mPrivateFlags |= PFLAG_PREPRESSED; // 设置PREPRESSED标志位 if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); } // 延迟pressed feedback postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); } else { // Not inside a scrolling container, so show the feedback right away setPressed(true); // 否则直接显示pressed feedback checkForLongClick(0); // 并启动长按监测 } break; case MotionEvent.ACTION_CANCEL: // 针对CANCEL事件的话,恢复各种状态,移除各种callback setPressed(false); removeTapCallback(); removeLongPressCallback(); break; case MotionEvent.ACTION_MOVE: // MOVE事件 final int x = (int) event.getX(); final int y = (int) event.getY(); // Be lenient about moving outside of buttons if (!pointInView(x, y, mTouchSlop)) { // 如果移动到view的边界之外了, // Outside button removeTapCallback(); // 则取消Tap callback,这样当你松手的时候onClick不会被触发 if ((mPrivateFlags & PFLAG_PRESSED) != 0) { // 当已经是按下状态的话 // Remove any future long press/tap checks removeLongPressCallback(); // 移除长按callback setPressed(false); // 恢复按下状态 } } break; } return true; // 最后返回true,表示对touch事件处理过了,消费了 } return false; // 既不能单击也不能长按的View,返回false,表示不处理touch事件 }
在开始介绍ViewGroup对touch事件的处理之前,我们还得先看看ViewGroup的一个内部类TouchTarget,因为它描述的就是被
touch的view和touch的手指相关的信息,代码如下:
/* Describes a touched view and the ids of the pointers that it has captured. * * This code assumes that pointer ids are always in the range 0..31 such that * it can use a bitfield to track which pointer ids are present. * As it happens, the lower layers of the input dispatch pipeline also use the * same trick so the assumption should be safe here... */ private static final class TouchTarget { private static final int MAX_RECYCLED = 32; private static final Object sRecycleLock = new Object[0]; private static TouchTarget sRecycleBin; // 回收再利用的链表头 private static int sRecycledCount; public static final int ALL_POINTER_IDS = -1; // all ones // The touched child view. public View child; // The combined bit mask of pointer ids for all pointers captured by the target. public int pointerIdBits; // The next target in the target list. public TouchTarget next; private TouchTarget() { } // 看到这个有没有很眼熟?是的Message里也有类似的实现,我们在之前介绍Message的文章里详细地分析过 public static TouchTarget obtain(View child, int pointerIdBits) { final TouchTarget target; synchronized (sRecycleLock) { if (sRecycleBin == null) { // 没有可以回收的目标,则new一个返回 target = new TouchTarget(); } else { target = sRecycleBin; // 重用当前的sRecycleBin sRecycleBin = target.next; // 更新sRecycleBin指向下一个 sRecycledCount--; // 重用了一个,可回收的减1 target.next = null; // 切断next指向 } } target.child = child; // 找到合适的target后,赋值 target.pointerIdBits = pointerIdBits; return target; } public void recycle() { // 基本是obtain的反向过程 synchronized (sRecycleLock) { if (sRecycledCount < MAX_RECYCLED) { next = sRecycleBin; // next指向旧的可回收的头 sRecycleBin = this; // update旧的头指向this,表示它自己现在是可回收的target(第一个) sRecycledCount += 1; // 多了一个可回收的 } else { next = null; // 没有next了 } child = null; // 清空child字段 } } }
现在开始看看ViewGroup的方法
@Override public boolean dispatchTouchEvent(MotionEvent ev) { if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(ev, 1); } boolean handled = false; if (onFilterTouchEventForSecurity(ev)) { // view没有被遮罩,一般都成立 final int action = ev.getAction(); final int actionMasked = action & MotionEvent.ACTION_MASK; // Handle an initial down. if (actionMasked == MotionEvent.ACTION_DOWN) { // 一堆touch事件(从按下到松手)中的第一个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(); // 作为新一轮的开始,reset所有相关的状态 } // Check for interception. final boolean intercepted; // 检查是否要拦截 if (actionMasked == MotionEvent.ACTION_DOWN // down事件 || mFirstTouchTarget != null) { // 或者之前的某次事件已经经由此ViewGroup派发给children后被处理掉了 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { // 只有允许拦截才执行onInterceptTouchEvent方法 intercepted = onInterceptTouchEvent(ev); // 默认返回false,不拦截 ev.setAction(action); // restore action in case it was changed } else { intercepted = false; // 不允许拦截的话,直接设为false } } else { // There are no touch targets and this action is not an initial down // so this view group continues to intercept touches. // 在这种情况下,actionMasked != ACTION_DOWN && mFirstTouchTarget == null // 第一次的down事件没有被此ViewGroup的children处理掉(要么是它们自己不处理,要么是ViewGroup从一 // 开始的down事件就开始拦截),则接下来的所有事件 // 也没它们的份,即不处理down事件的话,那表示你对后面接下来的事件也不感兴趣 intercepted = true; // 这种情况下设置ViewGroup拦截接下来的事件 } // Check for cancelation. final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL; // 此touch事件是否取消了 // Update list of touch targets for pointer down, if needed. // 是否拆分事件,3.0(包括)之后引入的,默认拆分 final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0; TouchTarget newTouchTarget = null; // 接下来ViewGroup判断要将此touch事件交给谁处理 boolean alreadyDispatchedToNewTouchTarget = false; if (!canceled && !intercepted) { // 没取消也不拦截,即是个有效的touch事件 if (actionMasked == MotionEvent.ACTION_DOWN // 第一个手指down || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) // 接下来的手指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 View[] children = mChildren; final boolean customOrder = isChildrenDrawingOrderEnabled(); // 从最后一个向第一个找 for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; final View child = children[childIndex]; if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { continue; // 不满足这2个条件直接跳过,看下一个child } // child view能receive touch事件而且touch坐标也在view边界内 newTouchTarget = getTouchTarget(child);// 查找child对应的TouchTarget if (newTouchTarget != null) { // 比如在同一个child上按下了多跟手指 // 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; // newTouchTarget已经有了,跳出for循环 } resetCancelNextUpFlag(child); // 将此事件交给child处理 // 有这种情况,一个手指按在了child1上,另一个手指按在了child2上,以此类推 // 这样TouchTarget的链就形成了 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // Child wants to receive touch within its bounds. mLastTouchDownTime = ev.getDownTime(); mLastTouchDownIndex = childIndex; mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); // 如果处理掉了的话,将此child添加到touch链的头部 // 注意这个方法内部会更新 mFirstTouchTarget newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; // down或pointer_down事件已经被处理了 break; // 可以退出for循环了。。。 } } } // 本次没找到newTouchTarget但之前的mFirstTouchTarget已经有了 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; } // while结束后,newTouchTarget指向了最初的TouchTarget newTouchTarget.pointerIdBits |= idBitsToAssign; } } } // 非down事件直接从这里开始处理,不会走上面的一大堆寻找TouchTarget的逻辑 // Dispatch to touch targets. if (mFirstTouchTarget == null) { // 没有children处理则派发给自己处理 // 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) { // 遍历TouchTarget形成的链表 final TouchTarget next = target.next; if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; // 已经处理过的不再让其处理事件 } else { // 取消child标记 final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; // 如果ViewGroup从半路拦截了touch事件则给touch链上的child发送cancel事件 // 如果cancelChild为true的话 if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; // TouchTarget链中任意一个处理了则设置handled为true } if (cancelChild) { // 如果是cancelChild的话,则回收此target节点 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) { // 取消或up事件时resetTouchState 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 onInterceptTouchEvent(MotionEvent ev) { return false; // 其默认直接返回false,表示不拦截 }
最后为了完整性,我这里把TouchTarget相关的方法都列一下以便大家参考:
/** * Resets all touch state in preparation for a new cycle. */ private void resetTouchState() { clearTouchTargets(); resetCancelNextUpFlag(this); mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; } /** * Resets the cancel next up flag. * Returns true if the flag was previously set. */ private static boolean resetCancelNextUpFlag(View view) { if ((view.mPrivateFlags & PFLAG_CANCEL_NEXT_UP_EVENT) != 0) { view.mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT; return true; } return false; } /** * Clears all touch targets. */ private void clearTouchTargets() { TouchTarget target = mFirstTouchTarget; if (target != null) { do { TouchTarget next = target.next; target.recycle(); target = next; } while (target != null); mFirstTouchTarget = null; } } /** * Cancels and clears all touch targets. */ private void cancelAndClearTouchTargets(MotionEvent event) { if (mFirstTouchTarget != null) { boolean syntheticEvent = false; if (event == null) { final long now = SystemClock.uptimeMillis(); event = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); event.setSource(InputDevice.SOURCE_TOUCHSCREEN); syntheticEvent = true; } for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) { resetCancelNextUpFlag(target.child); dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits); } clearTouchTargets(); if (syntheticEvent) { event.recycle(); } } } /** * Gets the touch target for specified child view. * Returns null if not found. */ private TouchTarget getTouchTarget(View child) { for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) { if (target.child == child) { return target; } } return null; } /** * 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; } /** * Removes the pointer ids from consideration. */ private void removePointersFromTouchTargets(int pointerIdBits) { TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; while (target != null) { final TouchTarget next = target.next; if ((target.pointerIdBits & pointerIdBits) != 0) { target.pointerIdBits &= ~pointerIdBits; if (target.pointerIdBits == 0) { if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); target = next; continue; } } predecessor = target; target = next; } } // 类似从链表中删除某个特定的节点 private void cancelTouchTarget(View view) { TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; while (target != null) { final TouchTarget next = target.next; if (target.child == view) { if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); final long now = SystemClock.uptimeMillis(); MotionEvent event = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); event.setSource(InputDevice.SOURCE_TOUCHSCREEN); view.dispatchTouchEvent(event); event.recycle(); return; } predecessor = target; target = next; } }
对于以上的理解,来自android开发艺术探索以及http://www.cnblogs.com/xiaoweiz/p/3838682.html这个链接的理解。
- view 事件分发机制
- View 事件分发机制
- View事件分发机制
- view事件分发机制
- View事件分发机制
- view事件分发机制
- view事件分发机制
- view事件分发机制
- View事件分发机制
- View 事件分发机制
- View事件分发机制
- View事件分发机制
- view 事件分发机制
- View事件分发机制
- View事件分发机制
- View事件分发机制
- view 事件分发机制
- view事件分发机制
- A. Opponents
- WriteOutputStream
- Service介绍
- 怎么实现ZBrush 中的映射大师功能的灵活运用
- Android LinearLayout
- View事件分发机制
- *.jar 与 *.aar 的生成与*.aar导入项目方法
- docker镜像相关命令
- Linux快速修改用户密码
- ios .h声明变量在@interface括号里和外面
- 理解Python中的with语句
- CNN 卷积神经网络-- 残差计算
- Wamp下的Apache无法启动的解决方法
- ListView的下拉刷新上拉加载以及带列的横向滚动