转载请标明出处:
http://blog.csdn.net/hai_qing_xu_kong/article/details/53431274
本文出自:【顾林海的博客】

##前言
关于Android事件的分发机制一直是面试的常客,当然,对于自定义View来说,了解事件的分发机制是很有必要的,在日常开发中,由于一些特殊的需求需要我们自定义控件,然而在创建自定义控件中经常会发生事件的冲突,因此需要我们对android事件的分发机制有一个相对的了解,这样才能在遇到事件冲突做到游刃有余。

##事件机制

在Android中一次完整的事件传递机制包括三个阶段,分别是事件的分发、事件的拦截以及事件的消费。当手指触摸屏幕就会触发这些事件,因此手指触摸屏幕的事件类型可以概况为三种。

  • ACTION_DOWN:用户手指的按下操作,一个按下操作表明一次触摸事件的开始。
  • ACTION_MOVE:用户手指触摸到屏幕后,在松开之前,手指在屏幕移动的距离超过一定的阈值,就被判定为ACTION_MOVE操作,ACTION_MOVE操作随着手指在屏幕移动时会触发一系列的ACTION_MOVE操作。
  • ACTION_UP:手值从屏幕离开的操作,一次抬起操作表明一次触摸事件的结束。

在屏幕上的一系列操作中,ACTION_DOWN和ACTION_UP两个事件是必需的,而ACTION_MOVE只有手指在屏幕上滑动一定距离才会触发移动操作,如果手指并没有移动,只会触发按下和抬起操作。了解了触摸事件的三种类型后,我们看看事件传递机制的三个阶段。

1、事件的分发对应着dispatchTouchEvent方法,所有的触摸事件都是通过这个方法来分发,它方法原型如下:

@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {    return super.dispatchTouchEvent(ev);}

在这个方法中,返回值为true表示事件被当前视图消费掉,不再继续分发事件,方法返回值为super.dispatchTouchEvent表示继续分发该事件,当前视图如果为ViewGroup及其子类,则调用onInterceptTouchEvent方法判断是否拦截该事件。

2、事件的拦截对应着onInterceptTouchEvent方法,这个方法只在ViewGroup及其子类中存在,在View和Activity中是不存在的,方法原型如下:

@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {   return super.onInterceptTouchEvent(ev);}

在这个方法中,返回值true表示拦截事件,不继续分发给子视图,同时交由自身的onTouchEvent方法进行消费;返回false或者super.onIterceptTouchEvent表示不对事件进行拦截,继续传递给子视图。

3、事件的消费对应着onTouchEvent方法,这个方法用于消费,方法原型如下:

@Overridepublic boolean onTouchEvent(MotionEvent event) {   return super.onTouchEvent(event);}

在这个方法中,返回值为true表示当前视图可以处理对应的事件,事件将不会向上传递给父视图;返回值为false表示当前视图不处理这个事件,事件会被传递给父视图的onTouchEvent方法进行处理。

总结来说Activity拥有dispatchTouchEvent和onTouchEvent方法,ViewGroup及其子类拥有dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent三个方法,View拥有dispatchTouchEvent和onTouchEvent两个方法。

在MainActivity中实现dispatchTouchEvent和onTouchEvent方法,源码如下:

public class MainActivity extends AppCompatActivity {    private static final String TAG = "MainActivity";    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);    }    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        switch (ev.getAction()) {            case MotionEvent.ACTION_DOWN:                Log.e(TAG, "dispatchTouchEvent----------ACTION_DOWN");                break;            case MotionEvent.ACTION_MOVE:                Log.e(TAG, "dispatchTouchEvent----------ACTION_MOVE");                break;            case MotionEvent.ACTION_UP:                Log.e(TAG, "dispatchTouchEvent----------ACTION_UP");                break;            default:                break;        }        return super.dispatchTouchEvent(ev);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        switch (event.getAction()) {            case MotionEvent.ACTION_DOWN:                Log.e(TAG, "onTouchEvent----------ACTION_DOWN");                break;            case MotionEvent.ACTION_MOVE:                Log.e(TAG, "onTouchEvent----------ACTION_MOVE");                break;            case MotionEvent.ACTION_UP:                Log.e(TAG, "onTouchEvent----------ACTION_UP");                break;            default:                break;        }        return super.onTouchEvent(event);    }}

手指在Activity上面点击不滑动时,日志如下:

MainActivity: dispatchTouchEvent----------ACTION_DOWN
MainActivity: onTouchEvent----------ACTION_DOWN
MainActivity: dispatchTouchEvent----------ACTION_UP
MainActivity: onTouchEvent----------ACTION_UP

手指从屏幕点击到抬起(无滑动操作),会经过两个过程,分别是事件的分发和消费。上面的代码中ACTION_DOWN事件经过dispatchTouchEvent方法也就是事件的分发,返回值super.dispatchTouchEvent,事件继续分发,ACTION_DOWN事件交由onTouchEvent方法来消费,ACTION_UP也是一样。

将dispatchTouchEvent方法返回值该为true或false(手指轻微滑动),日志如下:

MainActivity: dispatchTouchEvent----------ACTION_DOWN
MainActivity: dispatchTouchEvent----------ACTION_MOVE
MainActivity: dispatchTouchEvent----------ACTION_UP

dispatchTouchEvent方法中,返回值为true或false,说明事件被消费掉,不进行分发。

Activity中的三种事件流程图如下:

(1)ACTION_DOWN事件传递机制

(2)ACTION_UP事件传递机制

(3)ACTION_MOVE事件传递机制

在上面的代码中只是让Activity实现了diapatchTouchEvent和onTouchEvent方法,并分析了在Activity中手指触摸屏幕后的事件传递机制,下面增加一点难度,自定义一个View,继承自Button,实现它的dispatchTouchEvent和onTouchEvent方法,并在MainActivity中添加Button的点击事件(onClick)和触摸事件(onTouch)。详细代码如下:

@SuppressLint("AppCompatCustomView")public class TestButton extends Button {    private static final String TAG = "TestButton";    public TestButton(Context context) {        super(context);    }    public TestButton(Context context, AttributeSet attrs) {        super(context, attrs);    }    public TestButton(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    @Override    public boolean dispatchTouchEvent(MotionEvent event) {        switch (event.getAction()) {            case MotionEvent.ACTION_DOWN:                Log.e(TAG, "dispatchTouchEvent----------ACTION_DOWN");                break;            case MotionEvent.ACTION_MOVE:                Log.e(TAG, "dispatchTouchEvent----------ACTION_MOVE");                break;            case MotionEvent.ACTION_UP:                Log.e(TAG, "dispatchTouchEvent----------ACTION_UP");                break;            default:                break;        }        return super.dispatchTouchEvent(event);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        switch (event.getAction()) {            case MotionEvent.ACTION_DOWN:                Log.e(TAG, "onTouchEvent----------ACTION_DOWN");                break;            case MotionEvent.ACTION_MOVE:                Log.e(TAG, "onTouchEvent----------ACTION_MOVE");                break;            case MotionEvent.ACTION_UP:                Log.e(TAG, "onTouchEvent----------ACTION_UP");                break;            default:                break;        }        return super.onTouchEvent(event);    }}
public class MainActivity extends AppCompatActivity {    private static final String TAG = "MainActivity";    private TestButton btn_test;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        initViews();        initEvent();    }    private void initViews() {        btn_test = (TestButton) findViewById(R.id.btn_test);    }    private void initEvent() {        btn_test.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                Log.e(TAG, "TestButton----------onClick");            }        });        btn_test.setOnTouchListener(new View.OnTouchListener() {            @Override            public boolean onTouch(View v, MotionEvent event) {                switch (event.getAction()) {                    case MotionEvent.ACTION_DOWN:                        Log.e(TAG, "TestButton----onTouch------ACTION_DOWN");                        break;                    case MotionEvent.ACTION_MOVE:                        Log.e(TAG, "TestButton------onTouch----ACTION_MOVE");                        break;                    case MotionEvent.ACTION_UP:                        Log.e(TAG, "TestButton------onTouch----ACTION_UP");                        break;                    default:                        break;                }                return false;            }        });    }    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        switch (ev.getAction()) {            case MotionEvent.ACTION_DOWN:                Log.e(TAG, "dispatchTouchEvent----------ACTION_DOWN");                break;            case MotionEvent.ACTION_MOVE:                Log.e(TAG, "dispatchTouchEvent----------ACTION_MOVE");                break;            case MotionEvent.ACTION_UP:                Log.e(TAG, "dispatchTouchEvent----------ACTION_UP");                break;            default:                break;        }        return super.dispatchTouchEvent(ev);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        switch (event.getAction()) {            case MotionEvent.ACTION_DOWN:                Log.e(TAG, "onTouchEvent----------ACTION_DOWN");                break;            case MotionEvent.ACTION_MOVE:                Log.e(TAG, "onTouchEvent----------ACTION_MOVE");                break;            case MotionEvent.ACTION_UP:                Log.e(TAG, "onTouchEvent----------ACTION_UP");                break;            default:                break;        }        return super.onTouchEvent(event);    }}

通过反复测试,事件流程如下,这里只给出ACTION_DOWN的事件传递机制,ACTION_UP和ACTION_MOVE与ACTION_DOWN类似。

接下来会讨论Activity、ViewGroup、View这三者间的事件是如何分发的,这里我们将Activity、ViewGroup、View比作“产品经理(Activity)”、“项目主管(ViewGroup)”、“开发人员(View)”,依次分发任务。通过下图可以很清楚的了解这三者间的事件传递机制:

##总结

Activity、ViewGroup、View的事件分发机制如下:

1、Activity的dispatchTouchEvent的两种走向:

  • return true或false-------------------------------------->事件被消费掉
  • super.dispatchTouchEvent(ev)-------------------------->事件分发给(ViewGroup)

2、ViewGroup的dispatchTouchEvent的三种走向:

  • return true----------------------------------------------->事件被消费掉
  • return false----------------------------------------------->事件向上传递给Activity的onTouchEvent
  • super.dispatchTouchEvent(ev)---------------------------->事件分发给自身的onInterceptTouchEvent处理

3、ViewGroup的onInterceptTouchEvent的两种走向:

  • return true------------------------------------------------->进行拦截,事件自身处理,调用自身的onTouchEvent
  • return false或super.onInterceptTouchEvent(ev)------------->不进行拦截,事件往下传递,调用View的dispatchTouchEvent

4、ViewGroup的onTouchEvent的两种走向:

  • return true-------------------------------------------------->事件被消费掉
  • return false或super------------------------------------------>事件向上传递给Activity的onTouchEvent

5、View的dispatchTouchEvent的三种走向:

  • return true--------------------------------------------------->事件被消费掉
  • return false--------------------------------------------------->事件不进行分发,向上传递给ViewGroup的onTouchEvent
  • return super--------------------------------------------------->事件分发给自身的onTouchEvent处理

6、View的onTouchEvent的两种走向:

  • return true------------------------------------------------------>事件被消费掉
  • return false或super---------------------------------------------->事件向上传递给ViewGroup的onTouchEvent

7、如果事件不被中断的话,整个事件流的走向是这样的:

自上而下:dispatchTouchEvent(Activity)----->dispatchTouchEvent(ViewGroup)---------->onInterceptTouchEvent(ViewGroup)------->dispatchTouchEvent(View)
自下而上:onTouchEvent(View)-------->onTouchEvent(ViewGroup)-----------onTouchEvent(Activity)

8、dispatchTouchEvent和onTouchEvent返回true,整个事件就被停止掉。
dispatchTouchEvent和onTouchEvent返回false,事件会被回传给父控件的onTouchEvent处理。

9、dispatchTouchEvent返回tue被称为事件消费掉,也就是事件被终结了,那返回false时,就像上面所说的,事件被回传给父控件的onTouchEvent处理,通俗的讲就是事件不再往子View传递和分发,同时事件进行回传,也就是事件递归停止然后进行回传。

10、onInterceptTouchEvent的作用就是进行事件的拦截,通过返回true进行拦截,事件交由自身来处理,返回false不进行拦截,事件往下传递,默认是return super.onInterceptTouchEvent不进行拦截,返回false和super.onInterceptTouchEvent都一样,是不拦截的意思。

11、ViewGroup想把事件分发到自己的onTouchEvent处理的话,dispatchTouchEvent就得返回super.dispatchTouchEvent(ev),返回false事件向上传递,返回true事件被消费掉。因此通过返回super.dispatchTouchEvent(ev)将事件交由自身处理,最终通过 onInterceptTouchEvent返回 true 将事件拦截下来,交给自己的onTouchEvent处理。

12、由于View没有 onInterceptTouchEvent方法,因此dispatchTouchEvent返回super.dispatchTouchEvent(ev)来实现自己的onTouchEvent方法。

13、onInterceptTouchEvent本身不能消费事件,它只是起到一个分流的作用。

更多相关文章

  1. Android的消息循环机制 Looper Handler类分析
  2. [置顶] Android(安卓)studio build.gradle 各种错误解决总结
  3. 【DiskLruCache完全解析】Android(安卓)AdapterView图片硬盘缓存
  4. Androidの短信拦截方法详解
  5. Android搜索过滤
  6. android前端和java后端通过RSA加密方式传递数据时出现javax.cryp
  7. Android(安卓)kotlin之对象和类(2)
  8. 五、android中解析xml
  9. Android(安卓)Scroll 详解

随机推荐

  1. android 系统定制的小技巧(网络收集)
  2. 零碎知识点回顾——让android studio使用
  3. Android P九轴传感器数据读取
  4. Android Window 二 可移动悬浮窗口 Windo
  5. Android 如何识别判断小米 魅族 华为 系
  6. ImageView属性小结
  7. Android获取meta-data
  8. 最新Android 7.1.1 截屏方法
  9. Android 简单笔记
  10. 第一次执行Cordova build android失败原