一篇文章搞定《Android事件分发》
什么是事件分发
事件分发是将屏幕触控信息分发给控件树的一个套机制。 当我们触摸屏幕时,会产生一系列的MotionEvent事件对象,经过控件树的管理者ViewRootImpl,调用view的dispatchPointerEvnet方法进行分发。
 深入学习事件分发机制,是为了解决在Android开发中遇到的滑动冲突问题做准备。事件分发机制描述了用户的手势一系列事件是如何被Android系统传递并消费的。
MotionEvent事件
在MotionEvent.java中我们可以看到这些事件。(有很多事件这里只列举重要常用的几个)
| 事件 | 作用 | 
|---|---|
| ACTION_DOWN | 第一个手指初次接触到屏幕时触发 | 
| ACTION_MOVE | 手指 在屏幕上滑动 时触发,会多次触发 | 
| ACTION_UP | 最后一个手指离开屏幕时触发 | 
| ACTION_CANCEL | 事件被取消或被覆盖(事件被上层拦截了,由父View发送,不是用户自己触发的),也就是非人为触发 | 
| ACTION_POINTER_DOWN | 有非主要的手指按下(即按下之前已经有手指在屏幕上) | 
| ACTION_POINTER_UP | 有非主要的手指抬起(即抬起之后仍然有手指在屏幕上) | 
事件如何从屏幕到APP
这部分虽然暂时不必纠结,但是还是要知道整个流程。暂时重点放在页面的事件开发。后面如果想要深入的时候可以深入的进行研究。
InputManagerService
首先Android系统启动后在SystemServer进程会启动一系列系统服务,如AMS,WMS等
 其中还有一个就是我们管理事件输入的InputManagerService(IMS)。
 InputManagerService:这个服务就是用来负责与硬件通信,接受屏幕输入事件。他会读取我们系统收到的硬件点InputDispatcher线程,然后进行统一的事件分发调度。
WindowManagerService
Android中view的绘制和事件分发,都是以view树为单位。每一棵view树,都为一个window。
 每一棵view树都有一个根,叫做ViewRootImpl ,他负责管理这整一棵view树的绘制、事件分发等。
 1、所以我们最终肯定要把事件通知给该View树的ViewRootImpl。
 2、而管理Window与之通信的就是我们的WindowManagerService(WMS)
 3、每个viewRootImpl在wms中都有一个windowState对应,wms可以通过windowState找到对应的viewRootImpl进行管理。
 4、WMS是通过windowState进行Binder通信提供相关需要Window信息,并由IMS发送给APP相关事件
 5、InputEventReceiver它负责接收输入事件,并通过Handler发送给ViewRootImpl类处理
Window
1、window机制就是为了管理屏幕上的view的显示以及触摸事件的传递问题。
 2、那什么是window,在Android的window机制中,每个view树都可以看成一个window。
 3、什么是view树?例如你在布局中给Activity设置了一个布局xml,那么最顶层的布局如LinearLayout就是view树的根,他包含的所有view就都是该view树的节点,所以这个view树就对应一个window。
每一个view树对应一个window,view树是window的存在形式,window是view树的载体,我们平时看到的应用界面、dialog、popupWindow以及上面描述的悬浮窗,都是window的表现形式。
 举几个具体的例子:
- 我们在添加dialog的时候,需要给他设置view,那么这个view他是不属于antivity的布局内的,是通过WindowManager添加到屏幕上的,不属于activity的view树内,所以这个dialog是一个独立的view树,所以他是一个window。
 - popupWindow他也对应一个window,因为它也是通过windowManager添加上去的,不属于Activity的view树。
 - 当我们使用使用windowManager在屏幕上添加的任何view都不属于Activity的布局view树,即使是只添加一个button。
了解window机制的一个重要原因是:事件分发并不是由Activity驱动的,而是由系统服务驱动viewRootImpl来进行分发 ,甚至可以说,在框架层角度,和Activity没有任何关系。这将有助于我们对事件分发的本质理解。 
小结
1、在我们手指触摸屏幕时,会产生一个触摸点信息,包括位置、压力等信息。这个触摸信息由屏幕这个硬件产生,被系统底层驱动获取,交给Android的输入系统服务:InputManagerService,也就是IMS。
 2、输入系统会调用窗口管理器(WMS)的 API 来确定触摸事件应该被分发给哪个Window和对应的View。也就是说WMS提供了View信息。
 3、IMS拿到WMS提供的信息,发送给对应View的ViewRootImpl。这里是InputChannel在帮忙建立SocketPair进行双向通信,有兴趣的可以查一下InputChannel相关内容,这里就不做讲解了。
 
事件如何从APP到达对应页面
第一步:分类
那么事件现在到了InputEventReceiver通知到了ViewRootImpl 看看他具体做了什么
 下面是几个重要的方法:
//ViewRootImpl.java ::WindowInputEventReceiver
//1、接收到事件
final class WindowInputEventReceiver extends InputEventReceiver {
    public void onInputEvent(InputEvent event) {
        enqueueInputEvent(event, this, 0, true);
    }
}
//ViewRootImpl.java
//2、简单处理掉用doProcessInputEvents进行分类
void enqueueInputEvent(InputEvent event,
                       InputEventReceiver receiver, int flags, boolean processImmediately) {
    adjustInputEventForCompatibility(event);
    .....
    .....
    .....
    if (processImmediately) {
        doProcessInputEvents();
    } else {
        scheduleProcessInputEvents();
    }
}
//3、维护了输入事件队列
void doProcessInputEvents() {
   .....
    while (mPendingInputEventHead != null) {
        QueuedInputEvent q = mPendingInputEventHead;
        mPendingInputEventHead = q.mNext;
        deliverInputEvent(q);
    }
    ......
}
//调用InputStage责任链处理分类
private void deliverInputEvent(QueuedInputEvent q) {
    InputStage stage;
    ....
    //stage赋值操作
    ....
    if (stage != null) {
        stage.deliver(q);
    } else {
        //事件分发完成后会调用finishInputEvent,告知SystemServer进程的InputDispatcher线程,
        //最终将该事件移除,完成此次事件的分发消费。
        finishInputEvent(q);
    }
}
abstract class InputStage {
    .....
    public final void deliver(QueuedInputEvent q) {
        if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {
            forward(q);
        } else if (shouldDropInputEvent(q)) {
            finish(q, false);
        } else {
            traceEvent(q, Trace.TRACE_TAG_VIEW);
            final int result;
            try {
                result = onProcess(q);
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }
            apply(q, result);
        }
    }
}
 
可以看到这里ViewRootImpl对时间进行了进一步的分类比如视图输入事件,输入法事件,导航面板事件等等。那么InputStage责任链具体在哪里生成的呢,具体有哪几类?
 在setView方法中我们可以得到答案
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
       ...
        mSyntheticInputStage = new SyntheticInputStage();
        InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
        InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
                "aq:native-post-ime:" + counterSuffix);
        InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
        InputStage imeStage = new ImeInputStage(earlyPostImeStage,
                "aq:ime:" + counterSuffix);
        InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
        InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
                "aq:native-pre-ime:" + counterSuffix);
     ....
    }
}
 
可以看到在setView方法中,就把这条输入事件处理的责任链拼接完成了。
| 责任链成员 | 作用 | 
|---|---|
| SyntheticInputStage | 处理导航面板、操作杆等事件 | 
| ViewPostImeInputStage | 视图输入处理阶段,比如按键、手指触摸等运动事件,我们常用view事件分发就发生在这个阶段 | 
| NativePostImeInputStage | 本地方法处理阶段,主要构建了可延迟的队列 | 
| EarlyPostImeInputStage | 输入法早期处理阶段 | 
| ImeInputStage | 输入法事件处理阶段,处理输入法字符 | 
| ViewPreImeInputStage | 视图预处理输入法事件阶段 | 
| NativePreImeInputStage | 本地方法预处理输入法事件阶段 | 
所以第一步是将我们的事件通过InputStage来进行分类和分发。我们的View触摸事件就发生在ViewPostImeInputStage阶段。这样我们就将事件分类到ViewPostImeInputStage了哦,兄弟们。
第二步:送去Activity
我们的View层级如下。Activity -> PhoneWindow -> DecorView
 平时我们在调用setContentView绘制布局也是绘制在DecorView的ContentView中。
 
 那我们就要看看事件是怎么传递到页面事件的开始Activty的。
 首先我们在上面说到了,所有的事件都在ViewPostImeInputStage责任链中处理的
 那先看看ViewPostImeInputStage都有什么啊,宝贝们。
//ViewRootImpl.java ::ViewPostImeInputStage 
final class ViewPostImeInputStage extends InputStage {
    .....
    .....
    private int processPointerEvent(QueuedInputEvent q) {
        final MotionEvent event = (MotionEvent)q.mEvent;
        boolean handled = mView.dispatchPointerEvent(event)
        return handled ? FINISH_HANDLED : FORWARD;
    }
    //View.java
    public final boolean dispatchPointerEvent(MotionEvent event) {
        if (event.isTouchEvent()) {
            return dispatchTouchEvent(event);
        } else {
            return dispatchGenericMotionEvent(event);
        }
    }
 
可以看到最后还是走到了mView.dispatchPointerEvent(event)
 而ViewRootImpl中的mView就是DecorView
 现在事件已经传递到了DecorView,也就是我们界面的根布局
//DecorView.java
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    //Callback是我们的Activity和Dialog
    final Window.Callback cb = mWindow.getCallback();
    return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
            ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}
 
上面的cb是我们创建布局时创建的Activity
 之后我们可以在Activity中可以找到我们的dispatchTouchEvent。这个大家就熟悉了吧。
 就是DecorView这个大叛徒通过getCallback将时间传递给了Activity,让Activity作为页面事件的开端。
后续的传递
我们看一下Activity中dispatchTouchEvent和后续的传递
//Activity.java
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}
//PhoneWindow.java
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}
//DecorView.java
public boolean superDispatchTouchEvent(MotionEvent event) {
    return super.dispatchTouchEvent(event);
}
 
我们发现最后其实又传递回了DecorView
 整个流程为ViewRootImpl ->DecorView -> Activity -> PhoneWindow -> DecorView
问题:那么问题来了为什么ViewRootImpl不直接先从Activity开始处理事件呢?
 答案:ViewRootImpl并不知道有Activity这种东西存在!它只是持有了DecorView。所以,不能直接把触摸事件送到Activity.dispatchTouchEvent()
问题:那么当没有cb怎么办,也就是没有Activity。我们看到else调用了super.dispatchTouchEvent(ev);
 答案:所以如果顶层的viewGroup不是DecorView,那么对调用对应view的dispatchTouchEvent方法进行分发。例如,顶层的view是一个Button,那么会直接调用Button的 dispatchTouchEvent 方法;如果顶层viewGroup子类没有重写 dispatchTouchEvent 方法,那么会直接调用ViewGroup默认的 dispatchTouchEvent 方法。
问题:为什么Activity不直接调用DecorView
 答案:因为Activity没有维护DecorView,其中DecorView被PhoneWindow维护着。
小结:
1、首先事件被InputEventReceiver接收到给ViewRootImpl处理,先进行分类
 2、ViewRootImpl作为View的处理类,负责View的事件处理和管理
 3、事件被分类到ViewPostImeInputStage中,传递到mView的dispatchTouchEvent中
 4、mView是布局的DecorView根布局
 5、通过ViewRootImpl ->DecorView -> Activity -> PhoneWindow -> DecorView 最后到页面ViewGroup的事件分发
 6、PhoneWindow在最后会有简单个讲解
页面的事件分发
DecorView继承自FrameLayout,但是FrameLayout并没有重写 dispatchTouchEvent 方法,所以调用的就是ViewGroup类的方法了。所以到这里,事件就交给ViewGroup去分发给控件树了。
 当然我们的ViewGroup中包含了我们大量的View。
整个流程
结合上面的流程我们可以知道整个流程为:
 ViewRootImpl -> DecorView -> Activity ->PhoneWindow -> DecorView -> ViewGroup -> View
 然而我们最后在应用开发时关注的就是从Activity出发,或者从ViewGroup的事件处理。
 下面将会以Activity出发为例讲解
从Activity出发
Activity ->PhoneWindow -> DecorView -> ViewGroup -> View
 其中我们在页面时是不需要处理PhoneWindow -> DecorView的所以我们的流程可以简化为
 Activity -> ViewGroup -> View
 是不是一下子熟悉了。老生常谈的Activity -> ViewGroup -> View
事件序列
指从手指刚接触屏幕,到手指离开屏幕的那一刻结束,在这一过程产生的一系列事件,这个序列一般以down事件开始,中间含有多个move事件,最终以up事件结束
源码解析
这里简单的对源码解析一下哦,建议看一遍没多难的,也没多少代码,主要是了解流程中重要的方法。
Activity对事件的处理
//Activity.java
public boolean dispatchTouchEvent(MotionEvent ev) {  
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {  
            onUserInteraction();  
        }  
        if (getWindow().superDispatchTouchEvent(ev)) {  
            return true;  
        }  
        return onTouchEvent(ev);  
}
 
从源码里可以看出,事件交给了Activity所附属的Window进行分发,返回true则结束事件分发,否则代表所有的View的onTouchEvent返回了false(均不处理),这时是由Activity的onTouchEvent来处理。
Window对事件的处理
Window的唯一实现类为PhoneWindow
//PhoneWindow.java
@Override  
public boolean superDispatchTouchEvent(MotionEvent event) {  
    return mDecor.superDispatchTouchEvent(event);  
}
 
从源码里可以看出,事件交给了DecorView处理。我们继续看DecorView。
DecorView对事件的处理
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker{…}
 
public boolean superDispatchTouchEvent(MotionEvent event) {
    return super.dispatchTouchEvent(event);
}
 
DecorView是继承自FrameLayout的,而大家都知道FrameLayout又继承了ViewGroup,所以下一级为ViewGroup。
ViewGroup对事件的处理
dispatchTouchEvent核心代码如下:
 ViewGroup与Activity、View相比,多了一个onInterceptTouchEvent()事件拦截方法,事件传递到ViewGroup若onInterceptTouchEvent返回true,则事件由ViewGroup处理。若返回false,才会调用子View的dispatchTouchEvent
// 检查是否进行事件拦截  
final boolean intercepted;  
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {  
    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;  
    if (!disallowIntercept) {  
    //回调onInterceptTouchEvent(),返回false表示不拦截touch,否则拦截touch事件 
    intercepted = onInterceptTouchEvent(ev);  
    ev.setAction(action);
  } else {  
      intercepted = false;  
     }  
} else {  
   //没有touch事件的传递对象,同时该动作不是初始动作down,ViewGroup继续拦截事件  
   intercepted = true;
}
 
当ViewGroup的onInterceptTouchEvent返回false,会首先遍历所有的子元素,判断子元素是否能够接收点击事件。若子元素具备接收事件的条件,那么它的dispatchTouchEvent会被调用,若遍历完所有的子元素均返回false,那么只能ViewGroup自己去处理该事件。子元素的该方法返回true会终止遍历子元素。
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
        View child, int desiredPointerIdBits) {
    final boolean handled;
    // Canceling motions is a special case.  We don't need to perform any transformations
    // or filtering.  The important part is the action, not the contents.
    final int oldAction = event.getAction();
    if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
        event.setAction(MotionEvent.ACTION_CANCEL);
        if (child == null) {
            handled = super.dispatchTouchEvent(event);
        } else {
            handled = child.dispatchTouchEvent(event);
        }
        event.setAction(oldAction);
        return handled;
    }
 
View对事件的处理
那么就到最后的View的事件处理了
 View无法继续向下传递事件,通过一系列的判断最后通过onTouchEvent消费事件
public boolean dispatchTouchEvent(MotionEvent event) {  
boolean result = false;
//...
    if (onFilterTouchEventForSecurity(event)) {  
       //noinspection SimplifiableIfStatement  
       ListenerInfo li = mListenerInfo;  
       //会执行View的OnTouchListener.onTouch这个函数,若返回true,onTouchEvent便不会再被调用了。
       //可见OnTouchListener比onTouchEvent优先级更高。
       if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED  
                    && li.mOnTouchListener.onTouch(this, event)) {  
         return true; }  
  
       if (onTouchEvent(event)) {  
            return true; 
       }  
}
    //…
    return result;  
}
 
小知识:其中判断也使用了li.mOnTouchListener.onTouch(this, event)。所以OnTouchListener比onTouchEvent优先级更高。也是在这里体现出来的。
页面分发流程
废话不多说直接上个U型流程图,老生常谈的流程图,都用烂了。
 
注意:有一点不要被图所误导,ViewGroup中是没有onTouchEvent的。因为ViewGroup继承了View所以ViewGroup的onTouchEvent在View中。
dispatchTouchEvent() :分发点击事件
dispatchTouchEvent()方法是事件分发的核心方法,并且它是Activity、ViewGroup、View都实现了的方法。在事件分发中,事件首先会传递到Activity的dispatchTouchEvent()方法,该方法会根据具体情况将事件传递到父容器和子View的dispatchTouchEvent()方法中进行处理。如果事件被消耗了,就会立即停止事件传递,否则会一直传递到最后一个View中。如果View处理了此次事件则返回true否则返回false。
onInterceptTouchEvent():在ViewGroup层拦截点击事件
onInterceptTouchEvent()方法是ViewGroup中的一个方法,其主要作用是在ViewGroup拦截子View的TouchEvent,即截获子View的TouchEvent。如果ViewGroup拦截了TouchEvent,则对应的子View就接收不到TouchEvent。
onTouchEvent():处理点击事件
onTouchEvent()方法是View的一个方法,其主要作用是处理View的TouchEvent,比如说View被点击、View被拖动等。当View接收到TouchEvent时,会通过onTouchEvent()方法对TouchEvent进行响应处理。如果View处理了此次事件则返回true否则返回false。
小结
小结是结合图来的,需要对照着图来看(一定对着图看,不然看到字你会不想看)
 1、首先消费代表时间到此为止不再继续传递
 2、如果我们没有对控件里面的方法进行重写或更改返回值,而直接用super调用父类的默认实现,那么整个事件流向应该是从Activity---->ViewGroup—>View 从上往下调用dispatchTouchEvent方法,一直到叶子节点(View)的时候,再由View—>ViewGroup—>Activity从下往上调用onTouchEvent方法。
 3、对于 dispatchTouchEvent,onTouchEvent,return true是终结事件传递。return false 是回溯到父View的onTouchEvent方法。
 5、onTouchEvent return false就是不消费事件,并让事件继续往父控件的方向从下往上流动。
 6、ViewGroup 想把自己分发给自己的onTouchEvent,需要拦截器onInterceptTouchEvent方法return true 把事件拦截下来。
 7、View 没有拦截器,为了让View可以把事件分发给自己的onTouchEvent,View的dispatchTouchEvent默认实现(super)就是把事件分发给自己的onTouchEvent。
ACTION_MOVE 和 ACTION_UP
对于在onTouchEvent消费事件的情况:在哪个View的onTouchEvent 返回true,那么ACTION_MOVE和ACTION_UP的事件从上往下传到这个View后就不再往下传递了,而直接传给自己的onTouchEvent 并结束本次事件传递过程。
 举个例子:
 我们在ViewGroup2的onInterceptTouchEvent 返回true拦截此次事件并且在ViewGroup 1 的onTouchEvent返回true消费这次事件。
 红色的箭头代表ACTION_DOWN 事件的流向
 蓝色的箭头代表ACTION_MOVE 和 ACTION_UP 事件的流向
 
 可以看到我们的ACTION_MOVE 和 ACTION_UP会在事件消费的ViewGroup1就不再往下传递了,直接返回给自己ViewGroup1的onTouchEvent去处理。
Cancel事件讲解
什么时候会触发ACTION_CANCEL事件呢?
 首先Cancel事件是由父View通知给子View的。
 触发Cancel事件有以下两种:
 1、ViewGroup拦截并消费事件
 2、ViewGroup中移除了View
 以上两种情况要保证,Down事件是该View需要消费的情况下。
 举两个例子:
 例子一:ViewGroup拦截并消费事件
 在 ScrollView 中,手势的优先级一般是滚动操作 > 点击操作
 像Scrollview 这种可滚动控件中,如果是手指按下操作后继续滑动,会对之前点中的子控件发送一个 Cancel 事件
这是因为当手指按下时,如果用户继续向上或向下滑动,就会触发 ScrollView 的滚动操作,而 ScrollView 同时也会响应子控件的点击事件。如果用户在这时继续向上或向下滑动,就会产生一个冲突:ScrollView 想要滚动,同时子控件也想要处理点击事件。
为了避免这种冲突,ScrollView 会在用户按下并持续滑动时,向之前被点中的子控件发送一个 Cancel 事件,通知它取消掉之前的点击操作。这样一来,ScrollView 就可以顺利地滚动,而子控件也不会误操作。
也就是说ScrollView子View被ScrollView这个ViewGroup将事件拦截了。在ScrollView的源码中我们可以看到
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
        return true;
    }
    if (super.onInterceptTouchEvent(ev)) {
        return true;
    }
    ....
    ....
    switch (action & MotionEvent.ACTION_MASK) {
        case MotionEvent.ACTION_MOVE: {
            if (yDiff > mTouchSlop && (getNestedScrollAxes() & SCROLL_AXIS_VERTICAL) == 0) {
                mIsBeingDragged = true;
                mLastMotionY = y;
                initVelocityTrackerIfNotExists();
                mVelocityTracker.addMovement(ev);
                mNestedYOffset = 0;
                if (mScrollStrictSpan == null) {
                    mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll");
                }
                final ViewParent parent = getParent();
                if (parent != null) {
                    parent.requestDisallowInterceptTouchEvent(true);
                }
            }
            break;
        }
        ....
        ....    
    }
    return mIsBeingDragged;
}
 
在ACTION_MOVE的过程中,看那个If判断,我来解读一下。
如果手指在竖直方向上移动的距离大于系统默认的最小触摸距离(mTouchSlop),并且嵌套滑动轴(NestedScrolling)上竖直方向没有滚动,则将ScrollView标记为正在被拖拽(mIsBeingDragged = true)。
 记录当前手指的位置(mLastMotionY = y),并初始化速度追踪器(initVelocityTrackerIfNotExists())以追踪用户的滑动速度。此外,还设置NestedYOffset为0,为处理嵌套滑动做准备。
也就是说在ACTION_MOVE在确定是滑动状态后mIsBeingDragged = true。也就是onInterceptTouchEvent的返回值为true。
 上面我们讲到了onInterceptTouchEvent返回true就会把事件拦截在本层ViewGroup送去当前ViewGroup的onTouchEvent去消费。也就是将事件消费到了ScrollView。
总结:因为ScrollView在滑动时会拦截事件,所以会向子View发送了Cancel事件
例子二:ViewGroup中移除了View
 这个大家自己去测试一下比较简单:
 手指按在View 上,3s后将View 从ViewGroup里移除。之后观察onTouchEvent的事件返回。
viewGroup.postDelayed(new Runnable() {
        @Override
        public void run() {
            getWindowManager().removeView(getWindow().getDecorView());
        }
    }, 3000);
 
常见的事件分发问题
嵌套滑动
多个滑动控件同时存在时,滑动事件可能会相互干扰,需要通过事件分发机制来解决滑动冲突问题。
 我会单独对嵌套滑动单独出一篇文章来讲解,并且附带一些实战的用例。
 比如:
 1、ScrollView+ ListView(RecyclerView) 嵌套冲突
 2、ScrollView+ ViewPager嵌套问题
 3、RecyclerView + RecyclerView相同和不同方向的嵌套滑动
 4、商城APP常见的主页多列表,还有吸顶的Tab
多点触控
因为多点触控可能会出现手指交错的情况,导致事件分发混乱,所以需要通过事件分发机制去处理多点触控事件。
总结
没啥总结的,希望大家可以认真阅读。内容较多,可以分批阅读。关于嵌套滑动会在下一篇文章讲解。
 大家发现问题一定要评论下面哦,大家共同进步。










