Android_activity事件分发流程分析

今天来看一下Activity的事件分发过程:

 事件分发

  1. 用户通过屏幕与手机交互的时候,每一次点击,长按,移动等都是一个事件;

  2. 事件分发机制:某一个事件从屏幕传递各个View,由View来使用这个事件(消费事件)或者忽略这个事件(不消费事件),这整个过程的控制。

事件的分发的对象是谁

  •   系统将事件封装成一个MotionEvent对象,事件分发的过程就是MotionEvent分发的过程。

事件的类型

  1. 按下(ACTION_DOWN)
  2. 移动(ACTION_MOVE)
  3. 抬起(ACTION_UP)
  4. 取消(ACTION_CANCEL)

事件序列

  •      从手指按下屏幕开始,到手指离开屏幕所产生的一系列事件

传递层级

 Activity不是真正控制视图,真正控制视图的是window,

当我们的手指去触摸Activity的时候,事件会经过这小样的一个传递过程:

事件最初会在Activity之上,然后通过Window传递到decorView上,这里的DecorView就是Window所持有的一个DecorView,而DecorView又是继承ViewGroup,事件在这个ViewGroup中进行分发,然后才会分发放到我们创建的子的viewGroup和view上面。事件就是按照上面描述的层级关系传递的。

  1. Activity->Window->DecorView->ViewGroup->View

在这样的层级中,主要涉及的对象有三个【Activity,viewgroup,view】

我们开始进入源码:

首先从Activity中的dispatchTouchEvent()方法出发:

/**
 * Called to process touch screen events.  You can override this to
 * intercept all touch screen events before they are dispatched to the
 * window.  Be sure to call this implementation for touch screen events
 * that should be handled normally.
 *
 * @param ev The touch screen event.
 *
 * @return boolean Return true if this event was consumed.
 */
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}

在Activity中的dispatchTouchEvent()中首先判断事件类型是不是ACTION_DOWN,如果是那就开始调用onUserInteraction方法;

public void onUserInteraction() {}

在键盘事件,触摸事件,轨迹球事件等被分发到Activity都会调用这个方法,默认是空实现,如果你的Activity需要和设备交互的话,可以来实现这个方法。

    然后通过getWindow()获取到Activity持有的一个Window对象:

public Window getWindow() {
    return mWindow;
}

这就可以直接使用Window中的API来处理事件了,接着就是就调Window的superDispatchTouchEvent()方法,如果上述方法都返回的是false,说明事件没人处理,事件就会重新回到Activity中,调用其onTouchEvent()方法进行处理.

注意:Window是个抽象类,虽然在Window中声明了大量的方法,但是都没有实现,

Window是顶级窗口外观和行为策略抽象基类。此类的实例应用作添加到窗口管理器的顶级视图。它提供标准的ui策略,如背景、标题区域、默认密钥处理等。此抽象类的唯一现有实现是android.view.phonewindow,需要窗口时应该实例化它。

在Activity的内部API中有个attach()方法:

final void attach(Context context, ... Window window, ActivityConfigCallback activityConfigCallback) {
	...
	mWindow = new PhoneWindow(this, window, activityConfigCallback);
	...
}

所以,在attach()方法中,将PhoneWindow实例化,并将对象赋值给了mWindow。在getWind()方法中返回的就是一PhoneWindow对象,事件调用的就是PhoneWindow中的superDispatchTouchEvent方法。

那我们到PhoneWindow中看看superDispatchTouchEvent()方法做了些什么:

@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}

这里是调用了DecorView的superDispatchTouchEvent()方法。mDecor是DecorView的对象,就是说Activity持有Window的对象,Window又持有DecorView的对象.

我们进入DecorView中看看superDispatchTouchEvent()做了什么:

public boolean superDispatchTouchEvent(MotionEvent event) {
    return super.dispatchTouchEvent(event);
}

DecorView是调用了super.dispatchTouchEvent(event);它也是向上传递的,我们就看看DecorView的继承情况:DecorView继承与FrameLayout,但是FrameLayout中并没有dispatchTouchEvent,不过FrameLayout又继承与ViewGroup。是的,最后是调用了ViewGroup中的dispatchToucheEvent();

现在,事件分发就走到了ViewGroup中。

ViewGroup 的dispatchTouchEvent()是事件分发的主要的大逻辑,虽然代码量大,但是源码中有大量的注释,帮助我们理解其中的代码逻辑。Google真良心。但是我们分析可以看出它主要做的是哪些事:

  1. 去判断是否需要 拦截事件
  2. 在当前ViewGroup中找到用户真正点击的View
  3. 分发事件到View上。
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    ...
    boolean handled = false;
    // 验证事件是否符合安全策略,
    if (onFilterTouchEventForSecurity(ev)) {
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;
        ...
        // 检查是否需要拦截.
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action); // restore action in case it was changed
            } else {
                intercepted = false;
            }
        } else {
            //没有触碰目标,此操作不是初始向下,因此此视图组继续拦截触碰。
            intercepted = true;
        }
        ...
        for (int i = childrenCount - 1; i >= 0; i--) {
            // 尝试找到用户真正点击的view(先获取view的索引,然后获取真正的view)
            final int childIndex = getAndVerifyPreorderedIndex(
                            childrenCount, i, customOrder);
            final View child = getAndVerifyPreorderedView(
                            preorderedList, children, childIndex);
        ...
        // 将事件转换为特定子view的坐标空间,事件就进入下一层是分发
        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
            ...
            //将指定子项的触控目标添加到列表的开头,完成对mFirstTouchTarget的赋值并终止对子元素的遍历
            newTouchTarget = addTouchTarget(child, idBitsToAssign);
            alreadyDispatchedToNewTouchTarget = true;
            break;
        }


dispatchTouchEvent主要做的事

在dispatchTouchEvent()方法开始,就需要使用onFilterTouchEventForSecurity方法对事件进行安全策略的检查:

/**
 * Filter the touch event to apply security policies.
 *
 * @param event The motion event to be filtered.
 * @return True if the event should be dispatched, false if the event should be dropped.
 *
 * @see #getFilterTouchesWhenObscured
 */
public boolean onFilterTouchEventForSecurity(MotionEvent event) {
    //noinspection RedundantIfStatement
    if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
            && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
        // Window is obscured, drop this touch.
        return false;
    }
    return true;
}

判断的规则是什么呢?首先判断mViewFlags 是不是设置为了FILTER_TOUCHES_WHEN_OBSCURED(当视图窗口被遮盖时,该视图需要过滤触摸事件); 然后事件的标志是不是触摸事件确实被遮挡,上面两个条件都满足,说明窗口被遮盖,触摸事件丢弃,否则返回true,进行正常的事件分发。

然后检查事件是否需要拦截,我们可以看到只有一下两种情况才会对事件进行拦截:

一种是actionMasked == MotionEvent.ACTION_DOWN:说明事件是down事件

第二种是 mFirstTouchTarget != null,这个mFirstTouchTarget是TouchTarget链表中的第一个touch target;mFirstTouchTarget不为空说明有ViewGroup的子View或子ViewGroup中,有可以接受触摸事件的。那么,就将触摸事件分发给这些可以接受触摸事件的子View或子ViewGroup。

此时onInterceptTouchEvent方法就会被调用,

public boolean onInterceptTouchEvent(MotionEvent ev) {
    if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
            && ev.getAction() == MotionEvent.ACTION_DOWN
            && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
            && isOnScrollbarThumb(ev.getX(), ev.getY())) {
        return true;
    }
    return false;
}

通过上面的拦截判断条件看,需要是鼠标事件,并且是按下事件,;还要是点击鼠标左键;还要在滚动条上.我们的手机很少情况连接鼠标;所以绝大多数情况下这里都会是返回的false.即不会拦截事件,事件还是需要进行分发的.

所以接下来就要寻找子view.

 for (int i = childrenCount - 1; i >= 0; i--) {
      // 尝试找到用户真正点击的view(先获取view的索引,然后获取真正的view
      final int childIndex = getAndVerifyPreorderedIndex(
             childrenCount, i, customOrder);
      final View child = getAndVerifyPreorderedView(
             preorderedList, children, childIndex);
      ...
}

通过遍历子view的列表以此获取子view,先通过getAndVerifyPreorderedIndex()方法来获取子view的ID,让后将子view的ID传入getAndVerifyPreorderedView()方法来获取子view.

在获取得到子view之后,通过dispatchTransformedTouchEvent()方法,将事件传递到子view当中,实际上dispatchTransformedTouchEvent()是调用子view的dispatchTouchEvent().

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
    ...
    if (child == null) {
         handled = super.dispatchTouchEvent(event);
    } else {
         handled = child.dispatchTouchEvent(event);
    }
...
}

在dispatchTransformedTouchEvent中有这么一段代码,就是说如果当前的child 为空就会调用父元素的dispatchTouchEvent()方法,当前事件如果下面处理不了,就会往上传.

如果子view的dispatchTouchEvent()方法返回true.就是说事件进入了子view.那么此时viewGroup中就会对调用addTouchTarget()方法对mFirstTouchEvent进行赋值,并结束当前的子view遍历,到此事件就进入了子view中.

view中的dispatchTouchEvent

事件进入子view中,首先也是进过子view的dispaTouchEvent()方法,,下面我们近看看View中是怎么处理事件的.


public boolean dispatchTouchEvent(MotionEvent event) {
    // If the event should be handled by accessibility focus first.
   ...

    if (onFilterTouchEventForSecurity(event)) {
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }
        //noinspection SimplifiableIfStatement
        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中,也会对事件进行安全策略的检查,在符合安全策略后,

如果当前事件是鼠标拖动滚动条的事件就返回true;表示消费了这条事件,

然后系统进行了事件监听的操作,如果li != null && li.mOnTouchListener != null,说明当前的view注册了事件监听,并且当前的view的标志为enable状态,而且调用事件监听中的onTouch()方法返回了true.那么当前view就会返回true,就是消费了事件;

如果事件监听中的onTouch()方法反悔了false,就会调用view的onTouchEvent()方法,如果onTouchView()方法中返回了true,就是说onTouch()方法没有处理才会在onTouchEvent()中进行处理.

到此,我们将事件分发的流程大概梳理完了.

发布了41 篇原创文章 · 获赞 35 · 访问量 4317

猜你喜欢

转载自blog.csdn.net/weixin_38140931/article/details/102696717