view事件分发Android开发首页投稿(暂停使用,暂停投稿)

你的android事件传递知识能解答这个问题?

2016-07-14  本文已影响660人  岁月留痕

前言

突然想看看android 下拉刷新源码的实现,其中有scroller的运用,找到了郭霖大神几年前写的一个scroller简化版的viewPager 例子,照着例子敲了一遍,大致意思上在一个viewgroup上摆放一排view,根据滑动的距离调用viewGroup的scrollerby滑动,我在这个viewGroup上随意的放上了TextView,布局如下:

FDBEA756-8753-4DFF-95B4-165E047EE332.png

滑动了半天却是没有响应滑动事件.
布局很简单就不贴了,有需要的同学可以看下搜下郭神的这篇文章《Android Scroller完全解析,关于Scroller你所需知道的一切》,随意在button上点击后,log日志如下:

07-14 17:44:07.009 29370-29370/com.tomchen.zoom E/ScrollerViewGroup: dispatchTouchEvent0
07-14 17:44:07.009 29370-29370/com.tomchen.zoom E/ScrollerViewGroup: onInterceptTouchEvent0
07-14 17:44:07.010 29370-29370/com.tomchen.zoom E/TButton: dispatchTouchEvent0
07-14 17:44:07.011 29370-29370/com.tomchen.zoom E/TButton: onTouchEvent0
07-14 17:44:07.011 29370-29370/com.tomchen.zoom E/ScrollerViewGroup: onTouchEvent0

有兴趣的同学可以试试在其他地方点一点,结果也会一致,就是没有调用后续的ACTION_MOVE,ACTION_UP,跳回到viewGroup的onTouchEvent后事件就被掐掉了,为什么呢。

网上事件传递的文章很多,《3副图看清事件传递》...,我都快倒背如流。还有些用了很长的篇幅来讲解,看得我似懂非懂,也有人直接给出了答案,给子元素设置clickable=true。

如果你能说清楚为什么,并且能用其他方式解决这个问题就不用往下看啦。

我的android 安装了 sdk 23的 sourceCode,在android studio 上能直接查看源码 java文件,如果还有人下载sdk比较困难,这里介绍一个不用翻墙的方式下载 最新android studio和sdk的方式,点这里 进入androiddevtools,然后就能在android studio上直接设置断点查看源码啦。

这里先给出结论

先看图
图1


Paste_Image.png

先说ViewGroup层的事件分发源码,去除了很多我认为没有的代码,

@Overridepublic 
boolean dispatchTouchEvent(MotionEvent ev) {   
 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.    
          mFirstTouchTarget = null;  
    }     
   // Check for interception.        final boolean intercepted;     
   if (actionMasked == MotionEvent.ACTION_DOWN      
          || mFirstTouchTarget != null) {   //这里调用调用拦截事件
        intercepted = onInterceptTouchEvent(ev);
     } else {        
    // There are no touch targets and this action is not an initial down    
        // so this view group continues to intercept touches.           
       intercepted = true;     
   }     
   // Check for cancelation.     
   final boolean canceled = resetCancelNextUpFlag(this)           
     || actionMasked == MotionEvent.ACTION_CANCEL;     
   if (!canceled && !intercepted) {  
          if (actionMasked == MotionEvent.ACTION_DOWN       
             || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)            
        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {   
            dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)   //这里主要调用这个方法
       }    
    }        
// 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.        
    }        // Update list of touch targets for pointer up or cancel, if needed. 
   }    
  return handled;
}

首先(ACTION_DOWN,ACTION_MOVE,ACTION_MOVE)这一系列事件可以合并为一个流程,并且会和一个对象进行绑定,这个对象是mFirstTouchTarget,这个很重要,当用户按下时,会生成一个新的点击对象,并对老的对象会接受到cancel事件。

   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.    
          mFirstTouchTarget = null;  
    }   

这里开始调用onInterceptTouchEvent,这里注意如果是按下事件,或者mFirstTouchTarget !=null 就会调用onInterceptTouchEvent,刚刚按下事件已经将mFirstTouchTarget置为了空,所以能进如这个条件下的判断。

聪明的你联想下上下文,是不是大致猜测ACTION_DOWN 以后的事件没有被调用是和这个mFirstTouchTarget对象有关呢。

   if (actionMasked == MotionEvent.ACTION_DOWN      
          || mFirstTouchTarget != null) {   //这里调用调用拦截事件
        intercepted = onInterceptTouchEvent(ev);
     } else {        
    // There are no touch targets and this action is not an initial down    
        // so this view group continues to intercept touches.           
       intercepted = true;     
   }    

这行代码的意思是如果是没有取消事件,没有被拦截,并且为按下事件开始分发事件,如果返回true,就把当前点击的View 赋值给mFirstTouchTarget,并设置标识已经分发过一次。

   if (!canceled && !intercepted) {  
          if (actionMasked == MotionEvent.ACTION_DOWN       
             || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)            
        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {   
            if(dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)  ){
                   alreadyDispatchedToNewTouchTarget = true;
                //遍历childView 并把点击view赋值给mFirstTouchTarget
            }
       }    
    }   
    if (mFirstTouchTarget == null) {      
      // No touch targets so treat this as an ordinary view.    //如果没有响应的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.   
            if(alreadyDispatchedToNewTouchTarget && target == newTouchTarget){
                  handled = true;
            }
    }        // Update list of touch targets for pointer up or cancel, if needed. 

如果mFirstTouchTarget = null.就不会再传递事件,这时可以在 android studio 可以调试下,直接断点放到mFirstTouchTarget == null 里面,看看是不是跑这里来了,虽然我们只是在界面上放了几个view,但是却会顶层嵌套好几层其他view,AppBarLayout等。

看到这里好像还是不大明白,先看下dispatchTransformedTouchEvent这个分发方法到底做了什么

if (child == null) {  
  handled = super.dispatchTouchEvent(event);
} else {  
  handled = child.dispatchTouchEvent(event);
}

我们的child是几个TextView ,继承View,接着看看super.dispatchTouchEvent

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

             if (onTouchEvent(event)) {
                 return true;
             }
         }
         return false;

    }

这里代码只有几句很简单,如果你设置了onTouchListener,就会先调用mOnTouchListener.onTouch,如果onTouch事件返回true的话,事件将不会往下传递,再记忆下前面的只有在ACTION_DOWN的时候才设置mFirstTouchTarget的值,<b>否则事件不会再往下传递否则onTouch事件也只会调用一次ACTION_DOWN</b>,这里是不是可以作为突破口呢?

接着进入view的onTouchEvent

public boolean onTouchEvent(MotionEvent event) {
  if (((viewFlags & CLICKABLE) == CLICKABLE ||(viewFlags & LONG_CLICKABLE) ==LONG_CLICKABLE)(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {   
       //这里直接返回true
   }
  //否则返回false
}

这里也简单,如果View是可点击直接返回true,否则返回false,调用viewGroup的supler(View).dispatchEvent(),

看到这里,你想到几种方案解决上面的问题了呢?

要不现在动手试试?

个人水平有限,如果错误,请指正,大家一起学习进步。

上一篇下一篇

猜你喜欢

热点阅读