Android事件分发机制

首先我们应当心中有数,学习Android事件分发机制到底应该抓住哪些步骤?

 

  • Android事件分发流程如下:(必须熟记

      Android事件分发顺序:Activity(Window) -> ViewGroup -> View

然后我们来看看事件分发的整体流程图

 

其中:

super   调用父类函数

true    消费该事件,不再往下传递

false   一般情况是和super的传递方式一样的,就是在dispatchTouchEvent()函数传false的时候,会传递到上一级的onTouchEvent();

 

假设我们设定一个场景,activity总包含一个viewGroup,viewGroup中有一个View,那么这个事件是怎样传递的呢?

        在我们按下这个View:(在demo中全部使用super,不更改自定义的ViewGroup和View),首先这个事件会传递给activity的dispatchTouchEvent()函数,当这个函数return true的时候,activity(dispatchTouchEvent)------(super)----->ViewGroup(dispatchTouchEvent)------(super)----->ViewGroup(onInterceptTouchEvent)------(super)----->View(dispatchTouchEvent)------(super)----->View(onTouchEvent)------(super)----->activity(dispatchTouchEvent)

这三个函数返回值区分:

A、dispatchTouchEvent:

      ①super,和上述设定场景中事件传递方式相同

      ②false,会跳转到上一级的onTouchEvent()函数中

      ③true,直接停止此次Touch事件,但是后续的move和up事件还是会往下传的

 

B、onInterceptTouchEvent:

      ①super,也就是默认方式,会return false,表示容器不拦截此次事件,事件会继续下发下一级dispatchTouchEvent()函数

      ②false,return false,表示容器不拦截此次事件,事件会继续下发下一级dispatchTouchEvent()函数

      ③true,return true,表示容器拦截此次事件,事件会被传送到viewGroup.onTouchEvent()

 

 

C、onTouchEvent:

      ①super,也就是默认方式,会return false,表示容器不消费此次事件,事件会上传给上一级onTouchEvent()函数

      ②false,return false,表示容器不消费此次事件,事件会上传给上一级onTouchEvent()函数

      ③true,return true,表示消费此次事件,事件会消失掉

 
三者的关系:(我们可以使用一段伪代码来说明)

 

       //首先每一个touch事件都会进入activity的dispatchTouchEvent事件
	public  boolean  dispatchTouchEvent(MotiveEvent ev){
		//代表是否消耗事件
   	 	boolean consume = false;
		if(onInterceptTouchEvent(ev)){     
		   //如果onInterceptTouchEvent()返回true则代表当前View拦截了点击事件 
     		   //则该点击事件则会交给当前View进行处理 		
     		   //即调用onTouchEvent ()方法去处理点击事件
                   consume = onTouchEvent(ev);
  		}else{	
		   //如果onInterceptTouchEvent()返回false则代表当前View不拦截点击事件 
		   //则该点击事件则会继续传递给它的子元素 	
		   //子元素的dispatchTouchEvent()就会被调用,重复上述过程 	
		   //直到点击事件被最终处理为止
                   consume = child.dispatchTouchEvent(ev);
		}
		return consume 
	}

 

 

 

 

Android事件分发机制源码分析

对于事件分发机制源码,我们可以分为三个方面:

 

A、Activity事件分发的源码:

public boolean dispatchTouchEvent(MotionEvent ev) { //关注点1 
        // 一般事件列开始都是DOWN,所以这里基本是true 
        if (ev.getAction() == MotionEvent.ACTION_DOWN) { //关注点2 
            onUserInteraction();
        } //关注点3 
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

    在Activity中的dispatchTouchEvent()源码中,
    getWindow().superDispatchTouchEvent(ev)实际上是执行了viewGroup.dispatchTouchEvent,
    所以当我们执行Activity的dispatchTouchEvent()函数时,实际上相当于执行了viewGroup.dispatchTouchEvent,
    这样事件就从Activity转到了ViewGroup,当一个事件产生时,调用顺序如下:
    ①事件最先传入Activity的dispatchTouchEvent()进行事件分发

②调用window类实现了phoneWindow的superDisPatchTouchEvent()

③调用DecorView的superDispatchTouchEvent()

④最终调用DecorView父类的dispatchTouchEvent()也就是ViewGroup的dispatchTouchEvent()

 

B、ViewGroup的事件分发机制源码:

  public boolean dispatchTouchEvent(MotionEvent ev) {
        final int action = ev.getAction();
        final float xf = ev.getX();
        final float yf = ev.getY();
        final float scrolledXFloat = xf + mScrollX;
        final float scrolledYFloat = yf + mScrollY;
        final Rect frame = mTempRect;
        boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
        if (action == MotionEvent.ACTION_DOWN) {
            if (mMotionTarget != null) {
                mMotionTarget = null;
            } //看这个If判断语句
            // 第一个判断值disallowIntercept:是否禁用事件拦截的功能(默认是false)
            // 可以通过调用requestDisallowInterceptTouchEvent方法对这个值进行修改。
            // 第二个判断值: !onInterceptTouchEvent(ev):对onInterceptTouchEvent()返回值取反
            // 如果我们在onInterceptTouchEvent()中返回false,就会让第二个值为true,从而进入到条件判断的内部
            // 如果我们在onInterceptTouchEvent()中返回true,就会让第二个值为false,从而跳出了这个条件判断。
            // 关于onInterceptTouchEvent()请看下面分析(关注点1) 
            if (disallowIntercept || !onInterceptTouchEvent(ev)) {
                ev.setAction(MotionEvent.ACTION_DOWN);
                final int scrolledXInt = (int) scrolledXFloat;
                final int scrolledYInt = (int) scrolledYFloat;
                final View[] children = mChildren;
                final int count = mChildrenCount;
                //通过for循环,遍历了当前ViewGroup下的所有子View 
                for (int i = count - 1; i >= 0; i--) {
                    final View child = children[i];
                    if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                        child.getHitRect(frame);
                        //判断当前遍历的View是不是正在点击的View 
                        // 如果是,则进入条件判断内部 
                        if (frame.contains(scrolledXInt, scrolledYInt)) {
                            final float xc = scrolledXFloat - child.mLeft;
                            final float yc = scrolledYFloat - child.mTop;
                            ev.setLocation(xc, yc);
                            child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; //关注点2 
                            // 条件判断的内部调用了该View的dispatchTouchEvent()方法(具体请看下面的View事件分发机制) 
                            // 实现了点击事件从ViewGroup到View的传递 
                            if (child.dispatchTouchEvent(ev)) {
                                //调用子View的dispatchTouchEvent后是有返回值的 
                                // 如果这个控件是可点击的话,那么点击该控件时,dispatchTouchEvent的返回值必定是true 
                                // 因此会导致条件判断成立 mMotionTarget = child; 
                                // 于是给ViewGroup的dispatchTouchEvent方法直接返回了true,这样就导致后面的代码无法执行,直接跳出 
                                // 即把ViewGroup的touch事件拦截掉 
                                return true;
                            }
                        }
                    }
                }
            }
        }
        boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) || (action == MotionEvent.ACTION_CANCEL);
        if (isUpOrCancel) {
            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        }
        final View target = mMotionTarget; //关注点3
        // 没有任何View接收事件的情况,即点击空白处情况 
        if (target == null) {
            ev.setLocation(xf, yf);
            if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
                ev.setAction(MotionEvent.ACTION_CANCEL);
                mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
            }
            //调用ViewGroup的父类View的dispatchTouchEvent()
            // 因此会执行ViewGroup的onTouch()、onTouchEvent()
            // 实现了点击事件从ViewGroup到View的传递 
            return super.dispatchTouchEvent(ev);
        }
        //之后的代码在一般情况下是走不到的了,我们也就不再继续往下分析。 
        if (!disallowIntercept && onInterceptTouchEvent(ev)) {
            final float xc = scrolledXFloat - (float) target.mLeft;
            final float yc = scrolledYFloat - (float) target.mTop;
            mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
            ev.setAction(MotionEvent.ACTION_CANCEL);
            ev.setLocation(xc, yc);
            if (!target.dispatchTouchEvent(ev)) {
            }
            mMotionTarget = null;
            return true;
        }
        if (isUpOrCancel) {
            mMotionTarget = null;
        }
        final float xc = scrolledXFloat - (float) target.mLeft;
        final float yc = scrolledYFloat - (float) target.mTop;
        ev.setLocation(xc, yc);
        if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
            ev.setAction(MotionEvent.ACTION_CANCEL);
            target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
            mMotionTarget = null;
        }
        return target.dispatchTouchEvent(ev);
    }

 

关注点1(onInterceptTouchEvent()源码分析)

ViewGroup每次在做分发时,需要调用onInterceptTouchEvent()是否拦截事件;源码分析如下:

 

public boolean onInterceptTouchEvent(MotionEvent ev) {  
    return false;  
} 
返回false =不拦截(默认),允许事件继续往下传递(向子View传递);
因为子View也需要该事件,所以onInterceptTouchEvent拦截器
return super.onInterceptTouchEvent()和return false是一样的 = 不会拦截

关注点2

当点击了某个控件:
  1. 调用该控件所在布局(ViewGroup)的dispatchTouchEvent()
  2. 在布局的dispatchTouchEvent()中找到被点击的相应控件
  3. 再去调用该控件的dispatchTouchEvent(),实现了点击事件从ViewGroup到View的传递,Android事件分发是先传递到ViewGroup,再由ViewGroup传递到View,在ViewGroup中通过onInterceptTouchEvent()对事件传递进行拦截
  1. onInterceptTouchEvent方法返回true代表拦截事件,即不允许事件继续向子View传递;
  2. 返回false代表不拦截事件,即允许事件继续向子View传递;(默认返回false)
  3. 子View中如果将传递的事件消费掉,ViewGroup中将无法接收到任何事件。

 

 

4.3 View事件的分发机制

View中dispatchTouchEvent()的源码分析:

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


从上面可以看出:
第一个条件:mOnTouchListener!= null
注:mOnTouchListener是在View类下setOnTouchListener方法里赋值的
 
public void setOnTouchListener(OnTouchListener l) {
    //即只要我们给控件注册了Touch事件,mOnTouchListener就一定被赋值(不为空)    
    mOnTouchListener = l;
}
第二个条件:(mViewFlags & ENABLED_MASK) == ENABLED
 

    该条件是判断当前点击的控件是否enable由于很多View默认是enable的,因此该条件恒定为true

第三个条件:mOnTouchListener.onTouch(this, event)回调控件注册Touch事件时的onTouch方法

//手动调用设置
button.setOnTouchListener(new

OnTouchListener() {
    @Override public boolean onTouch (View v, MotionEvent event){
        return false;
    }
});
  • 如果在onTouch方法返回true,就会让上述三个条件全部成立,从而整个方法直接返回true。
  • 如果在onTouch方法里返回false,就会去执行onTouchEvent(event)方法。

接下来,我们继续看:onTouchEvent(event)的源码分析

public boolean onTouchEvent(MotionEvent event) {
        final int viewFlags = mViewFlags;
        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            // 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 (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }
        //如果该控件是可以点击的就会进入到下两行的switch判断中去;
        if (((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
            //如果当前的事件是抬起手指,则会进入到MotionEvent.ACTION_UP这个case当中。
            switch (event.getAction()) {
                case MotionEvent.ACTION_UP:
                    boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
                    // 在经过种种判断之后,会执行到关注点1的performClick()方法。
                    // 请往下看关注点1
                    if ((mPrivateFlags & 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 (!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)) { //关注点1
                                    // 请往下看performClick()的源码分析
                                    performClick();
                                }
                            }
                        }
                        if (mUnsetPressedState == null) {
                            mUnsetPressedState = new UnsetPressedState();
                        }
                        if (prepressed) {
                            mPrivateFlags |= PRESSED;
                            refreshDrawableState();
                            postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration());
                        } else if (!post(mUnsetPressedState)) {
                            // If the post failed, unpress right now
                            mUnsetPressedState.run();
                        }
                        removeTapCallback();
                    }
                    break;
                case MotionEvent.ACTION_DOWN:
                    if (mPendingCheckForTap == null) {
                        mPendingCheckForTap = new CheckForTap();
                    }
                    mPrivateFlags |= PREPRESSED;
                    mHasPerformedLongPress = false;
                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    break;
                case MotionEvent.ACTION_CANCEL:
                    mPrivateFlags &= ~PRESSED;
                    refreshDrawableState();
                    removeTapCallback();
                    break;
                case MotionEvent.ACTION_MOVE:
                    final int x = (int) event.getX();
                    final int y = (int) event.getY();
                    // Be lenient about moving outside of buttons 
                    int slop = mTouchSlop;
                    if ((x < 0 - slop) || (x >= getWidth() + slop)
                            || (y < 0 - slop) || (y >= getHeight() + slop)) {
                        // Outside button 
                        removeTapCallback();
                        if ((mPrivateFlags & PRESSED) != 0) {
                            // Remove any future long press/tap checks 
                            removeLongPressCallback();
                            // Need to switch from pressed to not pressed 
                            mPrivateFlags &= ~PRESSED;
                            refreshDrawableState();
                        }
                    }
                    break;
            }
            //如果该控件是可以点击的,就一定会返回true 
            return true;
        }
        //如果该控件是可以点击的,就一定会返回false 
        return false;
    }
关注点1:
performClick()的源码分析

public boolean performClick() {
    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
    if (mOnClickListener != null) {
        playSoundEffect(SoundEffectConstants.CLICK);
        mOnClickListener.onClick(this);
        return true;
    }
    return false;
}
  • 只要mOnClickListener不为null,就会去调用onClick方法;
  • 那么,mOnClickListener又是在哪里赋值的呢?请继续看:
public void setOnClickListener(OnClickListener l) {
    if (!isClickable()) {
        setClickable(true);
    }
    mOnClickListener = l;
}
  • 当我们通过调用setOnClickListener方法来给控件注册一个点击事件时,就会给mOnClickListener赋值(不为空),即会回调onClick()。
 

结论

  1. onTouch()的执行高于onClick()
  2. 每当控件被点击时:如果在回调onTouch()里返回false,就会让dispatchTouchEvent方法返回false,
  3. 那么就会执行onTouchEvent();
  4. 如果回调了setOnClickListener()来给控件注册点击事件的话,最后会在performClick()方法里回调onClick()。
  5. onTouch()返回false(该事件没被onTouch()消费掉) 
    dispatchTouchEvent()返回false(继续向下传递) 
    执行onTouchEvent() = 执行OnClick()
    如果在回调onTouch()里返回true,就会让dispatchTouchEvent方法返回true,
    那么将不会执行onTouchEvent(),即onClick()也不会执行;
    
    onTouch()返回true(该事件被onTouch()消费掉) 
    dispatchTouchEvent()返回true(不会再继续向下传递) 
    不会执行onTouchEvent() = 不会执行OnClick()
 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值