Android基础_事件分发源码走读

一、带着问题出发

 我们手触摸点击屏幕的时候,触摸、点击事件是如何分发的呢?

 布局中的控件是如何获取到按键事件的呢?

 布局中有多个控件,如何只让指定的控件接收到相关的事件呢?

二、说在前面

下面来大体说下事件分发涉及到的几个类和相关方法:

Android的事件分发顺序是:Activity ----> ViewGroup ----> View

涉及到的几个重要方法:

  dispatchTouchEvent();

  onInterceptTouchEvent();

  onTouchEvent();

dispatchTouchEvent负责事件的分发,onInterceptTouchEvent()是ViewGroup中判断是否进行事件拦截的方法,onTouchEvent() 是dispatchTouchEvent() 中执行的方法。

下面就分别来看下事件在Activity、ViewGroup、View 的分发机制。

三、Activity 的事件分发机制

Activity.java

public boolean dispatchTouchEvent(MotionEvent ev) {

   if (ev.getAction() == MotionEvent.ACTION_DOWN) {

            onUserInteraction();

   }

   // >>> 分析1

   if (getWindow().superDispatchTouchEvent(ev)) {

        return true;

  }

   return onTouchEvent(ev);

}

PhoneWindow.java

@Override

    public boolean superDispatchKeyEvent(KeyEvent event) {

        return mDecor.superDispatchKeyEvent(event);

   }

private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {

 @Override

 public boolean dispatchTouchEvent(MotionEvent ev) {

      final Callback cb = getCallback();

      return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev)

                    : super.dispatchTouchEvent(ev);

        }

}

public class FrameLayout extends ViewGroup {

  ...

}

分析1:getWindow()是获取当前的window,调用到的是PhoneWindow,PhoneWindow 的superDispatchKeyEvent()中调用了DecorView 的superDispatchKeyEvent();而DecorView中调用的是super.dispatchTouchEvent(ev);

DecorView继承的是FrameLayout,FrameLayout又是继承的ViewGroup,所以最终会调用到ViewGroup的dispatchTouchEvent(ev);

四、ViewGroup的事件分发机制

ViewGroup.java

public boolean dispatchTouchEvent(MotionEvent ev) {

   //>>> 分析2

   intercepted = onInterceptTouchEvent(ev);

   ...

   if(dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {

          ...         

        }

  ...

}

public boolean onInterceptTouchEvent(MotionEvent ev) {

   return false;

}

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,

            View child, int desiredPointerIdBits) {

      if (child == null) {

                handled = super.dispatchTouchEvent(event);

      } else {

                handled = child.dispatchTouchEvent(event);

      }

}

分析2:这里会有intercept,是否进行事件拦截的判断,该值是根据onInterceptTouchEvent()的返回值来判断,默认是返回的fale。

如果需要对事件进行拦截,可以重写ViewGroup的onInterceptTouchEvent()方法,直接return true,onInterceptTouchEvent()方法只有ViewGroup有,Activity和View都没有。

intercept 值为true,则进行事件拦截,执行的是super.dispatchTouchEvent(event),也就是View的dispatchTouchEvent(ev)方法,这个下面会分析.

intercept 值为false,则会遍历ViewGroup的子view,如果是处于子View的触摸区域,则会调用子view的dispatchTouchEvent();

五、View的事件分发机制

View.java

public boolean dispatchTouchEvent(MotionEvent event) {

      // >>>分析3

     if (li != null && li.mOnTouchListener != null

                    && (mViewFlags & ENABLED_MASK) == ENABLED

                    && li.mOnTouchListener.onTouch(this, event)) {

                result = true;

      }

      // >>>分析4

      if (!result && onTouchEvent(event)) {

                result = true;

      }

}

public void setOnTouchListener(OnTouchListener l) {

        getListenerInfo().mOnTouchListener = l;

}

 public boolean onTouchEvent(MotionEvent event) {

        ...

        if (mTouchDelegate != null) {

            if (mTouchDelegate.onTouchEvent(event)) {

                return true;

            }

        }

        if (((viewFlags & CLICKABLE) == CLICKABLE ||

                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {

            switch (event.getAction()) {

                case MotionEvent.ACTION_UP:

                  ......

                 if (mPerformClick == null) {

                      mPerformClick = new PerformClick();

                 }

     if (!post(mPerformClick)) {

                       performClick();

                 }

                    break;

                case MotionEvent.ACTION_DOWN:

                   ...

                    break;

                case MotionEvent.ACTION_CANCEL:

                   ...

                    break;

                case MotionEvent.ACTION_MOVE:

                    ...

                    break;

            }

            return true;

        }

        return false;

}

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;

        }

         return result;

    }

分析3:如果view设置了OnTouchListener监听,这会会执行OnTouchListener的监听,如果onTouchLister return true,则表示事件被消费掉了,会直接return true,事件分发结束;可以重写setOnTouchListener()方法.

分析4:OnTouchListener中返回false,则会往下执行onTouchEvent(ev)方法,onTouchEvent(ev)中会执行performClick()方法,这时如果view设置了onClick监听,会执行onClick方法。

这里也可以看到,onTouchListener的执行会先于onClickListener.

六、最后总结

通过上面简单的代码跟读,我们知道点击Activity时,点击事件最先传递到Activity.java dispatchTouchEvent()中进行分发,
dispatchTouchEvent()中会调用到ViewGroup 中的dispatchTouchEvent()方法,
ViewGroup 中有个重要的onInterceptTouchEvent()方法来判断是否进行事件拦截,该方法默认return false,如果我们希望进行事件拦截,
可以重写该方法,return true,则事件会被消费掉,
不会再分发给子view;onInterceptTouchEvent() return false的时候,
会继续往下走,往下会遍历子view,如果当前点击的区域是相应子view的区域,
则会调用到子view的dispatchTouchEvent()方法,view 的dispatchTouchEvent()中会
先优先判断是否设置了onTouchListener监听,onTouchListener return false,后面才会执行view 的onClick监听。

=======================================================================

*本人从事Android Camera相关开发已有5年,
*目前在深圳上班,
*小伙伴记得点我头像关注,也可以关注我的微信公众号【小驰笔记】,希望和更多的小伙伴一起交流 ~

---- 2020.01.06 深圳

posted @ 2021-01-13 17:36  小驰行动派  阅读(106)  评论(0编辑  收藏  举报