从源码角度分析安卓事件分发机制

从百度上找了一张安卓事件分发U型图,下面从源码角度来分析这张图

当一个点击事件发生时,事件最先传到Activity的dispatchTouchEvent()进行事件分发

//
public boolean dispatchTouchEvent(MotionEvent ev) {
        

        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }
/*
    DOWN事件调用onUserInteraction(),该方法为空方法当此activity在栈顶时,触屏点击按home,            
 back,menu键等都会触发此方法,我们可以重写他
    getWindow() 查看源码该方法获得window类对象,window是一个抽象类,其唯一实现子类是 
 phoneWindow,在Source InSight查看phoneWindow的superDispatchTouchEvent(ev)方法
*/
@Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }
/*
    可以看到调用了mDecor的superDispatchTouchEvent(event)方法,mDecor是一个DecorView对象,
查看DecorView源码
*/
 public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }
/*
    可以看到调用父类的dispatchTouchEvent(event),DecorView继承自FrameLayout,FrameLayout继承            
 自ViewGroup,如上图所示此时传递到了ViewGroup的dispatchTouchEvent()方法
*/

接下来分析ViewGroup的源码,ViewGroup的dispatchTouchEvent方法比较长,截取了下面一段

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

/*
    定义了一个 boolean 值变量 intercept 来表示是否要拦截事件。onInterceptTouchEvent(ev) 对 
  intercept 进行赋值。大多数情况下,onInterceptTouchEvent() 返回值为 false,但我们完全可以通过重 
  写 onInterceptTouchEvent(ev) 来改变它的返回值,
*/

继续向下阅读源码

/*
    由于代码太长,我们节选一部分,并结合源码中注释去理解
*/

if (!canceled && !intercepted) {
                .......
              
                    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 ArrayList 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);
            .......
                }
            }
        .......
    return handled;
    }
/*

    if (!canceled && !intercepted) 忽略里面!canceled,实际上这里就是我们没有重写 
 onInterceptTouchEvent,让它返回true,就可以进到方法里,后面做了一个 For 循环,遍历 ViewGroup 
 下面的所有子 View,源码中有很多的if判断,实际就是一个一个判断点击位置是否是该子 View 的布局区 
  域。最后返回的handled实际就是 super.dispatchTouchEvent(event),即View的dispatchTouchEvent
*/

上边源码看到传递到了View的dispatchTouchEvent方法,我们继续看view的dispatchTouchEvent方法

  public boolean dispatchTouchEvent(MotionEvent event) {  

    ......
     boolean result = false;

        if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

    /*
    如果这几个条件有一个没有满足,那么就看下面第二个if语句。
   */

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

        return result;  
    .....
  }
  /* 
        节选了一部分代码
        li != null:ListenerInfor里面包含了几个Listener,如TouchListener、TouchListener,FocusChangeListener,
      LayoutChangeListeners,ScrollChangeListener等。我们可以认为为真
       只有以下3个条件都为真,result  = true,此时才能进入下一个循环 dispatchTouchEvent()才返回true;否则执行onTouchEvent()
       1. mOnTouchListener != null
       2. (mViewFlags & ENABLED_MASK) == ENABLED
       3. mOnTouchListener.onTouch(this, event)
       
   
    若在onTouch()返回true,就会让上述三个条件全部成立,从而使得View.dispatchTouchEvent()直接返回true,事件分发结束
    
 若在onTouch()返回false,就会使得上述三个条件不全部成立,从而使得View.dispatchTouchEvent() 
 中跳出If,执行下一个if语句,那么此时关键在于onTouchEvent这个方法,如 果onTouEvent返回true,那 
 么就表示消耗了这个事件。可以看出onTouchListener的优先级比onTouchEvent优先级高,
*/

上边第一个条件li.mOnTouchListener != null,mOnTouchListener变量在View.setOnTouchListener()方法里赋值,

即只要我们给控件注册了Touch事件,mOnTouchListener就一定被赋值(不为空)

public void setOnTouchListener(OnTouchListener l) {
        getListenerInfo().mOnTouchListener = l;
    }

第二个条件 (mViewFlags & ENABLED_MASK) == ENABLED
该条件是判断当前点击的控件是否为 enable,但由于基本 View 都是 enable 的,所以这个条件基本都返回 true。

第三个条件mOnTouchListener.onTouch(this, event)
即我们调用 setOnTouchListener() 时必须覆盖的方法 onTouch() 的返回值。

//即 回调控件注册Touch事件时的onTouch();需手动复写设置,具体如下(以按钮Button为例)
  
    button.setOnTouchListener(new OnTouchListener() {  
        @Override  
        public boolean onTouch(View v, MotionEvent event) {  
     
            return false;  
        }  
    });
// 若在onTouch()返回true,就会让上述三个条件全部成立,从而使得View.dispatchTouchEvent()直接返回true,事件分发结束
/*
 若在onTouch()返回false,就会使得上述三个条件不全部成立,从而使得View.dispatchTouchEvent() 
 中跳出If,执行下一个if语句,那么此时关键在于onTouchEvent这个方法,如 果onTouEvent返回true,那么 
 就表示消耗了这个事件。可以看出onTouchListener的优先级比onTouchEvent优先级高,
*/

 

从上述的分析,onTouch() 方法优先级高于 onTouchEvent(event) 方法

继续看onTouchEvent源码

public boolean onTouchEvent(MotionEvent event) {
      
        ......

        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
                (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
            switch (action) {
                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 && !mIgnoreNextUpEvent) {
                            // 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();
                                }
                            }
                        }

                        if (mUnsetPressedState == null) {
                            mUnsetPressedState = new UnsetPressedState();
                        }

                        if (prepressed) {
                            postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
                        } else if (!post(mUnsetPressedState)) {
                            // If the post failed, unpress right now
                            mUnsetPressedState.run();
                        }

                        removeTapCallback();
                    }
                    mIgnoreNextUpEvent = false;
                    break;

                case MotionEvent.ACTION_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) {
                        mPrivateFlags |= PFLAG_PREPRESSED;
                        if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        }
                        mPendingCheckForTap.x = event.getX();
                        mPendingCheckForTap.y = event.getY();
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        // Not inside a scrolling container, so show the feedback right away
                        setPressed(true, x, y);
                        checkForLongClick(0, x, y);
                    }
                    break;

                case MotionEvent.ACTION_CANCEL:
                    setPressed(false);
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    break;

                case MotionEvent.ACTION_MOVE:
                    drawableHotspotChanged(x, y);

                    // Be lenient about moving outside of buttons
                    if (!pointInView(x, y, mTouchSlop)) {
                        // Outside button
                        removeTapCallback();
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                            // Remove any future long press/tap checks
                            removeLongPressCallback();

                            setPressed(false);
                        }
                    }
                    break;
            }
            //消耗事件
            return true;
        }
        //未消耗事件
        return false;
    }

观察我们上边的代码,如果CLICKABLE或LONG_CLICKABLE或CONTEXT_CLICKABLE满足任何一个,那么就返回true,也就是dispatchTouchEvent返回true,消耗了这个事件。如果都不满足就返回false,也就是没有消耗这个事件。需要注意的是在switch语句中,if语句里面判断MotionEvent.ACTION_UP:的时候,如果满足里面的情况会调用performClick()

public boolean performClick() {
        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);
        return result;
    }

只要我们通过setOnClickListener()为控件View注册1个点击事件 那么就会给mOnClickListener变量赋值(即不为空) 则会往下回调onClick() & performClick()返回true,前边分析过onTouch优先级高于onToucheEvent,所以方法执行优先级为:

onTouch>onToucheEvent>onClick

你可能感兴趣的:(从源码角度分析安卓事件分发机制)