Android之Android触摸事件传递机制

转载请标明出处:
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方法,所有的触摸事件都是通过这个方法来分发,它方法原型如下:

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

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

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

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

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

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

@Override
public 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本身不能消费事件,它只是起到一个分流的作用。

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值