一篇文章搞定《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
多点触控
因为多点触控可能会出现手指交错的情况,导致事件分发混乱,所以需要通过事件分发机制去处理多点触控事件。
总结
没啥总结的,希望大家可以认真阅读。内容较多,可以分批阅读。关于嵌套滑动会在下一篇文章讲解。
大家发现问题一定要评论下面哦,大家共同进步。