事件分发机制-源码分析

307 阅读6分钟
  • 事件是从activity->viewGroup->view->viewGroup->activity分发
    image.png

image

# 在Activity类中
public boolean dispatchTouchEvent(MotionEvent ev) {
      if (ev.getAction() == MotionEvent.ACTION_DOWN) {
          onUserInteraction(); //DOWN,空实现
      }
      //phonewindow如果拦截了返回ture,就结束这次事件
      if (getWindow().superDispatchTouchEvent(ev)) {
          return true;
      }
      //否则调用onTouchEvent
      return onTouchEvent(ev);
  }

#在phoneWindow类中
@Override
  public boolean superDispatchTouchEvent(MotionEvent event) {
       //调用decorView的superDispatchTouchEvent
      return mDecor.superDispatchTouchEvent(event);
  }

#DecorView类中
public boolean superDispatchTouchEvent(MotionEvent event) {
     //调用了父类的dispatchTouchEvent()
     //FrameLayout继承ViewGroup,相当于调用ViewGroup.dispatchTouchEvent()方法
      return super.dispatchTouchEvent(event);
  }

为什么要有事件分发机制? 因为在同一区域,会重叠很多view,比如有几个view和viewGroup,那么这时我们就要事件分发机制确定我们手指点击事件到底作用于谁?

事件分发机制的几个方法 1.dispatchTouchEvent():事件分发 2.onInterceptTouchEvent():事件拦截 3.onTouchEvent():事件处理

##view的事件分发: 我们分别在view中打印onTouchEvent,和在activity中打印onTouchEvent和click事件,看一看优先级。

public class TouchTest extends View {
  public TouchTest(Context context) {
      super(context);
  }

  public TouchTest(Context context, @Nullable AttributeSet attrs) {
      super(context, attrs);
  }

  public TouchTest(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
      super(context, attrs, defStyleAttr);
  }

  @Override
  public boolean onTouchEvent(MotionEvent event) {
      Log.d("View+onTouchEvent=",""+event.getAction());
      return super.onTouchEvent(event);
  }
}

#Activity类
@Override
  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.touch_item);
      TouchTest touchTest= findViewById(R.id.touch_view);
      touchTest.setOnTouchListener(new View.OnTouchListener() {
         @Override
         public boolean onTouch(View v, MotionEvent event) {
             Log.d("Activity+onTouch=",""+event.getAction());
             return false;
         }
     });

      touchTest.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View v) {
              Log.d("Activity+Click=","");
          }
      });
  }

结果:
D/Activity+onTouch=: 0
D/View+onTouchEvent=: 0
D/Activity+onTouch=: 1
D/View+onTouchEvent=: 1
D/Activity+Click=: -----


#把onCreate的onTouchEvent方法改为true,相当把事件消化掉了。
结果:
D/Activity+onTouch=: 0
D/Activity+onTouch=: 1

从上面分析得知,activity的onTouch优于vie的onTouch优于click点击事件。

View事件分发源码分析: 首先,我们触摸点击任何view都会先调用dispatchTouchEvent()方法,先从这里看起:

public boolean dispatchTouchEvent(MotionEvent event) {
      // 省略部分代码 ...

      boolean result = false;

      // 省略部分代码 ...

      if (onFilterTouchEventForSecurity(event)) {
          ///ListenerInfo 存放了所有view的Listener信息:如:onTouchListener、onClickListener
          ListenerInfo li = mListenerInfo;
          if (li != null && li.mOnTouchListener != null
                  && (mViewFlags & ENABLED_MASK) == ENABLED //是可用的
                  && li.mOnTouchListener.onTouch(this, event)) { //这里会先调用OnTouchListener实例的onTouch方法,判断是否为true,所以activity的touch先执行
              result = true;
          }

          if (!result && onTouchEvent(event)) { //view的onTouchEvent事件走不走取决于上面actviity返回touchListener实例onTouch方法返回的事件是否为false。
              result = true;
          }
      }
      
      // 返回 result
      return result;
  }

看看ListenerInfo如何装载所有的Listener事件:

static class ListenerInfo {
      /**
       * Listener used to dispatch focus change events.
       * This field should be made private, so it is hidden from the SDK.
       * {@hide}
       */
      protected OnFocusChangeListener mOnFocusChangeListener;

      /**
       * Listeners for layout change events.
       */
      private ArrayList<OnLayoutChangeListener> mOnLayoutChangeListeners;

      protected OnScrollChangeListener mOnScrollChangeListener;

      /**
       * Listeners for attach events.
       */
      private CopyOnWriteArrayList<OnAttachStateChangeListener> mOnAttachStateChangeListeners;

      /**
       * Listener used to dispatch click events.
       * This field should be made private, so it is hidden from the SDK.
       * {@hide}
       */
      public OnClickListener mOnClickListener;

      /**
       * Listener used to dispatch long click events.
       * This field should be made private, so it is hidden from the SDK.
       * {@hide}
       */
      protected OnLongClickListener mOnLongClickListener;

      /**
       * Listener used to dispatch context click events. This field should be made private, so it
       * is hidden from the SDK.
       * {@hide}
       */
      protected OnContextClickListener mOnContextClickListener;

      /**
       * Listener used to build the context menu.
       * This field should be made private, so it is hidden from the SDK.
       * {@hide}
       */
      protected OnCreateContextMenuListener mOnCreateContextMenuListener;

      private OnKeyListener mOnKeyListener;

      private OnTouchListener mOnTouchListener;

      private OnHoverListener mOnHoverListener;

      private OnGenericMotionListener mOnGenericMotionListener;

      private OnDragListener mOnDragListener;

      private OnSystemUiVisibilityChangeListener mOnSystemUiVisibilityChangeListener;

      OnApplyWindowInsetsListener mOnApplyWindowInsetsListener;
  }

可以看到这个ListenerInfo类中有各个监听接口的变量,我们看看li.mOnTouchListener这接口在哪里赋值。

#View
public void setOnTouchListener(OnTouchListener l) {
     //赋值接口实例
      getListenerInfo().mOnTouchListener = l;
  }
 //返回ListenerInfo实例
  ListenerInfo getListenerInfo() {
      if (mListenerInfo != null) {
          return mListenerInfo;
      }
      mListenerInfo = new ListenerInfo();
      return mListenerInfo;
  }

在看看view的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) {
                    //调用这个方法
                      performClick();
                  }
                  mIgnoreNextUpEvent = false;
                  break;
                  // 省略部分代码
          }

          return true;
      }

      return false;
  }

  public boolean performClick() {
      final boolean result;
      final ListenerInfo li = mListenerInfo;
      if (li != null && li.mOnClickListener != null) {
          playSoundEffect(SoundEffectConstants.CLICK);
          //执行ClickListener的onClick()方法
          li.mOnClickListener.onClick(this);
          result = true;
      } else {
          result = false;
      }

      sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
      return result;
  }

//赋值也是在View.set中完成
public void setOnClickListener(@Nullable OnClickListener l) {
      if (!isClickable()) {
          setClickable(true);
      }
      getListenerInfo().mOnClickListener = l;
  }

  ListenerInfo getListenerInfo() {
      if (mListenerInfo != null) {
          return mListenerInfo;
      }
      mListenerInfo = new ListenerInfo();
      return mListenerInfo;
  }

文字总结 view的事件分发是,每次先调用View的dispatchTouchEvent()方法,这个方法会先调用touchListener接口实例的onTouch()方法来查看是否调用onTouchEvent()方法。在onTouchEvent中在判断到事件类型为离开up时,会调用performClick()方法,其里面会调用clickListener接口实例的onClick()方法。

##ViewGroup的事件分发 我们分别打印ViewGroup的dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent。和子view的dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent

public class TouchViewGroup extends LinearLayout {
  ...
  @Override
  public boolean dispatchTouchEvent(MotionEvent ev) {
      Log.d("TAG","ViewGroup-dispatchTouchEvent->"+ev.getAction() );
      return super.dispatchTouchEvent(ev);
  }

  @Override
  public boolean onInterceptTouchEvent(MotionEvent ev) {
      Log.d("TAG","ViewGroup-onInterceptTouchEvent->"+ev.getAction() );
      return super.onInterceptTouchEvent(ev);
  }

  @Override
  public boolean onTouchEvent(MotionEvent event) {
      Log.d("TAG","ViewGroup-onTouchEvent->"+event.getAction() );
      return super.onTouchEvent(event);
  }
}

public class TouchTest extends View {
  ...
  @Override
  public boolean dispatchTouchEvent(MotionEvent event) {
      Log.d("TAG","View+dispatchTouchEvent->"+event.getAction());
      super.dispatchTouchEvent(event);
      return true;
  }

  @Override
  public boolean onTouchEvent(MotionEvent event) {
      Log.d("TAG","View+onTouchEvent->"+event.getAction());
      return super.onTouchEvent(event);
  }
}

@Override
  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.touch_item);
      TouchTest touchTest= findViewById(R.id.touch_view);

      touchTest.setOnTouchListener(new View.OnTouchListener() {
         @Override
         public boolean onTouch(View v, MotionEvent event) {
             Log.d("TAg","View-OnTouch->"+event.getAction());
             return false;
         }
     });

      touchTest.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View v) {
              Log.d("TAG","View-OnClick");
          }
      });
  }

打印结果:
1.当子view消费了事件
ViewGroup-dispatchTouchEvent->
ViewGroup-onInterceptTouchEvent->
View+dispatchTouchEvent->View-OnTouch->0View+onTouchEvent->
ViewGroup-dispatchTouchEvent->
ViewGroup-onInterceptTouchEvent->
View+dispatchTouchEvent->View-OnTouch->View+onTouchEvent->
View-OnClick

2.当子view没有消费事件
ViewGroup.dispatchTouchEvent -> 
ViewGroup.onInterceptTouchEvent -> 
View.dispatchTouchEvent -> View.onTouch -> View onTouchEvent -> 
ViewGroup.onTouchEvent

3. 当view的onTouchEvent() 方法里面返回true ,相当于消费了事件
第一次DOWN: ViewGroup.dispatchTouchEvent -> 
ViewGroup.onInterceptTouchEvent -> 
View.dispatchTouchEvent -> View.onTouch -> View.onTouchEvent
第二次MOVE: ViewGroup.dispatchTouchEvent -> 
ViewGroup.onInterceptTouchEvent -> 
View.dispatchTouchEvent -> View.onTouch -> View.onTouchEvent
第三次UP: ViewGroup.dispatchTouchEvent ->
ViewGroup.onInterceptTouchEvent ->
View.onTouch -> View.onTouchEvent 

4.当ViewGroup 的 onInterceptTouchEvent() 方法里面返回 true 
ViewGroup.dispatchTouchEvent -> 
ViewGroup.onInterceptTouchEvent ->
ViewGroup.onTouchEvent

ViewGroup的源码分析 事件首先进入ViewGroup的dispatchTouchEvent()方法:

public boolean dispatchTouchEvent(MotionEvent ev) {

if (actionMasked == MotionEvent.ACTION_DOWN) {
             // 清除TouchTargets 
             //mFirstTouchTarget = null
              cancelAndClearTouchTargets(ev);
              resetTouchState();
          }

1. //ViewGroup是否拦截事件
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
      || mFirstTouchTarget != null) {
  if (!disallowIntercept) {
     //DOWN事件正常情况下会调用,默认情况下返回false
     //但是onInterceptTouchEvent返回true,intercepted就等于ture,就不会调用子view的distachtTouchEvent, mFirstTouchTarget 就等于空
      intercepted = onInterceptTouchEvent(ev); 
      ev.setAction(action); // restore action in case it was changed
  } else {
      intercepted = false;
  }
} else {
   intercepted = true;
}

2.//如果viewGroup不拦截,主要是intercepted为falseif (!canceled && !intercepted) {
for(遍历child){
//调用子view的dispatchTouchEvent方法
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
     // 如果子 View 返回true 就会进来 主要给 mFirstTouchTarget = target; 赋值
      newTouchTarget = addTouchTarget(child, idBitsToAssign);
      break;
}


3.//所有子view都没有拦截或者onInterceptTouchEvent返回true拦截了。
if (mFirstTouchTarget == null) {
  // child传为null,即调用ViewGroup.onTouchEvent()
  handled = dispatchTransformedTouchEvent(ev, canceled, null,
          TouchTarget.ALL_POINTER_IDS);
} else {
      ...
  }
//最终返回handled让上一个调用
return handled;

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
          View child, int desiredPointerIdBits) {
      final boolean handled;
      final int oldAction = event.getAction();
      if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
          event.setAction(MotionEvent.ACTION_CANCEL);
         //这里的child是传进来的第三个参数
          if (child == null) {
              handled = super.dispatchTouchEvent(event);//用父类调用子类的onTouchEvent,即ViewGroup的onTouchEvent()。
          } else {
               //调用子view的dispatchTouchEvent
              handled = child.dispatchTouchEvent(event);
          }
          event.setAction(oldAction);
          return handled;
      }

private void clearTouchTargets() {
      TouchTarget target = mFirstTouchTarget;
      if (target != null) {
          do {
              TouchTarget next = target.next;
              target.recycle();
              target = next;
          } while (target != null);
          mFirstTouchTarget = null;
      }
  }

文字总结: viewGroup先调用dispatchTouchEvent()方法,此方法在事件DOWN的时候,会把标记mFirstTouchTarget置空Null,然后在事件DOWN或者标记TouchTarget != null时调用拦截事件 onInterceptTouchEvent方法,并把结果返回给一个布尔变量,后面根据这个变量是否为false,遍历所有子view调用view.dispatchTouchEvent方法,并给TouchTarget赋值,在根据TouchTarget是否为null,调用ViewGroup的onTouchEvent。

ViewGroup的dispatchTouchEvent返回的结果最终返回到phoneWindow的superdispatchTouchEvent方法中,并返回给了activity,如果viewGroup有拦截actvity则什么事也不做,否则调用actvity的onTouchEvent()方法。

其中activity和view没有拦截事件,因为activity拦截了事件,那么这个事件就不能向下传递。view则没有必要拦截事件,因为下面已经没有view要响应事件。

如果说子 View 没有一个地方返回 true ,只会进来一次只会响应 DOWN 事件,代表不需要消费该事件,如果你想响应 MOVE,UP 必须找个地方ture

对于ViewGroup来讲,如果你想拦截子 View 的 Touch 事件,可以覆写 onInterceptTouchEvent 返回 true 即可 , 如果说 onInterceptTouchEvent 返回的是 true 会执行该 ViewGroup 的 onTouchEvent 方法 , 如果子 View 没有消费 touch 事件也会调用该 ViewGroup 的 onTouchEvent 方法