欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

一、事件分发

程序员文章站 2022-05-14 15:27:03
...

有一段时间没更博客了,想象我写博客的初衷主要有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的这个过程发生了什么。

相关标签: 事件分发