一、事件分发

来源:互联网 发布:怎么申请淘宝直播 编辑:程序博客网 时间:2024/05/22 16:21

有一段时间没更博客了,想象我写博客的初衷主要有2个原因:

  1. 如果有人看到了,从中找到一点能帮助到他的东西,这样最好不过了。
  2. 写给自己看。我相信整理一遍写一遍对自己的理解和认知有很大的帮助。

最近闭关修炼,哈哈哈,说的有点魔幻。主要最近对安卓的看法有了一点新的看法,看了一个公众号的推送,有个人将安卓的学习和进阶分为了5个部分:

  1. UI
  2. 性能
  3. NDk
  4. 架构
  5. 其他

这样分是基于项目而言的,不同类型不同定位的项目这5个部分所占的比重也是不一样的。比如有些项目就要UI好看,用户看你UI好看就会极大的提升兴趣。

当然我最近学习这些东西不是因为看了这个公众号然后就去学UI相关的东西,而是我在学的过程中发现了这个公众号,我就觉得这样分也比较合理,就顺带提了一下。

回归主题:今天我要说的是我学习的第一个东西:

事件分发

之前一直学习这块,但是也是含含糊糊,这次静下心来好好的梳理了一遍,算是掌握了7-8分吧,但还有几个问题没弄明白,百度了也没百到,问了大神也没得到我想要的答案,然后就不了了之了。这些问题我记在心中,我相信在不远的将来肯定会遇到我的师傅,他肯定会为我传道解惑。

好了下面开始我的表演:

我之前只知道事件分发是从父view传递到子view,但是我不知道起点是那里。最近看了一篇博客,链接忘记了,我感到很羞耻,其实以我的水平我应该能想到是Activity,但是之前我没想到,但我现在知道了,哈哈哈。
为什么说是Activity呢,因为Activity是负责与用户交互的啊,那么我的手势操作不应该被Activity所捕捉然后进行下一步吗?
那么Activity是怎么捕捉到我们的手势的呢?这个我不知道,跟硬件或底层实现有关了,我只知道最终会从来到Activity的dispatchTouchEvent()方法:

  1. public boolean dispatchTouchEvent(MotionEvent ev) {    2.     3.         //如果是按下状态就调用onUserInteraction()方法,onUserInteraction()方法    4.         //是个空的方法, 我们直接跳过这里看下面的实现    5.         if (ev.getAction() == MotionEvent.ACTION_DOWN) {    6.             onUserInteraction();    7.         }    8.             9.         if (getWindow().superDispatchTouchEvent(ev)) {    10.             return true;    11.         }    12.             13.         //getWindow().superDispatchTouchEvent(ev)返回false,这个事件就交给Activity    14.         //来处理, Activity的onTouchEvent()方法直接返回了false    15.         return onTouchEvent(ev);    16.     }

根据注释我们可以看到,第9行如果返回true就说明有view能处理我们的事件,那么就不会调用Activity的onTouchEvent()方法;如果第9行返回false,那么就说明没有view能响应我们的事件,那么事件就交给Activity处理。

我们进入第9行的getWindow().superDispatchTouchEvent(ev)方法内部:
它是调用Window类的superDispatchTouchEvent(ev)方法,但是我们进入Window类发现这个方法是个抽象方法,而Window也是一个抽象类。Window就是我们的窗口,就是最顶层的一个承载窗体view的东西。他只有一个子类PhoneWindow,我们直接进入PhoneWindow类的superDispatchTouchEvent(ev)方法:

@Override    public boolean superDispatchTouchEvent(MotionEvent event) {        return mDecor.superDispatchTouchEvent(event);    }

mDecor是什么呢?他是DecorView的实例,这个DecorView是一个继承FrameLayout的的ViewGroup,在之前的版本中,它是作为PhoneWindow的内部类的形式出现,在之后的版本中它被独立出来作为一个单独的类。他的作用就是存放我们在Activity中设置的布局View,也就是说我们在Activity中通过setContentView(int resid)方法设置的布局,最终会绑定到DecorView上。看一张图:
这里写图片描述

图中DecorView中还有一个LinearLayout,因为要存放2个FrameLayout:一个是顶部的ActionBar,一个是我们给activity设置的布局。
好了,我们继续看DecorView类中的superDispatchTouchEvent(event)方法:

public boolean superDispatchTouchEvent(MotionEvent event) {        return super.dispatchTouchEvent(event);    }

调用的是父类的dispatchTouchEvent(event),我们知道他是继承FrameLayout的,所以我们去ViewGroup中看dispatchTouchEvent(event)方法:

@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.            //cancle检查            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;            // 1、这个if是在没有拦截也没有取消事件时执行的            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;                //从down开始                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;                    //拿到child                    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.                        //这句话的意思就是根据用户的触摸位置找到相应的子view,添加进集合中,然后根据子view的count数反向遍历,为什么要反向遍历,因为view是一层一层覆盖的啊(比如Relativelayout)。然后我们找到最上层的子view,让他去相应事件                        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 (!canViewReceivePointerEvents(child)                                    || !isTransformedTouchPointInView(x, y, child, null)) {                                ev.setTargetAccessibilityFocus(false);                                continue;                            }                            //这个newTouchTarget就是在链表里找到的接收事件的view对应的newTouchTarget ,这个newTouchTarget 应该包含了一些信息吧,如果不=null,说明这个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方法里面就是去调用该子view的dispatchtouchevent方法,下面会贴源码继续分析                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {                                // Child wants to receive touch within its bounds.                                //如果这个子view的dispatchtouchevent返回true ,应该这一套下来有一个不管是view还是ViewGroup能处理事件才会返回true                                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();                    }                    //没找到接收事件的child                    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;                    }                }            }            //2、接下来的if else逻辑是当拦截了或者是cancle事件时执行。            // 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 {            //这里应该是cancle事件吧                // 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;    }
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;    }

新版API(我这是API26)做了一些改动,有些东西提出去做了,有些东西整合进来了。分析起来有点吃力,之前看的是老版本的API,简单一些,但主体都差不多。

总结一下:对于ViewGroup的dispatchTouchEvent方法:

首先看down是否拦截,如果没拦截,根据我们点击的位置找到子view,然后调用它的dispatchTouchEvent方法,如果这个子view的dispatchTouchEvent方法返回true,那么ViewGroup也返回true。如果子viewdispatchTouchEvent返回false,代表不能处理事件,那么对于ViewGroup而言就要自己处理了,他会先判断是是cancle还是up事件,如果是cancle或者up得话,就将拦截事件的标志置为默认的false(可以理解当我们设置了拦截事件后,我们抬起手指或取消touch的时候会将这个标志置为false。所以说disallowIntercept在我们每次down的时候都是false。)。然后因为ViewGroup自己处理,就要调用ViewGroup的super.dispatchtouchevent方法,如果是up或者cancle,那么就传入up或cancle事件,并返回。下面还有,下面的逻辑down事件不会走,move和up可能会走到这里,如果move或up拦截了事件的话,那么就将cancle事件交给子view,ViewGroup返回true,表示消费了事件。

如果调用,那么最终会调用到我们想要点击的子view,执行他的dispatchtouchevent方法。然后根据返回情况向上返回,就是ViewGroup的dispatchtouchevent返回值是根据其子view的dispatchtouchevent返回值而定的。
有一张图:
这里写图片描述

view的dispatchtouchevent方法:

public boolean dispatchTouchEvent(MotionEvent event) {      if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&              mOnTouchListener.onTouch(this, event)) {          return true;      }      return onTouchEvent(event);  }

如果view设置了touchlistener,enable,而且ontouch方法返回了true的话,就不会执行ontouchevent了,那么点击事件就无法执行了。否则就执行ontouchevent方法。
我们看onTouchEvent方法:

  1. public boolean onTouchEvent(MotionEvent event) {    2.       final int viewFlags = mViewFlags;    3.     4.       if ((viewFlags & ENABLED_MASK) == DISABLED) {    5.           return (((viewFlags & CLICKABLE) == CLICKABLE ||    6.                   (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));    7.       }    8.     9.       //如果设置了Touch代理,就交给代理来处理,mTouchDelegate默认是null    10.       if (mTouchDelegate != null) {    11.           if (mTouchDelegate.onTouchEvent(event)) {    12.               return true;    13.           }    14.       }    15.     16.       //如果View是clickable或者longClickable的onTouchEvent就返回true, 否则返回false    17.       if (((viewFlags & CLICKABLE) == CLICKABLE ||    18.               (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {    19.           switch (event.getAction()) {    20.               case MotionEvent.ACTION_UP:    21.                   boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;    22.                   if ((mPrivateFlags & PRESSED) != 0 || prepressed) {    23.                       boolean focusTaken = false;    24.                       if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {    25.                           focusTaken = requestFocus();    26.                       }    27.     28.                       if (!mHasPerformedLongPress) {    29.                           removeLongPressCallback();    30.     31.                           if (!focusTaken) {    32.                               if (mPerformClick == null) {    33.                                   mPerformClick = new PerformClick();    34.                               }    35.                               if (!post(mPerformClick)) {    36.                                   performClick();    37.                               }    38.                           }    39.                       }    40.     41.                       if (mUnsetPressedState == null) {    42.                           mUnsetPressedState = new UnsetPressedState();    43.                       }    44.     45.                       if (prepressed) {    46.                           mPrivateFlags |= PRESSED;    47.                           refreshDrawableState();    48.                           postDelayed(mUnsetPressedState,    49.                                   ViewConfiguration.getPressedStateDuration());    50.                       } else if (!post(mUnsetPressedState)) {    51.                           mUnsetPressedState.run();    52.                       }    53.                       removeTapCallback();    54.                   }    55.                   break;    56.     57.               case MotionEvent.ACTION_DOWN:    58.                   if (mPendingCheckForTap == null) {    59.                       mPendingCheckForTap = new CheckForTap();    60.                   }    61.                   mPrivateFlags |= PREPRESSED;    62.                   mHasPerformedLongPress = false;    63.                   postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());    64.                   break;    65.     66.               case MotionEvent.ACTION_CANCEL:    67.                   mPrivateFlags &= ~PRESSED;    68.                   refreshDrawableState();    69.                   removeTapCallback();    70.                   break;    71.     72.               case MotionEvent.ACTION_MOVE:    73.                   final int x = (int) event.getX();    74.                   final int y = (int) event.getY();    75.     76.                   //当手指在View上面滑动超过View的边界,    77.                   int slop = mTouchSlop;    78.                   if ((x < 0 - slop) || (x >= getWidth() + slop) ||    79.                           (y < 0 - slop) || (y >= getHeight() + slop)) {    80.                       // Outside button    81.                       removeTapCallback();    82.                       if ((mPrivateFlags & PRESSED) != 0) {    83.                           removeLongPressCallback();    84.     85.                           mPrivateFlags &= ~PRESSED;    86.                           refreshDrawableState();    87.                       }    88.                   }    89.                   break;    90.           }    91.           return true;    92.       }    93.     94.       return false;    95.   } 

这个就是处理点击事件了,长点击在down里面执行,短点击在up里执行。
down里面会发送一个post延迟消息,用来判断是否是长点击事件即是否达到系统设置的长点击事件时间,而且要设置了onLongClickListener的话,达到事件后就去执行长点击事件,如果onLongClick返回了true,那么会有一个标志mHasPerformedLongPress被置为true,相当于表示事件被消费了。在up事件中if会对这个标志取反,如果被消费了,那么就不会进入if里面,那么点击事件不会得到执行了。但是up这个时间还是会执行的,只不过不执行点击事件了。

事件分发应该就是这样大致的流程,有几点注意:

  1. 我们重写拦截事件的方法去拦截事件,默认拦截的是down事件。如果想拦截其他事件,那么就需要在方法里面对ev进行判断然后进行拦截操作。
  2. 如果在ViewGroup中对down拦截了,那么就会调用其的super.dispatchtouchevent方法,也就是view的,但是ViewGroup是没有设置ontouchlistener的,而且默认不能点击,所以根据情况自己去做相应的处理,默认不做处理的话就直接返回false了。
  3. 如果对move或者up事件进行拦截的话,子view得不到这些事件,点击事件无法执行,但是长点击事件是可以执行的,因为长点击是在down里面做的,这种情况ViewGroup就会将cancle交给子view,并直接返回true。

就到这里为止吧,以后项目中遇到什么问题再来补充,学习的过程也是一个不断纠正错误的过程。

下一篇我要写从启动Activity到用户可以点击view的这个过程发生了什么。

原创粉丝点击