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

news2024/11/24 19:33:49

一篇文章搞定《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

多点触控

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

总结

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

道岔外锁闭装置介绍

简述 道岔外锁闭装置是一种能可靠地锁闭尖轨和基本轨的器械。它能有效地克服尖轨在密贴时的转换阻力&#xff0c;即使连接杆折断&#xff0c;外锁闭装置仍在起着锁闭作用。外锁闭能够隔离列车通过时对转换设备的振动和冲击&#xff0c;提高转换设备寿命和可靠性。 产品分类 …

Kubernetes Dashboard + Ingress 及其 yaml 文件分析

概述 记录部署Dashboard Ingress的具体过程及其 yaml 文件分析 Dashboard Yaml # Copyright 2017 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the Li…

【C++】类和对象的应用案例 2 - 点和圆的关系

目录 1、缘起 2、分析 3、示例代码 4、代码优化 4.1、point.h 4.2、point.c 4.3、circle.h 4.4、circle.c 4.4、main.c 5、总结 1、缘起 在 C 中&#xff0c;类和对象可以用来构建有趣的应用程序。一个生动形象且吸引人的案例是点和圆的关系。通过创建一个 Po…

C++内存管理 (new、delete)知识点+完整思维导图+实操图+深入细节通俗易懂建议收藏

绪论 我成功是因为我有决心&#xff0c;从不踌躇。——拿破仑 本章是关于c内存管理的文章&#xff0c;字数不多&#xff0c;内容简单&#xff0c;希望对你有所帮助&#xff01;&#xff01; 话不多说安全带系好&#xff0c;发车啦&#xff08;建议电脑观看&#xff09;。 附&a…

【redis基础】redis的十大数据类型

前言 接受虚竹哥的建议&#xff0c;后续将发布redis的系列文章 本篇文章针对有关Redis7数据类型的的使用率较高的操作进行讲解&#xff0c;有关Redis7中数据类型的全部操作请查阅&#xff1a;Commands | Redis 上一篇文章&#xff1a;初识redis【redis的安装使用与卸载】_努…

Java --- 云尚办公之Activiti7

目录 一、工作流 1.1、什么是工作流 1.2、工作流引擎 1.3、常见工作流引擎 二、 Activiti7概述 2.1、Activiti介绍 2.2、建模语言BPMN 2.3、 Activiti使用流程 三、 Activiti使用 3.1、部署流程工具 3.2、画出流程图并放入项目中 3.3、流程定义部署 3.4、启动流程…

要闻 | 自然资源部实景三维建设与城市精细化治理工程技术创新中心揭牌仪式暨第二季度工作推进会顺利召开

2023年5月16日上午&#xff0c;全国首个实景三维领域部级工程技术创新中心——自然资源部实景三维建设与城市精细化治理工程技术创新中心&#xff08;以下称“中心”&#xff09;在武汉大势智慧科技有限公司举行了揭牌仪式&#xff0c;并在大势会议室顺利召开了中心第二季度工作…

19-02 基于业务量级的架构技术选型演进

从零开始——单服务应用 单体应用技术选型 &#xff08;GitHub、Gitee…&#xff09;搜索是否有线程的产品用最熟悉的技术&#xff0c;最快的速度上线如果有经费&#xff1a;考虑商业化解决方案 个人小程序怎么做技术选型的 搜索是否有快速搭建下程序的软件技术选型 后端技…

PX4从放弃到精通(二十五):EKF2

文章目录 前言一、主程序二、update()二、predictState()三、controlFusionModes()四、controlGpsFusion五、fuseGpsVelPos()六、fuseHorizontalVelocity()七、fuseVelPosHeight()八、calculateOutputStates()前言 PX4 1.13.2 PX4 ekf的传感器数据放在FIFO的环形缓冲区中,每…

智能综合交通(水运、航空)主题汇总(附下载链接)

根据我国现行的交通运输体制&#xff0c;公路以外的交通运输称为综合交通运输形式&#xff0c;包括铁路、航空和水运等。本部分包括航空和水运运输&#xff08;铁路部分另外单独列出&#xff09;。 智慧水运、航道、港口 &#xff08;至2023年05月23日&#xff09; 湛江市综合交…

容器编排器们的自我介绍

哈喽大家好&#xff0c;我是咸鱼 咸鱼在《一文带你了解容器技术的前世今生》有介绍过容器技术的由来以及Docker项目的发展 我们知道&#xff0c;Docker 及其他容器技术能够极大地简化应用程序的部署&#xff0c;做到了”开箱即用“ 俗话说&#xff1a;”凡是具有两面性“。容…

OSPF 5种报文、状态机、邻居建立的过程

OSPF 5种报文、状态机、邻居建立的过程 一、OSPF的5种报文二、OSPF状态机三、OSPF建立的过程 一、OSPF的5种报文 Hello 用于发现邻居&#xff0c;维持邻居的状态 DD DD报文有确定设备主从&#xff0c;同步DD序列号 包含着LSA的头部信息&#xff0c;有同步LSDB数据库的作用 LSA…

云原生|Kubernetes Operator测试实例

目录 一、主要代码介绍 &#xff08;一&#xff09;变量定义&#xff1a; &#xff08;二&#xff09;测试程序入口 &#xff08;三&#xff09;before函数 &#xff08;四&#xff09;after函数 二、实际测试 &#xff08;一&#xff09;块划分 &#xff08;二&#x…

【数据分享】1997-2017 年中国市县级能耗与能效数据集

能源既关乎国家经济命脉&#xff0c;又影响人民生活水平&#xff01;能源相关的研究是各个行业的热门研究话题&#xff0c;能源相关的数据也是经常使用的数据&#xff01; 我们发现在figshare平台上分享有1997-2017 年我国市县级的能耗与能效数据&#xff0c;数据格式为Excel&…

CMake常用命令总结与练习

CMake常用命令总结 前言cmake_minimum_required (VERSION XX):CMake最低版本project (demo)&#xff1a;CMake工程名add_executable(main main.c):生成可执行文件aux_source_directory(dir var)&#xff1a;指定源文件放入变量set(val src):指定源文件放入变量include_director…

js闭包的简单应用

闭包的概念&#xff1a;有权访问其他函数的内部变量的函数 闭包是作用域运用的特殊场景。 注&#xff1a;js中自由变量的查找是在函数定义的地方向上级作用域查找&#xff0c;不是在执行的地方。 闭包的使用场景概况为两种&#xff0c;一种是函数作为参数被传递&#xff0c;一种…

面试官:MySQL自增主键一定是连续的吗?

测试环境&#xff1a; MySQL版本&#xff1a;8.0 数据库表&#xff1a;T &#xff08;主键id&#xff0c;唯一索引c&#xff0c;普通字段d&#xff09; 如果你的业务设计依赖于自增主键的连续性&#xff0c;这个设计假设自增主键是连续的。但实际上&#xff0c;这样的假设是错的…

还在用System.out+System.currentTimeMillis打印耗时?Arthas是你不可或缺的神器!

如何分析一下自己的web项目的性能消耗&#xff0c;一般我们会在开始与结束加时间相减打印出消耗时间。这种方法侵入率太高了&#xff0c;如果想利用arthas整体调优一下自己web项目的性能点的话&#xff0c;可以使用以下的步骤或思路。&#xff08;当然xrebel也是一个不错的选择…

进程的创建和回收

一、进程概念&#xff1a;程序运行的状态 程序&#xff1a; 1.存放在磁盘上的指令和数据的有序集合&#xff08;文件&#xff09; 2.静态的 进程&#xff1a; 1.执行一个程序所分配的资源的总称 2.动态的 进程和程序内容区别 进程包含的内容&#xff1a; BSS段&#xf…

Buffer Pool

Buffer Pool Innodb 存储引擎设计了一个缓冲池&#xff0c;来提升读写的性能。 在 MySQL 启动的时候&#xff0c;InnoDB 会为 Buffer Pool 申请一片连续的内存空间&#xff0c;然后按照默认的16KB的大小划分出一个个的页&#xff0c; Buffer Pool 中的页就叫做缓存页。此时这些…