Android事件分发机制

Android中当一个点击事件发生后,会根据事件分发机制进行处理该事件。 在点击事件分发的过程中,主要由3个方法来共同完成。 1、 dispatchTouchEvent(MotionEventev):用来分发事件。 2、 onInterceptTouchEvent(MotionEventev):用来判断是否拦截事件。 3、 onTouchEvent(MotionEventevent):用来处理点击事件。
该过程的伪代码:
        public boolean dispatchTouchEvent(MotionEvent ev){            boolean consume=false;            if(onInterceptTouchEvent( ev){ //此处进行判断是否要拦截事件。                consume=onTouchEvent(ev);//拦截,则将事件交由当前View处理。            }else {                consume=child.dispatchTouchEvent(ev);//不拦截,将事件交给子View,让子View去分发该事件。            }            return consume;        }


Android中一个点击事件的传递顺序基本遵循:Activity -> Window -> ViewGroup -> View 所以在整个事件分发的过程中可能存在以下几个过程: 1、Activity对事件的分发过程 2、ViewGroup对事件的分发过程 3、View对事件的处理过程
Activity对事件的分发过程
当一个点击事件发生后,首先会交给当前的Activity,让它去进行事件分发。
  • Activity中dispatchTouchEvent的源码
       public boolean dispatchTouchEvent(MotionEvent ev) {            if (ev.getAction() == MotionEvent.ACTION_DOWN) {                onUserInteraction();            }            //getWindow():首先获取当当前的Window对象            //superDispatchTouchEvent(ev):在Window的该方法中实际调用的是DecorView的superDispatchTouchEvent方法            //因此该事件已经有Activity经过Window传给了DecorView            if (getWindow().superDispatchTouchEvent(ev)) {                return true;            }            return onTouchEvent(ev);        }

其中,DecorView是顶级View,也就是在Activity中通过 setContentView 方法所设置的View,顶级View也叫根View,顶级View一般来说都是要传递给ViewGroup。
  • Activity的onTouchEvent(MotionEventevent)方法
<span style="white-space:pre"></span>public boolean onTouchEvent(MotionEvent event) {            if (mWindow.shouldCloseOnTouch(this, event)) {                finish();                return true;            }            return false;        }


从源码的注释中,知道该方法只有在该Activity的View中没有任何View处理该事件的时候,才会调用此方法。

ViewGroup对点击事件的分发过程
当事件分发给顶级View,这时就需要顶级View对此事件进行分发处理。一般来说,顶级View都是ViewGroup。 ViewGroup对事件处理的基本流程是: 如果ViewGroup拦截事件,即onInterceptTouchEvent 方法返回true,则事件由ViewGroup处理,这时如果ViewGroup的mOnTouchListener被设置了,则onTouch 会被调用,否则onTouchEvent会被调用(在同时提供的前提下,onTouch可能会屏蔽掉onTouchEvent方法),在onTouchEvent中,如果设置了mOnClickListener,则onClick方法会被调用。 如果ViewGroup不拦截事件,则事件会传递给它所在的点击事件链上的字View,这时字View的dispatchTouchEvent 方法会继续分发事件。 看源码来分析,由于源码较长,所以只说关键部分。
  • ViewGroup的dispatchTouchEvent方法
 <span style="white-space:pre"></span>    //首先会判断是否需要拦截当前事件            //判断标准是1、当前事件的类型为ACTION_DOWN。2、mFirstTouchTarget!=null            //如果该事件类型为ACTION_DOWN时,则该条件满足,需要进行事件拦截判断。            //ViewGroup中mFirstTouchTarget变量只有在某个事件处理成功时,才会将该变量的值指向子View,所以当某次事件中ViewGroup拦截了             //事件,则mFirstTouchTarget的值会为null,所以以后的事件中mFirstTouchTarget!=null会始终不成立(当下次ACTION_DOWN事件再次             //到来时可能由子View处理,重新为mFirstTouchTarget赋值)。            // Check for interception.            final boolean intercepted;            if (actionMasked == MotionEvent.ACTION_DOWN                    || mFirstTouchTarget != null) {                //FLAG_DISALLOW_INTERCEPT代表一个标志位,一旦设置则表明ViewGroup将无法拦截除ACTION_DOWN事件外的其他事件                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;            }


上面的那段代码中是为intercepted 变量赋值,intercepted =true,则表明当前ViewGroup需要拦截事件,反之表明ViewGroup不拦截该事件。 当ViewGroup不拦截该事件时,那么该事件会交给子View处理。
                        final View[] children = mChildren;                        //遍历所有子View                        for (int i = childrenCount - 1; i >= 0; i--) {                            final int childIndex = customOrder                                    ? getChildDrawingOrder(childrenCount, i) : i;                            final View child = (preorderedList == null)                                    ? children[childIndex] : preorderedList.get(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;                            }                            //判断子View是否能够接收到点击事件                            //canViewReceivePointerEvents(child):判断View是否能够接收到点击事件,在此方法中判断了View是否可见和View                            //是否在播动画。                            // isTransformedTouchPointInView(x, y, child, null):点击事件的坐标是否落在子元素的区域内。                            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);                            //第三个参数传入了子元素的对象,如果该元素不为空,则调用子元素的dispatchTouchEvent方法,重新进行分发事件。                            //在此方法中如果第三个参数为null,则会调用父元素的dispatchTouchEvent方法                            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 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方法中为mFirstTouchTarget,同时跳出for循环。                                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);                        }




View对点击事件的处理过程
  • View的dispatchTouchEvent方法
               public boolean dispatchTouchEvent(MotionEvent event) {                    .......                    if (onFilterTouchEventForSecurity(event)) {                //noinspection SimplifiableIfStatement                ListenerInfo li = mListenerInfo;                //首先会在此处判断mOnTouchListener 是否存在,存在则会执行onTouch,当onTouch方法的返回值是true时就不会再执行View的onTouchEvent(event)方法                if (li != null && li.mOnTouchListener != null                        && (mViewFlags & ENABLED_MASK) == ENABLED                        && li.mOnTouchListener.onTouch(this, event)) {                    result = true;                }                //当result =true时就不会再执行onTouchEvent(event)(由于&&具有短路功能)                if (!result && onTouchEvent(event)) {                    result = true;                }            }            ......            return result;        }


从dispatchTouchEvent方法中我们可以得出一个结论: onTouch方 法的优先级高于onTouchEvent方法
  • View的onTouchEvent(event)
             //虽然此时view处于不可用状态,但照样会消耗点击事件            if ((viewFlags & ENABLED_MASK) == DISABLED) {                if (event.getAction() == 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));            }            //以下是对点击事件的具体处理            if (((viewFlags & CLICKABLE) == CLICKABLE ||                    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {                switch (event.getAction()) {                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) {                                // 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.                                    if (mPerformClick == null) {                                        mPerformClick = new PerformClick();                                    }                                    if (!post(mPerformClick)) {                                        performClick();//这么多代码,关键就在这一句,调用此方法会响应onClick                                    }                            }                        }



  • View的performClick()方法
在此方法中可能会处理onClick
              public boolean performClick() {                final boolean result;                final ListenerInfo li = mListenerInfo;                //在此处会判断mOnClickListener 是否存在,存在则会执行onClick方法                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;            }





对于Android事件分发机制的结论:
1、每一次的点击事件都会产生一个事件序列,而事件序列是以down事件开始,中间含有数量不定的move事件,最终以up事件结束。 2、正常情况下,一个事件序列只能被一个View消费掉,即一旦某个View拦截了事件,则该序列中的所有事件都会交友该View处理。 3、当View拦截事件后,其onInterceptTouchEvent(MotionEventev)方法不会再被调用,即不再去调用该方法去询问View是否要拦截事件。 4、ViewGroup默认不拦截事件,Android源码中onInterceptTouchEvent(MotionEventev)方法默认返回false。 5、View没有onInterceptTouchEvent方法,一旦有点击事件传递给他,那么他的onTouchEvent(event)就会被调用(除非设置了OnTouchListener,并且onTouch方法的返回值为true,那么此时onTouchEvent不会被调用)。 6、View的enable属性不会影响onTouchEvent事件的返回值。 7、onCLick会发生的前提是当前View是可点击的,并且收到了down和up事件,其中down事件是当前View处理该事件系列的前提,up事件是onClick发生的事件。 8、事件的传递过程是由外向内的,即事件总是先传给父元素,然后再由父元素分发给子元素。










更多相关文章

  1. Android(安卓)底层渲染 - 屏幕刷新机制源码分析
  2. android中GridView关于间距的属性值介绍
  3. Android多屏幕支持Multi-WindowSupport
  4. Android解析XML
  5. Android(安卓)studio 连接数据库小经历遇到的问题以及解决方法(ja
  6. Android(安卓)属性动画简介
  7. 从零开始学习android
  8. 详解 Android(安卓)的 Activity 组件
  9. 详解 Android(安卓)的 Activity 组件

随机推荐

  1. Android(安卓)RelativeLayout 属性
  2. GridView相关
  3. Android使用AudioRecord遇到的问题与解决
  4. Android(安卓)ANR问题分析思路
  5. Android(安卓)layout属性大全
  6. 海康威视Android(安卓)SDK,即萤石Android(
  7. android 电容屏(三):驱动调试之驱动程序分析
  8. 简述修改logo以及文字
  9. 关于Android锁屏的问题
  10. android中的数据库操作