0. 前言  

转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52566965

深入学习事件分发机制,是为了解决在Android开发中遇到的滑动冲突问题做准备。事件分发机制描述了用户的手势一系列事件是如何被Android系统传递并消费的。

首先对事件分发机制进行概述:如果当一个点击事件发生时,事件最先传递给当前Activity,再传递给Window,接着传递给顶级View,最后按照事件分发机制去分发事件。事件的传递过程可以用以下伪代码进行描述:

public boolean dispatchTouchEvent(MotionEvent ev){//事件传递到,那么该方法一定会被调用
boolean consume = false;
//onInterceptTouchEvent只存在于ViewGrope,判断是否拦截该事件
//但不是每次都调用该方法,后面会详细介绍
if(onInterceptTouchEvent(ev)){
if(OnTouchListener.onTouch(ev)){// OnTouchListener优先级较onTouchEvent高
consume = onTouchEvent(ev);//处理点击事件
}
}else{
consume = child. dispatchTouchEvent(ev);
}
return consume;
}

对于事件传递后的事件消费,如果一个View设置了OnTouchListener,则OnTouchListener的onTouch会首先被调用。若onTouch返回false,最后才轮到onTouchEvent去消费该事件(我们平时设置的OnclickListener就在onTouchEvent方法中,优先级较低)。

若onTouchEvent返回了true,则表示事件已经被消费了,否则它的父容器的onTouchEvent将会调用,以此类推,直至由Activity的onTouchEvent被调用。

整个过程的简要流程图如下所示:

1. 事件的分发详解

如果需要再进一步分析事件的分发机制,那么必须阅读源码。

一个点击事件发生时,事件第一步最先传递给当前Activity。


1. 1  Activity对事件的分发

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来处理。


1. 2  Window对事件的分发

Window的superDispatchTouchEvent()是一个抽象方法,Window的唯一实现是PhoneWindow。

下面代码便来自于PhoneWindow对事件的分发逻辑。

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

从源码里可以看出,事件交给了DecorView处理。我们继续查看DecorView的定义。

1. 3  DecorView的定义

private final class DecorView extends FrameLayout implements RootViewSurfaceTaker{…}

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

从源码里可以看出,DecorView是继承自FrameLayout的,毫无疑问FrameLayout又继承了ViewGroup,那么剩下的工作就是研读 GroupView的dispatchTouchEvent方法了。

1. 4  ViewGroup

ViewGroup和Activity、View比,多了一个onInterceptTouchEvent()事件拦截方法,事件传递到ViewGroup若onInterceptTouchEvent返回true,则事件由ViewGroup处理(OnTouchListener比onTouchEvent优先级要高,这一点前面也介绍过了)。若返回false,子View的dispatchTouchEvent会被调用。

但是onInterceptTouchEvent并不是每次都会调用并判断是否拦截事件,ViewGroup.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判断是否去拦截事件的前提是ACTION_DOW或者mFirstTouchTarget != null,关于mFirstTouchTarget,如果事件由ViewGroup的子View成功处理,mFirstTouchTarget会指向该子View不为空。

当面对ACTION_DOW事件时,ViewGrope总是会调用自己的onInterceptTouchEvent来询问是否去拦截事件,因此若ViewGroup拦截了ACTION_DOWN事件,mFirstTouchTarget一定为空。当后续ACTION_MOVE和ACTION_UP事件到来时,ViewGroup的onInterceptTouchEvent不会调用,直接拦截。那么有什么办法可以修改这种默认机制呢?

我们还注意到源码中的标记位FLAG_DISALLOW_INTERCEPT,该标记位通过子View的getParent().requestDisallowInterceptTouchEvent方法来设置,作用是ViewGrope将无法拦截除ACTION_DOW以外的点击事件。这为我们后面如何处理滑动冲突提供思路。至于为什么无法拦截ACTION_DOW,可以由以下源码证明。

//处理初始的down事件
if (actionMasked == MotionEvent.ACTION_DOWN) {
// ACTION_DOWN到来时的重置操作
//当app切换、 ANR或一些其他的touch状态发生时,framework会丢弃或取消先前的touch状态
cancelAndClearTouchTargets(ev);
resetTouchState();//该方法中会重置FLAG_DISALLOW_INTERCEPT标记位
}


1. 5  View对事件的处理

当ViewGrope的onInterceptTouchEvent返回false,会首先遍历所有的子元素,判断子元素是否能够接收点击事件(通过判断子元素是否在播放动画并且点击坐标落在该子元素区域内)。若子元素具备接收事件的条件,那么它的dispatchTouchEvent会被调用,若遍历完所有的子元素均返回false,那么只能ViewGrope自己去处理该事件。子元素的该方法返回true会终止遍历子元素。

事件传递就来到了子View的“手里”,处理过程如下。

public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = false;
//...
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
return true; } if (onTouchEvent(event)) {
return true; }
}
//…
return result;
}

View无法继续向下传递事件,只能处理之。从源码第8行可以看出会执行View的OnTouchListener.onTouch这个函数,若返回true,onTouchEvent便不会再被调用了。可见OnTouchListener比onTouchEvent优先级更高。

1. 6  View的onTouchEvent

首先当View处于不可用状态时的处理过程如下:

if ((viewFlags & ENABLED_MASK) == DISABLED) {
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}

可以看出View的enable属性不影响onTouchEvent的返回值,View的onTouchEvent会默认消耗事件并返回true,除非其CLICKABLE和LONG_CLICKABLE均为false。后者在View中默认为false,前者根据控件本身来决定。通过setClickable和setLongClickable可以改变View的这两个属性。setOnClickListener和setOnLongClickListener本质上也是通过setClickable和setLongClickable来改变View的这两个属性。

View对事件的具体处理如下:

public boolean onTouchEvent(MotionEvent event) {
//...
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
...
performClick();
break;
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_CANCEL:
break;
case MotionEvent.ACTION_MOVE:
break;
}
//
return true;
}
return false;
}

从源码中可以看出,如果一个控件是clickable或longclickable的,那么就会执行ACTION_UP、ACTION_DOWN等case里面,并最终返回true。需要说明的是,如果在上一个case(比如:ACTION_UP)中返回了false,那么下面所有的case(比如:ACTION_CANCEL、ACTION_MOVE等)都不会得到执行。

ACTION_UP发生时会调用performClick方法,源码如下所示:

public boolean performClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
if (mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListener.onClick(this);
return true;
}
return false;
}

从performClick的实现来看,如果View设置了OnClickListener,在performClick方法中会调用它的onClick方法。

至此关于事件分发机制就介绍完毕了

转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52566965

Android开发——事件分发机制详解的更多相关文章

  1. Android View 事件分发机制详解

    想必很多android开发者都遇到过手势冲突的情况,我们一般都是通过内部拦截和外部拦截法解决此类问题.要想搞明白原理就必须了解View的分发机制.在此之前我们先来了解一下以下三个非常重要的方法: di ...

  2. Android事件分发机制详解

    事件分发机制详解 一.基础知识介绍 1.经常用的事件有:MotionEvent.ACTION_DOWN,MotionEvent.ACTION_MOVE,MotionEvent.ACTION_UP等 2 ...

  3. Android 的事件传递机制,详解

    Android 的事件传递机制,详解 前两天和一个朋友聊天的时候.然后说到事件传递机制.然后让我说的时候,忽然发现说的不是非常清楚,事实上Android 的事件传递机制也是知道一些,可是感觉自己知道的 ...

  4. Android Touch事件传递机制详解 下

    尊重原创:http://blog.csdn.net/yuanzeyao/article/details/38025165 资源下载:http://download.csdn.net/detail/yu ...

  5. Android事件分发机制详解(2)----分析ViewGruop的事件分发

    首先,我们需要 知道什么是ViewGroup,它和普通的View有什么区别? ViewGroup就是一组View的集合,它包含很多子View和ViewGroup,是Android 所有布局的父类或间接 ...

  6. android 事件分发机制详解(OnTouchListener,OnClick)

    昨天做东西做到触摸事件冲突,以前也经常碰到事件冲突,想到要研究一下Android的事件冲突机制,于是从昨天开始到今天整整一天时间都要了解这方面的知识,这才懂了安卓的触摸和点击事件的机制.探究如下: 首 ...

  7. IOS 触摸事件分发机制详解

    欢迎大家前往云+社区,获取更多腾讯海量技术实践干货哦~ 作者:MelonTeam 前言 很多时候大家都不关心IOS触摸事件的分发机制的实现原理,当遇到以下几种情形的时候你很可能抓破头皮都找不到解决方案 ...

  8. 【Android面试查漏补缺】之事件分发机制详解

    前言 查漏补缺,查漏补缺,你不知道哪里漏了,怎么补缺呢?本文属于[Android面试查漏补缺]系列文章第一篇,持续更新中,感兴趣的朋友可以[关注+收藏]哦~ 本系列文章是对自己的前段时间面试经历的总结 ...

  9. Android事件分发机制详解(1)----探究View的事件分发

    探究View的事件分发 在Activity中,只有一个按钮,注册一个点击事件 [java] view plaincopy button.setOnClickListener(new OnClickLi ...

随机推荐

  1. ArcGIS for Service中JavaScript预览在内网环境无法使用

    1.问题说明 在使用ArcGIS for Service时经常会遇到一个问题,那就是我们需要对已经发布的服务进行预览,预览时点击对应服务,选择View in中的ArcGIS JavaScript就可在 ...

  2. Myeclipse与tomcat的运行问题

    在myeclipse中修改自己servlet后,在次运行时,可能会没有变化,这时需要重启tomcat,重新加载servlet

  3. http相关文章目录

    四种常见的 POST 提交数据方式  https://imququ.com/post/four-ways-to-post-data-in-http.html

  4. thinkphp的find()方法获取结果

    find方法返回的是一行记录,结果是一个数组,数组的key和sql中的field相对应,假设: $res=$model->find(filed="a,b,c"); 获取结果中 ...

  5. 一起来看看IOS内存泄漏的一个问题

    很多iOS开发的朋友都是比较关心内存泄漏的问题,在实际的开发工作中首先我们需要知道程序有没有内存泄露,然后定位到底是哪行代码出现内存泄露了,这样才能将其修复.最简单的方法当然是借助于专业的检测工具,比 ...

  6. 2017.10.28 QB模拟赛 —— 上午

    题目链接 T1 1e18 内的立方数有 1e6个 直接枚举可过 二分最优 考场用set  死慢.. #include <cstdio> int t; long long p; int ma ...

  7. linux下composer+laravel随笔

    1.composer中文网:https://www.phpcomposer.com/   laravel中文网:https://d.laravel-china.org/ 2.composer是的作用是 ...

  8. framework7滑动删除列表触发chrome 报错解决办法

    使用 <div class="list-block"> <ul> <li class="swipeout"> <div ...

  9. 画X,模拟水题

    题目链接:http://codeforces.com/contest/404/problem/A #include <stdio.h> #include <string.h> ...

  10. 轻量级HTTP服务器Nginx(Nginx性能优化技巧)

    轻量级HTTP服务器Nginx(Nginx性能优化技巧)   文章来源于南非蚂蚁   一.编译安装过程优化 1.减小Nginx编译后的文件大小在编译Nginx时,默认以debug模式进行,而在debu ...