一篇文章搞定《Android事件分发》

一篇文章搞定《Android事件分发》

  • 什么是事件分发
  • MotionEvent事件
  • 事件如何从屏幕到APP
    • InputManagerService
    • WindowManagerService
    • Window
    • 小结
  • 事件如何从APP到达对应页面
    • 第一步:分类
    • 第二步:送去Activity
    • 后续的传递
    • 小结:
  • 页面的事件分发
    • 整个流程
    • 从Activity出发
    • 事件序列
    • 源码解析
      • Activity对事件的处理
      • Window对事件的处理
      • DecorView对事件的处理
      • ViewGroup对事件的处理
      • View对事件的处理
    • 页面分发流程
      • dispatchTouchEvent() :分发点击事件
      • onInterceptTouchEvent():在ViewGroup层拦截点击事件
      • onTouchEvent():处理点击事件
      • 小结
    • ACTION_MOVE 和 ACTION_UP
    • Cancel事件讲解
  • 常见的事件分发问题
    • 嵌套滑动
    • 多点触控
  • 总结

什么是事件分发

事件分发是将屏幕触控信息分发给控件树的一个套机制。 当我们触摸屏幕时,会产生一系列的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

多点触控

因为多点触控可能会出现手指交错的情况,导致事件分发混乱,所以需要通过事件分发机制去处理多点触控事件。

总结

没啥总结的,希望大家可以认真阅读。内容较多,可以分批阅读。关于嵌套滑动会在下一篇文章讲解。
大家发现问题一定要评论下面哦,大家共同进步。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/23726.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

移动云COCA架构,重新定义下一代云

当前&#xff0c;算力已经成为 全球科技竞争的焦点 为此&#xff0c;移动云重磅发布 「移动云COCA&#xff08;Compute on chip Architecture&#xff09;软硬一体片上计算架构」 以下简称移动云COCA架构 以此打造国家级自主可控的 高性能算力底座 带动国产化智算产业成熟…

【ISO14229_UDS刷写】-5-$38诊断服务RequestFileTransfer理论部分

总目录&#xff1a;&#xff08;单击下方链接皆可跳转至专栏总目录&#xff09; 《UDS/OBD诊断需求编辑工具》总目录https://blog.csdn.net/qfmzhu/article/details/123697014 目录 1 $0x38 RequestFileTransfer诊断服务描述 2 0x38服务请求消息 2.1 0x38服务请求消息定义…

写给初学者的YOLO目标检测 概述

文章目录 什么是目标检测What is YOLO?为什么YOLO在目标检测领域如此流行&#xff1f;1. 速度快2. 高检测精度3. 更好的泛化性4. 开源 YOLO架构YOLO目标检测是如何工作的&#xff1f;残差块(Residual blocks)边界框回归&#xff08;Bounding box regression&#xff09;交并比…

基于ESP32-CAM 和 OpenCV 设计的手势控制虚拟鼠标

概述 在本文中,我们将使用ESP32-CAM和OpenCV开发手势控制虚拟鼠标。ESP32 Camera Module和Python程序可用于无线控制鼠标跟踪和点击操作。 入门者必须具备 Python、图像处理、嵌入式系统以及物联网的丰富知识。首先,我们将了解如何控制鼠标跟踪和单击,以及运行 python 程序…

20年前,微软给金山那刀,现今一举将WPS推上领奖台,WPS,赢了

WPS&#xff0c;赢了 正如姚冬&#xff08;金山办公副总裁&#xff09;所说&#xff1a; 文本的命令行到图形界面的那次&#xff0c;改变整个人机交互的方式&#xff0c;我们公司历史上最大的一次危机也是那一次&#xff0c; 但我们依然挺过来了&#xff0c;我们相信这次技术浪…

一个神奇的工具,让URL地址都变成了“ooooooooo“

一个神奇的工具&#xff0c;让URL地址都变成了"ooooooooo" 一、核心代码二、URL编码/解码 最近发现一个有意思工具&#xff0c;就是将一个URL地址转换为都是 ooooooooo 的样子&#xff0c;通过转换后的地址访问可以转换回到原始地址&#xff0c;转换的逻辑有点像短链…

linuxOPS基础_linux安装配置

Linux系统下载 Linux系统版本选择&#xff1a;CentOS7.6 x64&#xff0c;【镜像一般都是CentOS*.iso文件】 问题&#xff1a;为什么不选择最新版的8 版本&#xff1f; 7.x 目前依然是主流 7.x 的各种系统操作模式是基础 官网&#xff1a;https://www.centos.org/ &#xff0c;…

springboot+vue4S店车辆管理系统(java项目源码+文档)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的4S店车辆管理系统。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 &#x1f495;&#x1f495;作者&#xff1a;风…

linux小技巧-如何修改IP(四种方法)

目录 项目场景&#xff1a; 方法分析及步骤介绍 原因分析&#xff1a; 解决方案&#xff1a; 项目场景&#xff1a; 项目上经常遇到修改IP的情况&#xff0c;这里总结一些各个情况下修改IP的方法&#xff0c;尤其时有时候没有主机屏幕显示&#xff0c;借助于命令行的方式修…

行业领先生物制药企业在冷链物流运输中采用虹科LIBERO温度记录解决方案

中国首个获得世界卫生组织国际通用名的生物Ⅰ类新药是用于抗血管内皮生长因子的融合蛋白&#xff0c;该药物通过结合血管内皮生长因子VEGF&#xff0c;竞争性抑制VEGF与受体结合并阻止VEGF家族受体的激活&#xff0c;从而抑制内皮细胞增殖和血管新生&#xff0c;达到治疗湿性年…

即时通讯软件(企业IM)对企业的作用

随着网络时代的来临&#xff0c;即时通讯软件也开始逐渐在企业中流行起来&#xff0c;甚至很多公司都已经将即时通讯软件作为内部沟通的主要工具&#xff0c;那么这种情况下&#xff0c;即时通讯软件对企业究竟有什么作用呢&#xff1f; 首先&#xff0c;即时通讯软件对于企业内…

Solidity拓展:数据类型的转换

1.数据类型隐式转换 (自动) 同一类型之间的转换:由低长度转换为高长度int8-int16-int32int256,但int不能自动转换成uint&#xff0c;因为放不下负数所以直接不让转换,且 int8 不能转换成 uint256 &#xff08;因为 uint256 不能涵盖某些值&#xff0c;例如&#xff0c; -1&…

Spark集群的运行基本流程是怎样的?

Spark集群的运行架构 Spark是基于内存计算的大数据并行计算框架&#xff0c;比MapReduce计算框架具有更高的实时性&#xff0c;同时具有高效容错性和可伸缩性&#xff0c;在学习Spark操作之前&#xff0c;首先介绍Spark运行架构&#xff0c;如图所示。 在上图中&#xff0c;Sp…

“数字”厨电成新宠?“传统”厨电如何凭实力年销破百亿?|厨房电器SMI社媒心智品牌榜

Social Power 核心解读 AI加持&#xff0c;数字厨电新物种持续走红 传统厨电发力社媒&#xff0c;“有范儿”实力吸睛 4月中下旬的“魔都”可谓热闹非凡&#xff0c;上海车展喧嚣未落&#xff0c;隔壁2023AWE&#xff08;中国家电及消费电子博览会&#xff09;的群雄逐鹿紧随…

PCIE知识点-022:PCIe 时钟结构

图1&#xff1a;参考时钟结构示意图[4] 1. Common Refclk Architecture Common Refclk Architecture&#xff0c;即同源参考时钟架构&#xff0c;PCIe收发设备共用一个时钟源&#xff0c;是目前是使用最为广泛的方案。 缺点&#xff1a; 对于适用同一 Common Clock 作为参考时…

新冠又临“涨潮”期,看我AI显身手

#疫情#北京时间5月24日&#xff0c;北京市卫生健康委发布2023年第20周疫情周报。报告显示&#xff0c;2023年第20周全市共报告法定传染病14种25544例&#xff0c;死亡1例。报告病例数居前5位的病种依次为:新型冠状病毒感染、其它感染性腹泻病、流行性感冒、肺结核和病毒性肝炎&…

chatgpt赋能python:Python请求头——让你的网络请求更有效率

Python请求头——让你的网络请求更有效率 网络请求是现代应用程序的核心。但是&#xff0c;如果你不了解Python请求头的概念和用途&#xff0c;那么你可能会面临一些棘手的问题。在本文中&#xff0c;我们将深入探讨Python请求头&#xff0c;了解其作用、语法和最佳实践。 什…

GPT-4平替版:MiniGPT-4,支持图像理解和对话,现已开源

项目地址&#xff1a;https://minigpt-4.github.io/ 论文链接&#xff1a;https://github.com/Vision-CAIR/MiniGPT-4/blob/main/MiniGPT_4.pdf 代码&#xff1a;https://github.com/Vision-CAIR/MiniGPT-4 视频&#xff1a;https://youtu.be/__tftoxpBAw 数据集&#xff…

推荐|x86视觉运动控制一体机VPLC710

正运动技术始终围绕客户需求不断迭代升级产品及开发&#xff0c;积极探索工控自动化高质量发展新路径&#xff0c;着眼于全力为客户提供更优质的产品与服务&#xff0c;特此开发了一款可满足全场景高速高精及中大型产线设备应用需求的x86的IPC形态控制器。 VPLC710产品简介 VP…

【JavaSE】Java基础语法(九):封装

文章目录 ☔1. private关键字☔2. private关键字的使用☔3. this关键字☔4. this内存原理☔5. 封装思想 ☔1. private关键字 概述 : private是一个修饰符&#xff0c;可以用来修饰成员&#xff08;成员变量&#xff0c;成员方法&#xff09; 特点 : 被private修饰的成员&…