Android事件分发

view和viewGroup,是android事件分发中的主要角色。view中,处理事件的方法为dispatchTouchEvent和onTouchEvent,viewGroup继承自view,相关方法有三个,父view的onTouchEvent,重写了dispatchTouchEvent,增加了onInterceptTouchEvent。这篇文章,主要分析一下这个流程。先盗张图:在这里插入图片描述
如果之前已经对事件分发有些了解,我们首先要澄清一个知识点,避免进入误区。
viewGroup对应的父类是view,当viewgroup(上图②)调用它的super.dispatchTouchEvent方法执行的是当前②的父类 view的dispatchTouchEvent。并不是执行①的dispatchTouchEvent。这点很容易在理解时出现偏差导致进入误区。那事件是如何在view树中传递的呢?我们逐步分析系统的源码来解答。

1.viewGroup的dispatchTouchEvent

1.事件由activity发起,找到当前activity的decorView(FrameLayout根布局),调用decorView的dispatchTouchEvent

activity分发touch事件
public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }
PhoneWindow类中的调用   
public boolean superDispatchTouchEvent(MotionEvent event) {
	  return mDecor.superDispatchTouchEvent(event);
  }

2.于是乎如果我们理清了viewGroup间的事件传递,便弄懂了整个流程。源码不算长但是逻辑复杂,我们先理一个简单的伪代码的流程图

viewGroup的dispatch流程
  public boolean dispatchTouchEvent(MotionEvent ev){
        ....//其他处理
        View[] views=buildTouchDispatchChildList();//找到在该viewGroup中的子view
        for(int i=0;i<views.length;i++){
           //判断touch的位置在子view范围内 
            if(...){
            if(views[i].dispatchTouchEvent(ev))
              return true;
             }
        }
        ...//其他处理
    }
  
    

ViewGroup会遍历它包含着的子View(layout的层级先后顺序),如果该事件在touch的范围内(通过view的坐标和触摸的位置判断),调用子view的dispatchTouchEvent方法,而当子View为ViewGroup时,自然会按照上述方式遍历,如此递归调用,直到某一个view的返回值为true,则下发中断,后续事件由该view接收处理
再看一个复杂点的:

private View mTarget;
 @Override
 public boolean dispatchTouchEvent(MotionEvent ev) {
 	boolean handled =false;
 	如果是按下事件
 	if(ev.getAction()==MotionEvent.ACTION_DOWN){
 		向上一次处理了touch事件的子view发送cancel事件通知上一次事件取消。
 		 cancelAndClearTouchTargets(ev);

 	}
 
 	//如果有子view想拦截                               或者自己不用拦截
 	if(requestDisallowInterceptTouchEvent==true||onInterceptTouchEvent(ev)==false){
 		View target;
	 	if(ev.getAction()==MotionEvent.ACTION_DOWN){
	 		 View[] views=buildTouchDispatchChildList();//找到在该viewGroup中的子view
	        for(int i=0;i<views.length;i++){
	            if(touch的位置在子view范围内){
	            	if(views[i].dispatchTouchEvent(ev))
	            	mTarget=view[i];
	            	  handled=true;         
	              	break;  //break很重要,也即当有子view拦截事件后,不再向其他子view发射事件 
	             }
	        }
	 	}
 	}
	if(mFirstTouchTarget==null){
		//也即view的dispatchTouchEvent
		handled=super.dispatchTouchEvent(ev);
	}
	return handled;

}

1.viewGroup在ACTION_DOWN事件中,会先发送cancel事件。如果自己不需要拦截事件,或者有子view想拦截事件
2.如果自身的onInterceptTouchEvent(返回true),或者子view需要拦截(requestDisallowInterceptTouchEvent(true)),直接通过dispatchTransformedTouchEvent找出到底谁需要拦截(mFirstTouchTarget),交由它处理。

2.view的dispatchTouchEvent

1.我们先看一个简单的流程图,view的dispatchTouchEvent,主要是调用它的onTouchEvent

  view的dispatch流程主要处理了view的事件
    public boolean dispatchTouchEvent(MotionEvent ev){
        ....//其他处理
        return onTouchEvent(event);
    }

也即,根据view的onTouchEvent的返回结果,来判断是否需要拦截。

	public boolean dispatchTouchEvent(MotionEvent event) {
		boolean result =false;
		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;
            }
            return result;
            
     }  

假定在某一层级下,view的onTouchEvent在ACTION_DOWN时返回结果为true,即该view拦截了事件,进而影响viewGroup的dispatchTouchEvent的返回结果。后续事件,都将由该view来处理。

2.view的onTouchEvent

public boolean onTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;
        final int action = event.getAction();
      //如果view的touchDelegate不为空,则交由mTouchDelegate处理结果
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }
		if(ev.getAction()==MotionEvent.ACTION_DOWN){
		       //如果有长按监听发送长按延时消息
		}
		if(ev.getAction()==MotionEvent.ACTION_UP){
			//如果没执行过长按,有点击监听,执行点击事件
		}

        return false;
    }

view的touch事件,主要处理了tap,点击,长按等事件的逻辑处理。
1.如果view被set了TouchDelegate,则交由它处理,否则,会在actiondown是埋下一个长按的延时消息,如果是在滑动容器,会埋下一个setPressed的延时消息,在Up事件中,如果处理了长按,则不触发点击事件。
2.setPressed();该方法是设置view的按下状态(我们在写selector时经常用到该属性state_pressed)如果是在滑动容器,会延时100ms发送。如果有兴趣可以详细阅读关于setPressed的处理
3.如果有onTouchListener,返回onTouchListener的返回结果
如果没有,调用onTouchEvent,并返回该方法的执行结果。

总结一些经常迷惑的点:
1.onInterceptTouchEvent,requestDisallowInterceptTouchEvent到底有啥用
源码在判断该方法返回结果的条件是这样的。

if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {

若down事件时便进行intercept,mTarget会一直为null,因为mTarget是在Down事件中赋值的。由于mTarge为null,该ViewGroup的onTouchEvent事件被执行。即当前viewGroup处理该事件。若down时还未拦截,move时触发return true,若某个子View返回了true,接下去的Action_Move(如果上一个操作不是UP)将由ViewGroup的onTouchEvent处理,并向之前的子view发送cancel事件。
2.view在有onTouchListener并返回结果为true时,不再走onTouchEvent,需要程序员自己处理。
3.假如在每一层遍历的时候的dispatch结果都为false,即没有view处理事件时,会在dispatchTouchEvent完成时调用自己的onTouchEvent,网上通常说的U型图是这个意思。具体位置在`

if(mFirstTouchTarget==null){
		//也即view的dispatchTouchEvent
		handled=super.dispatchTouchEvent(ev);
	}

super,即是view的dispatch,会调用view的onTouchEvent。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值