View的事件体系_View的事件分发机制

点击事件的传递规则:
当一个MotionEvent产生后,系统需要把这个事件传递给一个具体的View,而这个过程就是事件的分发过程,这个过程由3个很重要的方法组成,dispatchTouchEvent,onInterceptTouchEvent和onTouchEvent,

public boolean dispatchTouchEvent(MotionEvent ev)(ViewGroup里面有的方法)
用来进行事件的分发.如果事件能够传递到当前的View,那么此方法一定会被调用,返回结果受当前View的onTouchEvent和下级子View的dispatchTouchEvent方法的影响,表示是否消耗当前事件

public boolean onInterceptTouchEvent(MotionEvent ev)(ViewGroup里面有的方法)
在上面的dispatchTouchEvent方法里面调用,用来判断是否拦截某个事件,如果当前View拦截了某个事件(例如down事件),那么在同一个事件序列当中,此方法不会被再次调用,返回结果表示是否拦截当前事件

说明:
同一个事件 序列是指:从手机接触屏幕的那一刻起,到手机离开屏幕的那一刻结束,在这个过程中所产生的一系列事件,这个事件序列以down事件开始,中间含有数量不定的move事件,最终以up事件结束

public boolean onTouchEvent(MotionEvent event)(View里面有的方法)
在上面的dispatchTouchEvent方法里面调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗(down事件返回false),则在同一个事件序列当中,当前view无法再次接受到(move和up)事件

这3个方法的关系可以用下面的为代码来表示:

public boolean dispatchTouchEvent(MotionEvent ev) {
   boolean consume=false;
    if (onInterceptTouchEvent(ev)) {
        //如果父类ViewGroup要拦截事件,那么它就会调用自己的onTouchEvent方法来处理事件
        consume=this.onTouchEvent(ev);
    }else{
        //如果父类ViewGroup不要事件,那么它就会让自己的孩子去分发事件
        consume=child.dispatchTouchEvent(ev);
    }
    return consume;
}

当一个点击事件产生后,它的传递过程遵循如下顺序:Activity–>window–>view,即事件总是先传递给Activity,Activity再传递给window,最后window再传递给顶级View

当一个View需要处理事件时,如果它设置了OnTouchListener,那么OnTouchListener中的onTouch方法会被回调,如果onTouch方法返回false,则当前的onTouchEven方法会被调用,如果返回true,就不会调用onTouchEven方法,所以OnTouchListener的优先级大于
onTouchEven,在onTouchEvent方法中,如果当前设置的有onCLickListener,那么它的onClick方法会被调用

优先级:OnTouchListener>onTouchEven(onClick()方法在这个此方法里面)
正常情况下:一个事件序列只能被一个View拦截且消耗

当一个事件传到view上时:首先是触发action_down方法,action_down返回true表示此View要消耗这个事件,那么同一个事件序列的action_move和action_up都会传给它,action_down返回false 表示此view不要这个事件,那么同一个事件序列的action_move和action_up都不会传到它那里

事件的传递过程是由外向内,即事件总是先传递给父元素,然后再由父元素分发给子View,通过getParent().requestDisallowInterceptTouchEvent(boolean disallowIntercept);方法可以在子元素中干预父元素的事件分发过程,但是父元素的action_down事件子元素干预不了

事件分发的源码解析:

1.Activity对点击事件的分发过程:

点击操作发生时,事件最先传递给当前的acivity,由activity的dispatchTouchEvent方法来进行分发,具体的工作是由activity内部的window来完成,window会把事件传递给decor view,decor view一般就是当前界面的底层容器(即setContentView所设置的View的父容器),通过Activity.getWindow.getDecorView()可以获得.decor view继承帧布局FrameLayout

源码:Activity#dispatchTouchEvent(MotionEvent ev)

/**
     * Called to process touch screen events.  You can override this to
     * intercept all touch screen events before they are dispatched to the
     * window.  Be sure to call this implementation for touch screen events
     * that should be handled normally.
     * @param ev The touch screen event.
     * @return boolean Return true if this event was consumed.
     */
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
          //这个方法是个空实现 interaction:相互作用,相互影响
          //我们可以重写添加我们的逻辑
            onUserInteraction();
        }
        //交由activity所属的window来派发事件
        //返回true;表示有View消耗了这个事件
        //返回false;表示没有View消耗事件,需要act自己来处理
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        //如果没有view消耗事件,那么act自己的onTouchEven会调用
        return onTouchEvent(ev);
    }

2.window.对点击事件的分发过程:

接下来看一下window是如何把事件传递给ViewGroup的

/* <p>The only existing implementation of this abstract class is
 * android.view.PhoneWindow, which you should instantiate when needing a
 * Window.
 */
public abstract class Window {...}

源码:window#superDispatchTouchEvent(MotionEvent event)

/**
     * Used by custom windows, such as Dialog, to pass the touch screen event
     * further down the view hierarchy. Application developers should
     * not need to implement or call this.
     */
    public abstract boolean superDispatchTouchEvent(MotionEvent event);

通过源码和它的英文解释,我们知道Window 是一个抽象类,window的唯一实现类是PhoneWindow
(如何进到PhoneWindow源码里面看具体代码呢,这个有个小技巧:找到window里面的一个抽象方法 例如这里的getDecorView()方法,然后按住ctrl按键同时鼠标点击getView(),出现如下图的情况
在这里插入图片描述
找到PhoneWindow.java类,点击进去,就可以进到PhoneWindow.java类里面了)

/** @hide */
public class PhoneWindow extends Window implements MenuBuilder.Callback {...}

源码:PhoneWindow#superDispatchTouchEvent

//PhoneWindow实现window的superDispatchTouchEvent抽象方法
 public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }

由上所知PhoneWindow把事件直接传递给了DecorView,至此window的事件就分发完成了

3.DecorView.对点击事件的分发过程:
首先我们看一下DecorView是什么东西
源码: phoneWindow#mDecor

 // This is the top-level view of the window, containing the window decor.
    private DecorView mDecor;
/** @hide */
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {...}

由以上的源码和英文解释可以知道DecorView 是顶级View即根View,它继承与FrameLayout.通过getWindow().getDecorView()获得,我们常在activity中见的setContentView所设置的View,就可以通过mDecorView.findViewById(android.R.id.content).getChildAt(0)来得到

我们大致看一下ViewGroup对事件的分发:

源码 ViewGroup#dispatchTouchEvent

 @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
        }
        ...代码省略
        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;
            }
          ....代码省略 
            // 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 {
                // 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;
                }
            }
        ....代码省略 
        if (!handled && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
        }
        return handled;
    }

这里我们再回顾一下viewGroup事件的分发过程:
点击事件到达顶级View(一般是一个ViewGroup)以后,会调用ViewGroup的DispatchTouchEvent方法,如果顶级View拦截事件即onInterceptTouchEven方法返回true,则事件由顶级ViewGroup自己处理,这时候,如果ViewGroup的onTouchListener被设置了,则onTouchListener的onTouch()方法会被调用,否则onTouchEven方法会被调用,也就是说如果都提供的话,onTouchListener()方法会屏蔽onTouchEven方法(),在onTouchEven方法中如果设置了onClickListerner,则onClick方法会被调用.如果viewGroup不拦截事件,事件会分发给它的子View,这时子View的DispatchTouchEvent方法会被调用,到此事件已经从顶级VIewGroup传给了它的子view,即下来的传递过程和顶级ViewGroup的过程是一样的,如此循环完成整个事件的分发

说明:
1当子view通过设置父View不要拦截事件后即
getParent().requestDisallowInterceptTouchEvent(true);
父ViewGroup就拦截不了ACTION_MOVE和 ACTION_UP事件,但是 还是可以拦截ACTION_DOWN事件,
理由:ViewGrop会在ACTION_DOWN事件到来时做重置状态的操作,resetTouchState()方法中会对FLAG_DISALLOW_INTERCEPT进行重置,因此子view设置requestDisallowInterceptTouchEvent(true)不能影响ViewGroup对ACTION_DOWN事件的处理,源码如下

源码 ViewGroup#dispatchTouchEvent

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

4.View对点击事件的分发
源码: view#dispatchTouchEvent

public boolean dispatchTouchEvent(MotionEvent event) {
  ...省略代码
   if (onFilterTouchEventForSecurity(event)) {
      if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }

        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }
 ...省略代码
}

首先View会判断有没有设置OnTouchListener,如果有,则执行li.mOnTouchListener.onTouch(this, event)返回true,否则onTouchEvent(event)方法会被调用,我们在看一下onTouchEvent(event)方法里面的源码

源码:view#onTouchEvent(MotionEvent event)

public boolean onTouchEvent(MotionEvent event) {
  ...省略代码
   switch (action) {
       case MotionEvent.ACTION_UP:
        ...省略代码
       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();
                 }
             }
         }     
  ...省略代码
}

由以上源码可知在 MotionEvent.ACTION_UP事件里 如果 if (!post(mPerformClick)) 条件为真的情况下,会执行performClick()方法

源码view#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);
        notifyEnterOrExitForAutoFillIfNeeded(true);
        return result;
    }

在performClick() 里面如果 if (li != null && li.mOnClickListener != null) 条件为真的情况下,会执行 li.mOnClickListener.onClick(this);

至此,事件分发机制已经讲完啦~(容我喝口水,吃跟辣条休息一下,精彩马上继续^- ^)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值